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 ↓
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
@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]
When using Validation in non ActiveRecords, when is Validation triggered?
@Josef Validation is triggered when calling the “valid?” method on an instance. The “errors” method will then return any errors.
[...] Sexy Validation in Edge Rails (Rails 3) [...]
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).
Well Illustrated!
Quick check : In the second example snippet IntenseFilmTitleValidator, you should probably add line no:3, unless value =~ /The/
[...] Validations get sexy [...]
@sharanya thanks, well spotted, I have updated the example
@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.
Awesome validations, i love the new way of custom validations its really useful..
Thanks for sharing!
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.
@Joe Yes that will work too.
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?
@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.
How can i validate case sensitive uniqueness with sexy validation?
@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 }
[...] Sexy Validation in Rails 3 [...]
[...] Sexy Validation in Rails 3 [...]
Hi,
What about :allow_nil? I’ve tried `validates :email, :email => {:allow_nil => true}`, but it hasn’t worked.
@Ravicious I believe your email validator would need to support the :allow_nil option.
Leave a Comment