- 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")
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:
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).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 codeInteger("5555555555")
it will output the integer value5555555555
, however if you runInteger("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:
phone
needs to only contain integer values in the stringphone
can only have10
characters (this app will only send to US numbers)body
cannot exceed160
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.
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!