Validating Data with ActiveRecord
Problem
You want to prevent bad data from getting into your ActiveRecord data objects, whether the source of the data is clueless users or buggy code.
Solution
The simplest way is to use the methods defined by the ActiveRecord::Validations module. Each of these methods (validates_length_of, validates_presence_of, and so on) performs one kind of validation. You can use them to declare restrictions on the data in your objects fields.
Lets add some validation code to the Comment class for the weblog application first seen in Recipe 13.11. Recall that a Comment object has two main fields: the name of the author, and the text of the comment. Well reject any comment that leaves either field blank. Well also reject comments that are too long, and comments whose body contains any string from a customizable list of profane words.
require cookbook_dbconnect activerecord_connect class Comment < ActiveRecord::Base @@profanity = %w{trot krip} @@no_profanity_re = Regexp.new(^(?!.*( + @@profanity.join(|) + ))) validates_presence_of %w{author} validates_length_of :content, :in => 1..200 validates_format_of :content, :with => @@no_profanity_re, :message => contains profanity end
Comment objects that don fit these criteria won be saved to the database.
comment = Comment.create comment.errors.on author # => "can be blank" comment.errors[content] # => "is too short (minimum is 1 characters)" comment.save # => false comment = Comment.create(:content => x * 1000) comment.errors[content] # => "is too long (maximum is 200 characters)" comment = Comment.create(:author => Alice, :content => "About what Id expect from a trotting krip such as yourself!") comment.errors.count # => 1 comment.errors.each_full { |msg| puts msg } # Content contains profanity comment = Comment.create(:author => Alice, :content => I disagree!) comment.save # => true
Discussion
Every ActiveRecord record has an associated ActiveRecord::Errors object, which starts out empty. Before the record is saved to the database, all the predefined restrictions for that class of object are checked. Every problem encountered while applying the restrictions adds an entry to the Errors object.
If, at the end of this trial by ordeal, the Errors object is still empty, ActiveRecord presumes the data is valid, and saves the object to the database.
ActiveRecords Validations module provides many methods that implement validation rules. Apart from the examples given above, the validates_numericality_of method requires an integer value (or a floating-point value if you specify :integer => false). The requires_inclusion_of method will reject any value not found in a predefined list of acceptable values.
If the predefined validation rules aren enough for you, you can also write a custom validation rule using validate_each. For instance, you might validate URL fields by fetching the URLs and making sure they e valid.
The method Errors#each_full prepends each error message with the corresponding field name. This is why the actual error messages look like "is empty" and "contains profanity": so each_full will yield "Author is empty" and "Content contains profanity".
ActiveRecord assumes you named your fields so that these messages will be readable. You can customize the messages by passing in keyword arguments like :message, but then youll need to access the messages with Errors#each instead of Errors#each_full. Heres an alternate implementation of the Comment validation rules that customizes the messages:
require cookbook_dbconnect activerecord_connect class Comment < ActiveRecord::Base @@profanity = %w{trot krip} @@no_profanity_re = Regexp.new(^(?!.*( + @@profanity.join(|) + ))) validates_presence_of %w{author}, :message => Please enter your name. validates_length_of :content, :in => 1..200, :too_short => Please enter a comment., :too_long => Comments are limited to 200 characters. validates_format_of :content, :with => @@no_profanity_re, :message => Try to express yourself without profanity. end
The declarative validation style should be flexible enough for you, but you can do custom validation by defining a validate method. Your implementation is responsible for checking the current state of an object, and populating the Errors object with any appropriate error messages.
Sometimes new objects have different validation rules from existing objects. You can selectively apply a validation rule by passing it the :on option. Pass in :on => :create, and the validation rule will only be triggered the first time an object is saved to the database. Pass in :on => :update, and the validation rule will be triggered every time except the first. You can also define the custom validation methods validate_on_add and validate_on_update as well as just plain validate.
See Also
- Recipe 1.19, "Validating an Email Address"
- Recipe 8.6, "Validating and Modifying Attribute Values"
- The built-in validation methods (http://rubyonrails.org/api/classes/ActiveRecord/Validations/ClassMethods.html)
- Some sample validate implementations (http://rubyonrails.org/api/classes/ActiveRecord/Validations.html)
- The Errors class defines a few helper methods for doing validation in a validate implementation (http://rubyonrails.org/api/classes/ActiveRecord/Errors.html)
- Og defines some declarative validation methods, similar to ActiveRecords (http://www.nitrohq.com/view/Validation/Og)
Категории