Sexy Validation in Edge Rails (Rails 3)

I have just had my sexy validations patch accepted into Rails. Much thanks to José Valim for helping me get this applied.

The reason for the name “sexy validations” is that it gives a much more concise way of defining validation and reusing custom validator classes. Much like what sexy migrations did for defining your database schema.

Simple example of using existing Rails validations, the “sexy” way:


class Film < ActiveRecord::Base
  validates :title, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
  validates :budget, :presence => true, :length => { :within => 1..10000000 }
end

The power of the “validates” method comes though, when using in conjunction with custom validators:


class IntenseFilmTitleValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors[attribute] << "must start with 'The'" unless value =~ /^The/
  end
end

class SpendValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    spend = case options[:size]
      when :big then 100000000
      when :small then 100000
    end
    record.errors[attribute] << "must not exceed #{spend}" if value > spend
  end
end

class Film < ActiveRecord::Base
  validates :title, :presence => true, :intense_film_title => true
  validates :budget, :spend => { :size => :big } # using custom options
end

All validations in Rails, along with other common model functionality have been extracted into ActiveModel, so you can also use validations and Validator classes without ActiveRecord e.g.


class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors[attribute] << (options[:message] || "is not an email") unless
      value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
  end
end

class Person
  include ActiveModel::Validations
  attr_accessor :name, :email

  validates :name, :presence => true, :length => { :maximum => 100 }
  validates :email, :presence => true, :email => true
end

Have fun!

Published by

Jamie

Hi, I am the Managing Director of SonicIQ Limited in the UK. I have been working in the web development industry since 1999 and have been running SonicIQ since 2001. Currently Ruby On Rails is my preferred development platform. I am experienced in designing with web standards, HTML5, CSS3 and Javascript.

43 thoughts on “Sexy Validation in Edge Rails (Rails 3)”

  1. Hi,

    Thanks for this!
    Is it possible to use numericality options with this?

    For example, the following line doesn’t work :

    validates :price, :numericality => true, :greater_than => 100

  2. @ahe you could do the following:

    validates :price, :numericality => true, :length => { :minimum => 100 }

    or, if you wanted a range

    validates :price, :numericality => true, :length => [100..1000]

  3. @Josef Validation is triggered when calling the “valid?” method on an instance. The “errors” method will then return any errors.

  4. It’s great to be able to use validations outside of AR, and to be able to use custom validators easily. But a couple of things here strike me as fairly unsexy.

    First, I would much prefer

    validates_email :email, :presence => true

    over

    validates :email, :presence => true, :email => true

    Any particular reason this isn’t possible for custom validators (or is it)? I think it becomes much clearer what kind of validation it is, and it requires less typing.

    Also, the usage of “length” for something that clearly has nothing to do with length is confusing and odd-looking to me. As in your example

    validates :price, :numericality => true, :length => [100..1000]

    It just makes no sense (unless you’re trying to say that the decimal representation of the price must be between 100 and 1000 characters long).

  5. Well Illustrated!
    Quick check : In the second example snippet IntenseFilmTitleValidator, you should probably add line no:3, unless value =~ /The/

  6. @mikael The intention was to make a single shortcut method to all existing AM validators and any custom ones. Your example of validates_email would mean some method_missing magic which I don’t think is neccessary. Think of the validates method as a shortcut to all the other validates_?_of methods allowing you to specify multiple validations for an attribute with one line.

  7. Jamie, wouldn’t the correct syntax for what ahe wanted be:
    validates :price, :numericality => {:greater_than => 100}

    or something like that? If that is not implemented then it really should be. I mean, that is clearly the right way to pass in options for a validator, and it fits well with true if no options need to be specified, since a hash is definitely a true value in ruby.

  8. Jamie,
    Is there a way to suppress a single / all validations based on a condition. For example

    validates :width, :numericality => true, :unless => :fullscreen?

    In a similar way to what I can do with the old school validates_numericality_of method?

  9. @Thomas Williams To suppress the numericality validator based on the outcome of a given method you would do:

    validates :width, :numericality => { :unless => :fullscreen? }

    You can supply a hash with any options that validates_numericality_of accepts. The “:numericality => true” simply says use the numericality validator, whereas “:numericality => my_options_hash” passes any options to the validator… think of the “validates” method as a shortcut to the “validates_?_of” methods.

    Hope this helps.

  10. @Lisinge The uniqueness validator is case sensitive by default so you could do something like:

    validates :name, :uniqueness => true

    …or if you wanted it not to be case sensitive:

    validates :name, :uniqueness => { :case_sensitive => false }

  11. I got an “Unknown validator: ‘url_exists'” error. This was caused due to the fact, that the ‘lib’ – dir is not autoloaded (anymore in Rails3?). In all tutorials on validators the custom validators are put in this dir. If you’re using it, you have to add the line to your config/application.rb: config.autoload_paths += %W( #{config.root}/lib )
    Cheers.

  12. Not to be an arse, but “excepted” and “accepted” almost have opposite meanings. I’m sure you mean that your code was *accepted*, not singled out for exclusion…

  13. I think I like these, but I am not sure how you’d effectively use custom messages with these. With validates_presence_of, it accepts :message. It’s not clear where I’d place the custom message (when the validation fails) with: validates :, :presence => true

    Thanks!

  14. @kedar You would just pass a hash in instead of true e.g. validates :presence => { :message => ‘My message’ }

    Hope that helps.

  15. Thanks for this post, Jamie!

    Where do you put your validators? I’ve found some other pages saying that lib is the place for custom validators but (as I now see in a comment above), that’s not autoloaded.

    I can get away with putting my validator in config/initializers, but I’d like to know if there’s a convention for files like these. Removing autoloading of the lib folder implies (to me) that this isn’t where the Rails community would expect such files

  16. @Eric The lib folder is fine, just add a “require ‘my_validator'” to the top of the model files that use them.

  17. So we just discovered sort-of-a-design-flaw-not-exactly with sexy validations. The Rails 3 way to do a unique validation is:

    validates :first_name, :uniqueness => true

    But this induces a common error:

    validate :first_name, :uniqueness => true

    And that mistake is *perfectly legal code that runs without error*. It means “Treat the method `first_name` as my validator function”. The :uniqueness => true gets ignored, because it’s an options hash that doesn’t contain any options we care about. first_name will always be an existing method; it’s the first_name field on your model! And that doesn’t add anything to `errors`, so the validation always succeeds.

    Unfortunately, I can’t think of a way around it without deprecating and renaming either validate (perhaps “validator”?) or validates. Jamie, what do you think? Our own team has decided that we will never use the “validate :method_name” syntax; instead, we’ll always use “def validate”, and our CI system will enforce the rule.

  18. @Jay I can see that may be hard to track down if you accidentally leave the “s” off. I don’t really see it as a design flaw though, how about setting up some syntax highlighting to warn when the singular is used (bright red or something)?

  19. Any way to do a validation in stages, for example

    1> check that last_name, first_name, and address aren’t blank
    2> combine them into a variable in the model called validate
    3> make sure that validate is unique

    Hopefully this would make sure that a person doesn’t have two records, and allow a common name to have 2 different addresses.

    I used before_validation and after_validation in 2.x, but not sure here

    Thanks
    Bob

  20. I have a problem with validates_presence_of command in rails 2.3.8.I am using netbeans 6.9.1 and when i check the validation i get this

    {{count}} errors prohibited this {{model}} from being saved

    Please if you know something answer.

  21. Can any one help me on this?

    I have got a dynamic field for ranges, which is rendered in offers page. I want to do certain validations on dynamic fields and it values. They are as follows:

    1) Allowing user to add only two ranges not more than that.

    2) Allowing user to enter atleast one set of ranges.

    3) If the first range is from 1 to 99 then automatically the first field of second range should be 100.

    I am pasting some snippets to make myself more clear:

    offer.rb

    has_many :couponranges, :dependent => :destroy
    accepts_nested_attributes_for :couponranges, :reject_if => lambda { |a| a[:from].blank? }, :allow_destroy => true

    offers/_form.html.erb

    builder %>

    _couponrange_fields.html.erb

    “cupnrang_lbl” %>
    “6”, :id => “coupon_range_from”, :onclick =>’tooltiparrow11()’,
    :class=> “cupnrang_txt”,:placeholder=>’ e.g. $25 ‘ %>

    “cupnrang_lbl” %>
    “6”, :id => “coupon_range_to” , :class=> “cupnrang_txt”,:placeholder=>’e.g. $99 ‘ %>

  22. Can any one help me on this?

    I have got a dynamic field for ranges, which is rendered in offers page. I want to do certain validations on dynamic fields and it values. They are as follows:

    1) Allowing user to add only two ranges not more than that.

    2) Allowing user to enter atleast one set of ranges.

    3) If the first range is from 1 to 99 then automatically the first field of second range should be 100.

    I am pasting some snippets to make myself more clear:

    offer.rb

    has_many :couponranges, :dependent => :destroy
    accepts_nested_attributes_for :couponranges, :reject_if => lambda { |a| a[:from].blank? }, :allow_destroy => true

    offers/_form.html.erb

    builder %>

    _couponrange_fields.html.erb

    “cupnrang_lbl” %>
    “6”, :id => “coupon_range_from”, :onclick =>’tooltiparrow11()’,
    :class=> “cupnrang_txt”,:placeholder=>’ e.g. $25 ‘ %>

    “cupnrang_lbl” %>
    “6”, :id => “coupon_range_to” , :class=> “cupnrang_txt”,:placeholder=>’e.g. $99 ‘ %>

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>