Sexy Validation in Edge Rails (Rails 3)

I have just had my sexy validations patch excepted 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!

21 comments ↓

#1 ahe on 02.15.10 at 10:14 pm

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 Jamie on 02.15.10 at 11:25 pm

@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 Schmitz on 02.19.10 at 10:15 am

When using Validation in non ActiveRecords, when is Validation triggered?

#4 Jamie on 02.22.10 at 4:12 pm

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

#5 Validações personalizadas no Rails 3 – :LenOn_Rails on 03.05.10 at 12:15 am

[...] Sexy Validation in Edge Rails (Rails 3) [...]

#6 mikael on 03.07.10 at 7:08 am

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).

#7 Sharanya on 03.10.10 at 10:20 am

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

#8 Rails 3 Resources | Web Development News on 03.12.10 at 2:37 pm

[...] Validations get sexy [...]

#9 Jamie on 03.15.10 at 9:26 am

@sharanya thanks, well spotted, I have updated the example

#10 Jamie on 03.15.10 at 9:33 am

@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.

#11 Waheedi on 04.01.10 at 12:36 pm

Awesome validations, i love the new way of custom validations its really useful..

Thanks for sharing!

#12 Joe Smith on 04.01.10 at 7:58 pm

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.

#13 Jamie on 04.01.10 at 8:06 pm

@Joe Yes that will work too.

#14 Thomas Williams on 05.19.10 at 1:20 pm

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?

#15 Jamie on 05.19.10 at 1:35 pm

@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.

#16 Lisinge on 05.20.10 at 4:39 pm

How can i validate case sensitive uniqueness with sexy validation?

#17 Jamie on 05.26.10 at 12:41 pm

@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 }

#18 Ruby on Rails / Ruby on Rails 3 — Заметки к финальному релизу on 08.29.10 at 7:46 pm

[...] Sexy Validation in Rails 3 [...]

#19 Ruby on Rails / [Перевод] Ruby on Rails 3 — Заметки к финальному релизу on 08.29.10 at 8:13 pm

[...] Sexy Validation in Rails 3 [...]

#20 Ravicious on 08.31.10 at 6:27 pm

Hi,

What about :allow_nil? I’ve tried `validates :email, :email => {:allow_nil => true}`, but it hasn’t worked.

#21 Jamie on 08.31.10 at 10:52 pm

@Ravicious I believe your email validator would need to support the :allow_nil option.

Leave a Comment