Validate Ruby objects with Active Model Validations
Tags: ruby, twilio
Published: 2017-07-11 14:03:01

---
title: Validate Ruby objects with Active Model Validations
published: true
description: Active Model Validations means we can use Rails style validations on any Ruby object. Learn how in this post.
tags: ruby, Twilio
canonical_url: https://www.twilio.com/blog/2017/06/validate-ruby-objects-with-active-model-validations.html
cover_image: https://thepracticaldev.s3.amazonaws.com/i/b9ixn0g3ixsn2613ifao.png
---

In the world of Rails and Active Record, validating data and storing it in a database is easy. If you'd ever <a href="https://dev.to/philnash/create-a-landing-page-with-sinatra-google-spreadsheets-and-ruby">built a simple site that stores data in a Google Spreadsheet</a> then you'd quickly learn that users can enter anything (or nothing). In this post we'll see how to validate input using part of Active Record: `ActiveModel::Validations`.

## Spreadsheets can be databases too

In my last post we built a <a href="https://dev.to/philnash/create-a-landing-page-with-sinatra-google-spreadsheets-and-ruby">landing page for a new app using Sinatra and Google Spreadsheets as the database</a>. The app needs one improvement though; users can enter any data they want in the form and it will happily submit. We need to guarantee we get some real data if we are going to contact our users when the app launches.

We should validate our input and ensure that

* We get at least a name and email address
* If we get an email address, it looks like a valid email address
* If we get a phone number then it is a valid number

Let's improve this app so it can do all of the above.

## Getting setup

You'll need a Google Spreadsheet and the <a href="https://dev.to/philnash/create-a-landing-page-with-sinatra-google-spreadsheets-and-ruby">application from the previous post</a> if you want to follow along with the code in this tutorial. Don't worry if you haven't got those set up, the instructions to do so are below.

If you don't already have a Spreadsheet setup and permissions to edit it via the API then <a href="https://dev.to/philnash/google-spreadsheets-and-ruby">follow this post to generate your credentials</a>, saved as a file called `client_secret.json`. Make sure to give your service edit access to your spreadsheet too.

Next, clone or download the application and check out the save-data branch. This is the current state of the app from the end of <a href="https://dev.to/philnash/create-a-landing-page-with-sinatra-google-spreadsheets-and-ruby">the previous blog post</a>.

```bash
git clone https://github.com/philnash/ruby-google-sheets-sinatra.git
cd ruby-google-sheets-sinatra
git checkout save-data
```

Install the dependencies with Bundler and run the application and visit at <a href="http://localhost:4567">http://localhost:4567</a> to make sure it's working as expected.

```bash
bundle install
bundle exec ruby app.rb
```

<img alt="An animation showing a user completing the form in the application we are working with, and successfully posting their data to the spreadsheet." src="https://twilioinc.wpengine.com/wp-content/uploads/2017/06/V0nFfq0H-tlU9rIaofpRtScv8BQUSoXtcZY-D0lndme3R0kVai5HHWbvm1XFe7q8Ro1UgzGFuvnjOCt_e2OG0DgWxy7y1jHMyOfR5xQw8EbpuWIyedJ0VMdmL85BM5P7LWMe7So.png" class='aligncenter'>

To start with, the application needs a bit of a refactor.

## Plain old Ruby objects

Currently the Sinatra application we built just takes the form parameters the user submits and sends them straight to the Google Sheets API. To start our refactoring of this process it would be better to capture the input as an object that we can then reason about. Create a class we can use to encapsulate this data.

```ruby
class UserDetails
  attr_reader :name, :email, :phone_number
  def initialize(name=nil, email=nil, phone_number=nil)
    @name = name
    @email = email
    @phone_number = phone_number
  end

  def to_row
    [name, email, phone_number]
  end
end
```

Give the class an initializer that receives the name, email and phone number as arguments and stores them as instance variables. Include an `attr_reader` to expose these properties and a `to_row` method that returns the properties in the correct format to add a row to the spreadsheet.

Now we can update our route to use this object instead of the raw parameters.

```ruby
post "/" do
  user_details = UserDetails.new(params["name"], params["email"], params["phone_number"])
  begin
    worksheet.insert_rows(worksheet.num_rows 1, [user_details.to_row])
    worksheet.save
    erb :thanks
  rescue
    erb :index, locals: {
      error_message: "Your details could not be saved, please try again."
    }
  end
end
```

If you start the application again you should find it still works as before. The work we've done so far is just a useful refactoring for the next part; validating the data.

## Validation with ActiveModel::Validations

To make validating our user details easy and recognisable to any Rails developer, we can use `ActiveModel::Validations` to validate the object. Start by adding ActiveModel to your `Gemfile`.

```ruby
# frozen_string_literal: true
source "https://rubygems.org"

gem "sinatra"
gem "google_drive"
gem "activemodel", require: "active_model"
```

Update your dependencies by running `bundle install`. We're ready to add some validations to our objects.

### Out of the box validations

Validating that the user submits a name and email address is nice and easy with `ActiveModel::Validations`. Let's add the validations to the `UserDetails` class. Include the `ActiveModel::Validations` module into the `UserDetails` class. Then use the <a href="http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates">class method `validates`</a> to validate the presence of both the name and email fields.

```ruby
class UserDetails
  include ActiveModel::Validations

  attr_reader :name, :email, :phone_number

  validates :name, presence: true
  validates :email, presence: true

  # etc
end
```

You can test this has worked by loading the application up in irb. Just run

```bash
irb -r ./app.rb
```

This will give you a Ruby REPL with access to the `UserDetails` class.

```ruby
user_details = UserDetails.new
user_details.valid?
#=> false
user_details.errors.full_messages
#=> ["Name can't be blank", "Email can't be blank"]
user_details2 = UserDetails.new("Phil", "Phil's email")
user_details2.valid?
#=> true
```

`ActiveModel::Validations` has added the `valid?` and `errors` methods to the `UserDetails` object. Those will be useful later, there are more validations to write first.

We also wanted to check that the email address at least looks like an email address. Email validation doesn't need to be 100% perfect, the only real way to check an email address belongs to the person that entered it is to send it an email, but it would be nice to catch obvious typos. We can use the format validation to check against a regular expression. I have no wish to write my own regular expression to check for email address formats, so I'm going to borrow one from <a href="https://github.com/plataformatec/devise/blob/ee01bac8b0b828b3da0d79c46115ba65c433d6c8/lib/devise.rb#L113">Devise</a>.

```ruby
class UserDetails
  include ActiveModel::Validations

  attr_reader :name, :email, :phone_number

  validates :name, presence: true
  validates :email, presence: true, format: { with: /A[^@s] @[^@s] z/, allow_blank: true }

  # etc
end
```

### Custom validations

We want to check that if the user supplies a phone number that it looks valid too. There is no out of the box validation or good regular expression for this. This is where custom validations are useful. The next thing to do is to write a custom validation using the <a href="https://www.twilio.com/docs/api/lookups">Twilio Lookups API</a>, wrapping up the technique Greg showed when he wrote about <a href="https://www.twilio.com/blog/2015/04/validate-phone-numbers-in-ruby-using-the-lookup-api.html">verifying phone numbers in Ruby</a>.

First, we should add the <a href="https://www.twilio.com/docs/libraries/ruby">Twilio Ruby gem</a> to the `Gemfile`.

```ruby
# frozen_string_literal: true
source "https://rubygems.org"

gem "sinatra"
gem "google_drive"
gem "activemodel", require: "active_model"
gem "twilio-ruby"
```

Install the latest dependencies with `bundle install`. 

When you write a custom validation for an attribute you need to create a subclass of `ActiveModel::EachValidator` and implement the `validate_each` method. 

Create a new `Twilio::REST::LookupsClient` to use to access the <a href="https://www.twilio.com/docs/api/lookups">Lookups API</a>. I use environment variables to supply my Twilio credentials, you can do the same (check out this post by Dominik on <a href="https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html">how to set environment variables</a> if you don't know how) or just add in your actual Account SID and Auth Token from your <a href="https://www.twilio.com/console">Twilio console</a>.

Look up the value, which will be the phone number submitted by the user, with the `lookups_client`, with `response = lookups_client.phone_numbers.get(value)`. The Twilio library is lazy, so it doesn't actually perform the request until we try to inspect a property of the result, so call on `response.phone_number`. In this case, the API will return successfully if the phone number is real and will return a 404 error if it isn't. <a href="https://www.twilio.com/blog/2015/04/validate-phone-numbers-in-ruby-using-the-lookup-api.html">As Greg described</a> you need to `rescue` if there is a `Twilio::REST::RequestError` and check for a 20404 code (<a href="https://www.twilio.com/docs/api/errors/20404">20404 is the Twilio API's version of a 404</a>). If it is 20404, add an error to the record's errors object for the attribute being validated. If some other error occurred then raise the error again.

```ruby
class PhoneNumberValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    lookups_client = Twilio::REST::LookupsClient.new(ENV["TWILIO_ACCOUNT_SID"], ENV["TWILIO_AUTH_TOKEN"])
    begin
      response = lookups_client.phone_numbers.get(value)
      response.phone_number
    rescue Twilio::REST::RequestError => error
      if error.code == 20404
        record.errors[attribute] << (options[:message] || 'is not a valid phone number')
      else
        raise error
      end
    end
  end
end
```

The custom validator is written, so we can use it like our existing ones. Since the phone number is not a required field, add `allow_blank: true` to the options.

```ruby
  validates :name, presence: true
  validates :email, presence: true, format: { with: /A[^@s] @[^@s] z/, allow_blank: true }
  validates :phone_number, phone_number: { allow_blank: true }
```

## Validating on submit

We have written our validations for our class, so they need to be put to action. Earlier we updated our `post '/'` route to use the `UserDetails` class. Now we need to update again to use our new `valid?` method to render the index again if the object isn't valid.

```ruby
post "/" do
  user_details = UserDetails.new(params["name"], params["email"], params["phone_number"])
  if user_details.valid?
    begin
      worksheet.insert_rows(worksheet.num_rows 1, [user_details.to_row])
      worksheet.save
      erb :thanks
    rescue
      erb :index, locals: {
        error_message: "Your details could not be saved, please try again."
      }
    end
  else
    erb :index
  end
end
```

Restart the application and load it up in the browser. Try submitting an empty form. You should no longer see the page that says "thank you", rather the index will be rendered again. But we're getting no feedback either. We need to send our `user_details` object to the view and use the `user_details.errors` object to display error messages. To make it easy on our view, update both routes to send a `user_details` object.

```ruby
get "/" do
  erb :index, locals: { user_details: UserDetails.new }
end

post "/" do
  user_details = UserDetails.new(params["name"], params["email"], params["phone_number"])
  if user_details.valid?
    begin
      worksheet.insert_rows(worksheet.num_rows 1, [user_details.to_row])
      worksheet.save
      erb :thanks
    rescue
      erb :index, locals: {
        error_message: "Your details could not be saved, please try again."
      }
    end
  else
    erb :index, locals: { user_details: user_details }
  end
end
```

Update the form in `views/index.erb` to display the feedback. You can use `user_details.errors.include?(attribute_name)` to calculate whether the field does have any errors, and add a `"has-error"` class to the surrounding `<div>`. To display the errors for an attribute, loop through `user_details.errors.full_messages_for(attribute_name)`, printing out the error message each time.

Here's the fully updated form from the template:

```html
        <form action="/" method="POST">
          <div class="form-group<%= ' has-error' if user_details.errors.include?(:name) %>">
            <label for="name" class="control-label">Name</label>
            <input type="text" class="form-control" name="name" id="name" value="<%= user_details.name %>">
            <% user_details.errors.full_messages_for(:name).each do |message| %>
              <span class="help-block"><%= message %></span>
            <% end %>
          </div>
          <div class="form-group<%= ' has-error' if user_details.errors.include?(:email) %>">
            <label for="email" class="control-label">Email address</label>
            <input type="email" class="form-control" name="email" id="email" value="<%= user_details.email %>">
            <% user_details.errors.full_messages_for(:email).each do |message| %>
              <span class="help-block"><%= message %></span>
            <% end %>
          </div>
          <div class="form-group<%= ' has-error' if user_details.errors.include?(:phone_number) %>">
            <label for="phone_number" class="control-label">Phone number</label>
            <input type="tel" class="form-control" name="phone_number" id="phone_number" value="<%= user_details.phone_number %>">
            <% user_details.errors.full_messages_for(:phone_number).each do |message| %>
              <span class="help-block"><%= message %></span>
            <% end %>
          </div>
          <button type="submit" class="btn btn-primary">Submit</button>
        </form>
```

Restart the app one more time and try to enter some invalid data.

<img alt="Now when you try to submit the form without any data you will see error messages on the form elements that are invalid." src="https://twilioinc.wpengine.com/wp-content/uploads/2017/06/36vpuazoWKFdCUTrJFYhzzV4xBPuKt2ZDWQ-MPwgPrdzy6MS4yU4iQjHlWek6pDM2FBOWqI33CqzJ5iG74x7iD1MfxTRTG6Dn2M484AG7hAOHDbJu6e4Lp00tbdTefpfUwBoLuU.png" class="aligncenter">

Success! We are validating our data and showing feedback to the user if something has gone wrong. Fill in the correct data and it is posted through to our Google Spreadsheet.

## A spreadsheet full of sensible data

You've seen how to validate data easily within any Ruby application, including writing custom validations. And our main Ruby app is still under 70 lines long. Check out <a href="https://github.com/philnash/ruby-google-sheets-sinatra/tree/validations">this branch </a><a href="https://github.com/philnash/ruby-google-sheets-sinatra/tree/validations">on GitHub with the final code for our application</a>.

`ActiveModel` has some other useful modules you can take advantage of outside of Rails applications. Check out <a href="http://guides.rubyonrails.org/active_model_basics.html#translation">`ActiveModel::Translation` for internationalisation</a> or <a href="http://guides.rubyonrails.org/active_model_basics.html#serialization">`ActiveModel::Serialization` if you are building an API</a>.

Have you used `ActiveModel::Validations` without Active Record before? Or built applications using other Rails components outside of Rails. I'd be interested to hear what you're experiences have been. Drop me a comment below or hit me up on Twitter at <a href="https://twitter.com/philnash">@philnash</a>.

___

_[Validate Ruby objects with Active Model Validations](https://www.twilio.com/blog/2017/06/validate-ruby-objects-with-active-model-validations.html) was originally published on the [Twilio blog](https://www.twilio.com/blog) on June 14, 2017._