Ensuring Data Integrity with ActiveRecord Validations
In this guide we will work through adding additional data validations to our ActiveRecord models to ensure that the API will only accept valid requests.
Guide Tasks
  • Read Tutorial

In this guide we will work through adding additional data validations to our ActiveRecord models to ensure that the API will only accept valid requests. Let's see what happens when we try to pass our API some invalid data. Open up a rails console session in sandbox mode to run the tests so that we don't pollute our database:

rails c --sandbox

First let's try to create a Notification with a phone number only 3 digits long:

Notification.create!(phone: "123", body: "My message", source_app: "SomeApp")

large

As shown, this works, even though our app should have blocked a phone number with only 3 digits from being created. Now let's try to create a notification where the phone number being passed in has words:

Notification.create!(phone: "uh oh", body: "My message", source_app: "SomeApp")

Once again, this works. If we were to let these types of values to be passed to the API without validating the data we could run into a number of issues, including:

  1. The client sending the request would have no idea why the notifications weren't being sent, because our API would return a success message, but our application would be getting error messages from Twilio (the service we'll be using to send SMS messages).

  2. In the future if we try to run reports on the data, such as passing the Integer method to convert the numbers to integers we could end up with nasty data type conversion bugs. For example, if you run the code Integer("5555555555") it will output the integer value 5555555555, however if you run Integer("yikes") it will through an argument error. So we need to know that the string only contains integers.

Let's be organized developers and list out each of the data validation requirements so we can go down the list and create our tests:

  1. phone needs to only contain integer values in the string
  2. phone can only have 10 characters (this app will only send to US numbers)
  3. body cannot exceed 160 characters

If we implement these three data validations we should be able to be confident of our data integrity and that services connecting to the API will receive the proper error messages if they pass invalid parameters.

Implementing Validation Specs

These tests should be isolated from the rest of the application's functionality, so let's add them to the model specs. We already have a validation describe block in place, so let's add some new specs to it:

# spec/models/notification_spec.rb

    xit 'requires the phone attribute to contain a string of integers' do
    end

    xit 'requires the phone attribute to only have 10 characters' do
    end

    xit 'limits the body attribute to 160 characters' do
    end

With these placeholder specs in place let's write the first test:

# spec/models/notification_spec.rb

    it 'requires the phone attribute to contain a string of integers' do
      notification = FactoryGirl.build_stubbed(:notification)
      notification.phone = "atextphonenumber"
      expect(notification).to_not be_valid
    end

This creates a notification stub and checks to see if it's valid if the phone number is changed to a string filled with text values instead of integers. Obviously this fails since we haven't implemented this feature yet. Let's add in a validation in the model file:

# app/models/notification.rb

class Notification < ActiveRecord::Base
  validates_presence_of :phone, :body, :source_app
  validates_numericality_of :phone
end

This was actually a pretty easy one since we can leverage the validates_numericality_of Rails validation method. This validation will try to convert the value to a float and if it does it passes the validation. You could also use a Regex matcher for this, however I tend to prefer using built in methods since typically the edge cases have been fully tested compared with building a matcher from scratch. If you run rspec you'll see that the tests are passing again, so we can check this one off the list. For the refactor step let's move the stub creation method into a before block to remove the duplicate code in both of the specs that are live, make sure to also change each of the variables to instance variable calls:

# spec/models/notification_spec.rb

  describe 'validations' do
    before { @notification = FactoryGirl.build_stubbed(:notification) }

    it 'can be created if valid' do
      @notification.phone = nil
      @notification.body = nil
      @notification.source_app = nil
      expect(@notification).to_not be_valid
    end

    it 'requires the phone attribute to contain a string of integers' do
      @notification.phone = "atextphonenumber"
      expect(@notification).to_not be_valid
    end

    xit 'requires the phone attribute to only have 10 characters' do
    end

    xit 'limits the body attribute to 160 characters' do
    end
  end

Now let's move onto the next spec and add in what we're trying to test:

# spec/models/notification_spec.rb

    it 'requires the phone attribute to only have 10 characters' do
      @notification.phone = "12345678901"
      expect(@notification).to_not be_valid
    end

This will update the string character count to 11, which would be an invalid number to send to, this will cause a failure. Let's implement the validation back in the model and this time we'll use the length validator and pass in the argument that says that the values saved for the phone number have to be exactly 10:

# app/models/notification.rb

class Notification < ActiveRecord::Base
  validates_presence_of :phone, :body, :source_app
  validates_numericality_of :phone
  validates :phone, length: { is: 10 }
end

If you run the specs you'll see that the tests are passing, so just one validation left and it will be very similar to the last one, except this time it will limit the character length to 160 for the body attribute:

# spec/models/notification_spec.rb

    it 'limits the body attribute to 160 characters' do
      @notification.body = "word" * 500
      expect(@notification).to_not be_valid
    end

As expected this will give us a failure, let's implement this validation in the model:

# app/models/notification.rb

class Notification < ActiveRecord::Base
  validates_presence_of :phone, :body, :source_app
  validates_numericality_of :phone
  validates :phone, length: { is: 10 }
  validates :body, length: { maximum: 160 }
end

For this validation we're using the maximum argument for the length value. If you run the specs you'll see that everything is passing, and if we open up the rails console and try the same command that we ran earlier in the lesson you'll see that it fails instantly.

large

I'm happy with where we're at from a validation perspective and how our API works, so in the next section we're going to build out the ability for our app to send SMS messages!

Resources