1. Improved validations in Rails 3

    Quite sorry about not getting another post up sooner; I’ve been very busy lately with a few different things (some of which shall be revealed very soon!). Today I want to talk about validations. Validations received quite a bit of love in Rails 3, but if you don’t go looking for the new shiny features, you won’t find them. The old API is still around, so nothing will break if you try them, but there is also a variation on that theme:

    validates :login, :presence => true, :length => {:minimum => 4},
              :uniqueness => true, :format => { :with => /[A-Za-z0-9]+/ }
    

    This new form is excellent since you can compress what would have previously been 4 lines of code into 1, making it dead simple to see all the validations related to a single attribute all in one place. The valid keys/value types for this form are:

    As I mentioned previously, you can still use the old API, but it makes sense to switch to this form since, when scanning your code, you’re rarely looking for what sort of validation it is rather than the attribute that’s being validated.

    Another great new validation feature is the ability to have a custom validation class. It’s fairly common for Rails developers to develop their own validation methods that look something like this:

    def validates_has_proper_category
      validates_each :category_id do |record, attr, value|
        unless record.user.category_ids.include?(value)
          record.errors.add attr, 'has bad category.'
        end
      end
    end
    

    These methods are really useful, especially if you use this validation in a lot of different classes, but they often add a bit of ugly code. Fortunately, in Rails a lot of that nastiness can go away. Those old methods should still work, but you could make them look like this instead:

    class ProperCategoryValidator < ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        unless record.user.category_ids.include?(value)
          record.errors.add attribute, 'has bad category.'
        end
      end
    end
    

    Basically, create a class that inherits from ActiveModel::EachValidator and implements a validate_each method; inheriting from this class will make it available to all Active Record classes. Not only is the code a bit cleaner, since you can spread it out a bit more if it’s hairy without polluting the model class, it also makes these validations easily testable without much hassle, and you can also integrate them into the short form validations like this:

    validate :category_id, :proper_category => true
    

    Note that the key name is taken from the class name (i.e., ProperCategoryValidator becomes :proper_category). A similar new feature is the ability to have validator classes that bundle validations into a single object. If you have a lot of classes that need some very complex validation logic, you can create a class like this:

    class ReallyComplexValidator < ActiveModel::Validator
      def validate(record)
        record.errors[:base] << "This check failed!" unless thing(record)
        record.errors[:base] << "This failed!" unless other(record)
        record.errors[:base] << "FAIL!" unless fail(record)
      end
    
    private
      def thing(record)
        # Complex validation here...
      end
    
      def other(record)
        # Complex validation here...
      end
    
      def fail(record)
        # Complex validation here...
      end
    end
    

    The API is basically to inherit from ActiveModel::Validator and implement a validate method that takes a record as its only argument. Then in your model classes, use it like so:

    class NewsPost < ActiveRecord::Base
      validates_with ReallyComplexValidator
    end
    

    This pattern is nice for wrapping up a lot of unruly validation code, but a more interesting variation on it will be building class factories (o noez not a factory!) based on parameters that build these validation classes (i.e., a class that generates validation classes). You can find a little more information these and other Active Model validation features in its API documentation at http://api.rails.info/classes/ActiveModel/Validator.html .

    Comments

Powered by Tumblr; designed by Adam Lloyd.