Tuesday, 6 April 2010

Rails - Day 16 - Validation

Rails  - Day 1 - The RubyGem Love Story
Rails -  Day 10 - Testing Rails Controllers
Rails -  Day 13 -ActiveRecord Relationships Part II
Rails - Day 15 - Rails Forms Part II
In the last post, we took a deeper look into working with a complex object and the Rails form builder.

We have been using a mock application example of an expense tracker to illustrate the key points.  The expense tracker will record the expenses incurred running a small business.

The complex object in question is an Expense model object that is made up of the following structure:

Below is the form we have constructed to capture an expense structure that is created from the inputs submitted in this form:

One thing that has not been mentioned so far is how to validate the user supplied input that is sent from this form to the server.  In this post we want to ensure that:

  • The External reference input has a value.
  • The posting Description input has a value
  • The user has selected an Expense Type from the dropdown
  • The net field has a valid monetary value added.  The net field is an attribute of the Expense's child object ExpensePayment.

If we take our first requirement from the above, we can create the following unit test to ensure our assertions:

Notice that there is also a test to check that the expense object is valid.  It is important to start from a good position and then assert failures.

In the above test, we are creating a valid @expense object and then setting the field we want to test (external_reference) to nil.

This test obviously fails as no validation instructions have been added on the Expense model.  

Rails Model Validators

When developing a Ruby on Rails application it is a good idea to use ActiveRecord validators to ensure the state of a model before trying a persist it to the database.  This type of validation is also known as first pass validation.

The most common approach is to use the declarative validates_xxxx_of class methods.

In order to make the above test pass, we use the validates_presence_of ActiveRecord class method to ensure an expense has an expense_payment attribute:

Our test now passes.  The validates_presence_of method takes a list of attribute names that we want to ensure have non nil values.  

Other similar attributes are:

We can validate the other attributes that we want to ensure are required like this:

validates_presence_of :external_reference, :description, :expense_date

But what about associated objects?  How can we verify that the user has selected an ExpenseType from the dropdown.  In our model, an Expense belongs to an ExpenseType.

In order to test this, we create the following test:

This type of relationship is very easy to validate.

The above test obviously fails but if we update the model to add the expense_type_id to the list of symbols passed as arguments to the validates_presence_of then all is good.

Our final two requirements concern the has_one  ExpensePayment relationship.  There are two things we need to test:

  • We need to ensure that there is an ExpensePayment child object contained within the Expense object.
  • We need to ensure that the contained child ExpensePayment  object has a non nil name field.

As ever we create the following failing tests:

In order to make the first test pass which ensures that an expense_payment object is contained with in the Expense model, we just update the symbol list validates_presence_of to include the :expense_payment attribute

This will enable our first assertion to pass but the second assertion still fails.

The first step is to add a call to validates_presence_of class method in the ExpensePayment object and include the net attribute.

We then update the  Expense object to include a call to the ActiveRecord validation class method validates_associated.

Above we can see the call to validates_associated :expense_payment which will instruct the framework to include the child object validations of the ExpensePayment model in the @expense.errors array.

With our validation tested, we now need to add the following to our new.html.haml view that will take care of displaying any validation errors:

And that is it, the error_messages_for :expense expression will display any error messages contained within the @expenses.errors array.

Below is how these error messages are displayed on the page.  Obviously you would style the div containing the error messages but as this is post 16, I simply cannot be bothered:

Obviously, this type of validation is very simplistic.  There are many hooks to get into this validation process for more complex scenarios. There are callbacks available on :before_save or :after_save or you can even override the validate_on_create or validate_on_update class methods.

In this post, we have discussed validation at a very high level.

In the next post, we will finish off the series by completing the update and delete actions.

No comments:

Post a Comment