diff options
Diffstat (limited to 'guides/source/active_record_validations.md')
-rw-r--r-- | guides/source/active_record_validations.md | 329 |
1 files changed, 272 insertions, 57 deletions
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 2e2f0e4ea9..546c0608ee 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -85,7 +85,7 @@ end We can see how it works by looking at some `rails console` output: ```ruby -$ rails console +$ bin/rails console >> p = Person.new(name: "John Doe") => #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil> >> p.new_record? @@ -117,12 +117,11 @@ database only if the object is valid: * `save` * `save!` * `update` -* `update_attributes` -* `update_attributes!` +* `update!` The bang versions (e.g. `save!`) raise an exception if the record is invalid. -The non-bang versions don't: `save` and `update_attributes` return `false`, -`create` and `update` just return the objects. +The non-bang versions don't, `save` and `update` return `false`, +`create` just returns the object. ### Skipping Validations @@ -163,8 +162,8 @@ Person.create(name: nil).valid? # => false ``` After Active Record has performed validations, any errors found can be accessed -through the `errors` instance method, which returns a collection of errors. By -definition, an object is valid if this collection is empty after running +through the `errors.messages` instance method, which returns a collection of errors. +By definition, an object is valid if this collection is empty after running validations. Note that an object instantiated with `new` will not report errors even if it's @@ -176,28 +175,28 @@ class Person < ActiveRecord::Base end >> p = Person.new -#=> #<Person id: nil, name: nil> ->> p.errors -#=> {} +# => #<Person id: nil, name: nil> +>> p.errors.messages +# => {} >> p.valid? -#=> false ->> p.errors -#=> {name:["can't be blank"]} +# => false +>> p.errors.messages +# => {name:["can't be blank"]} >> p = Person.create -#=> #<Person id: nil, name: nil> ->> p.errors -#=> {name:["can't be blank"]} +# => #<Person id: nil, name: nil> +>> p.errors.messages +# => {name:["can't be blank"]} >> p.save -#=> false +# => false >> p.save! -#=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank +# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank >> Person.create! -#=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank +# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank ``` `invalid?` is simply the inverse of `valid?`. It triggers your validations, @@ -244,7 +243,7 @@ line of code you can add the same kind of validation to several attributes. All of them accept the `:on` and `:message` options, which define when the validation should be run and what message should be added to the `errors` collection if it fails, respectively. The `:on` option takes one of the values -`:save` (the default), `:create` or `:update`. There is a default error +`:create` or `:update`. There is a default error message for each one of the validation helpers. These messages are used when the `:message` option isn't specified. Let's take a look at each one of the available helpers. @@ -338,7 +337,7 @@ set. In fact, this set can be any enumerable object. ```ruby class Account < ActiveRecord::Base validates :subdomain, exclusion: { in: %w(www us ca jp), - message: "Subdomain %{value} is reserved." } + message: "%{value} is reserved." } end ``` @@ -358,10 +357,12 @@ given regular expression, which is specified using the `:with` option. ```ruby class Product < ActiveRecord::Base validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, - message: "Only letters allowed" } + message: "only allows letters" } end ``` +Alternatively, you can require that the specified attribute does _not_ match the regular expression by using the `:without` option. + The default error message is _"is invalid"_. ### `inclusion` @@ -426,7 +427,7 @@ class Essay < ActiveRecord::Base validates :content, length: { minimum: 300, maximum: 400, - tokenizer: lambda { |str| str.scan(/\w+/) }, + tokenizer: lambda { |str| str.split(/\s+/) }, too_short: "must have at least %{count} words", too_long: "must have at most %{count} words" } @@ -435,12 +436,10 @@ end Note that the default error messages are plural (e.g., "is too short (minimum is %{count} characters)"). For this reason, when `:minimum` is 1 you should -provide a personalized message or use `validates_presence_of` instead. When +provide a personalized message or use `presence: true` instead. When `:in` or `:within` have a lower limit of 1, you should either provide a personalized message or call `presence` prior to `length`. -The `size` helper is an alias for `length`. - ### `numericality` This helper validates that your attributes have only numeric values. By @@ -503,8 +502,8 @@ end ``` If you want to be sure that an association is present, you'll need to test -whether the foreign key used to map the association is present, and not the -associated object itself. +whether the associated object itself is present, and not the foreign key used +to map the association. ```ruby class LineItem < ActiveRecord::Base @@ -513,7 +512,8 @@ class LineItem < ActiveRecord::Base end ``` -You should also be sure to have a proper `:inverse_of` as well: +In order to validate associated records whose presence is required, you must +specify the `:inverse_of` option for the association: ```ruby class Order < ActiveRecord::Base @@ -526,9 +526,57 @@ If you validate the presence of an object associated via a `has_one` or `marked_for_destruction?`. Since `false.blank?` is true, if you want to validate the presence of a boolean -field you should use `validates :field_name, inclusion: { in: [true, false] }`. +field you should use one of the following validations: + +```ruby +validates :boolean_field_name, presence: true +validates :boolean_field_name, inclusion: { in: [true, false] } +validates :boolean_field_name, exclusion: { in: [nil] } +``` + +By using one of these validations, you will ensure the value will NOT be `nil` +which would result in a `NULL` value in most cases. + +### `absence` + +This helper validates that the specified attributes are absent. It uses the +`present?` method to check if the value is not either nil or a blank string, that +is, a string that is either empty or consists of whitespace. + +```ruby +class Person < ActiveRecord::Base + validates :name, :login, :email, absence: true +end +``` + +If you want to be sure that an association is absent, you'll need to test +whether the associated object itself is absent, and not the foreign key used +to map the association. + +```ruby +class LineItem < ActiveRecord::Base + belongs_to :order + validates :order, absence: true +end +``` -The default error message is _"can't be empty"_. +In order to validate associated records whose absence is required, you must +specify the `:inverse_of` option for the association: + +```ruby +class Order < ActiveRecord::Base + has_many :line_items, inverse_of: :order +end +``` + +If you validate the absence of an object associated via a `has_one` or +`has_many` relationship, it will check that the object is neither `present?` nor +`marked_for_destruction?`. + +Since `false.present?` is false, if you want to validate the absence of a boolean +field you should use `validates :field_name, exclusion: { in: [true, false] }`. + +The default error message is _"must be blank"_. ### `uniqueness` @@ -536,7 +584,9 @@ This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, -you must create a unique index in your database. +you must create a unique index on both columns in your database. See +[the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) +for more details about multiple column indexes. ```ruby class Account < ActiveRecord::Base @@ -577,10 +627,6 @@ The default error message is _"has already been taken"_. This helper passes the record to a separate class for validation. ```ruby -class Person < ActiveRecord::Base - validates_with GoodnessValidator -end - class GoodnessValidator < ActiveModel::Validator def validate(record) if record.first_name == "Evil" @@ -588,6 +634,10 @@ class GoodnessValidator < ActiveModel::Validator end end end + +class Person < ActiveRecord::Base + validates_with GoodnessValidator +end ``` NOTE: Errors added to `record.errors[:base]` relate to the state of the record @@ -605,10 +655,6 @@ Like all other validations, `validates_with` takes the `:if`, `:unless` and validator class as `options`: ```ruby -class Person < ActiveRecord::Base - validates_with GoodnessValidator, fields: [:first_name, :last_name] -end - class GoodnessValidator < ActiveModel::Validator def validate(record) if options[:fields].any?{|field| record.send(field) == "Evil" } @@ -616,6 +662,39 @@ class GoodnessValidator < ActiveModel::Validator end end end + +class Person < ActiveRecord::Base + validates_with GoodnessValidator, fields: [:first_name, :last_name] +end +``` + +Note that the validator will be initialized *only once* for the whole application +life cycle, and not on each validation run, so be careful about using instance +variables inside it. + +If your validator is complex enough that you want instance variables, you can +easily use a plain old Ruby object instead: + +```ruby +class Person < ActiveRecord::Base + validate do |person| + GoodnessValidator.new(person).validate + end +end + +class GoodnessValidator + def initialize(person) + @person = person + end + + def validate + if some_complex_condition_involving_ivars_and_private_methods? + @person.errors[:base] << "This person is evil" + end + end + + # ... +end ``` ### `validates_each` @@ -628,7 +707,7 @@ we don't want names and surnames to begin with lower case. ```ruby class Person < ActiveRecord::Base validates_each :name, :surname do |record, attr, value| - record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ + record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/ end end ``` @@ -655,8 +734,6 @@ class Coffee < ActiveRecord::Base end ``` -TIP: `:allow_nil` is ignored by the presence validator. - ### `:allow_blank` The `:allow_blank` option is similar to the `:allow_nil` option. This option @@ -668,12 +745,10 @@ class Topic < ActiveRecord::Base validates :title, length: { is: 5 }, allow_blank: true end -Topic.create("title" => "").valid? # => true -Topic.create("title" => nil).valid? # => true +Topic.create(title: "").valid? # => true +Topic.create(title: nil).valid? # => true ``` -TIP: `:allow_blank` is ignored by the presence validator. - ### `:message` As you've already seen, the `:message` option lets you specify the message that @@ -699,7 +774,7 @@ class Person < ActiveRecord::Base validates :age, numericality: true, on: :update # the default (validates on both create and update) - validates :name, presence: true, on: :save + validates :name, presence: true end ``` @@ -717,7 +792,7 @@ end Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank ``` -There is also an ability to pass custom exception to `:strict` option +There is also an ability to pass custom exception to `:strict` option. ```ruby class Person < ActiveRecord::Base @@ -805,7 +880,7 @@ should happen, an `Array` can be used. Moreover, you can apply both `:if` and ```ruby class Computer < ActiveRecord::Base validates :mouse, presence: true, - if: ["market.retail?", :desktop?] + if: ["market.retail?", :desktop?], unless: Proc.new { |c| c.trackpad.present? } end ``` @@ -844,8 +919,8 @@ end The easiest way to add custom validators for validating individual attributes is with the convenient `ActiveModel::EachValidator`. In this case, the custom validator class must implement a `validate_each` method which takes three -arguments: record, attribute and value which correspond to the instance, the -attribute to be validated and the value of the attribute in the passed +arguments: record, attribute, and value. These correspond to the instance, the +attribute to be validated, and the value of the attribute in the passed instance. ```ruby @@ -908,6 +983,146 @@ class Invoice < ActiveRecord::Base end ``` +Working with Validation Errors +------------------------------ + +In addition to the `valid?` and `invalid?` methods covered earlier, Rails provides a number of methods for working with the `errors` collection and inquiring about the validity of objects. + +The following is a list of the most commonly used methods. Please refer to the `ActiveModel::Errors` documentation for a list of all the available methods. + +### `errors` + +Returns an instance of the class `ActiveModel::Errors` containing all errors. Each key is the attribute name and the value is an array of strings with all errors. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true, length: { minimum: 3 } +end + +person = Person.new +person.valid? # => false +person.errors.messages + # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]} + +person = Person.new(name: "John Doe") +person.valid? # => true +person.errors.messages # => {} +``` + +### `errors[]` + +`errors[]` is used when you want to check the error messages for a specific attribute. It returns an array of strings with all error messages for the given attribute, each string with one error message. If there are no errors related to the attribute, it returns an empty array. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true, length: { minimum: 3 } +end + +person = Person.new(name: "John Doe") +person.valid? # => true +person.errors[:name] # => [] + +person = Person.new(name: "JD") +person.valid? # => false +person.errors[:name] # => ["is too short (minimum is 3 characters)"] + +person = Person.new +person.valid? # => false +person.errors[:name] + # => ["can't be blank", "is too short (minimum is 3 characters)"] +``` + +### `errors.add` + +The `add` method lets you manually add messages that are related to particular attributes. You can use the `errors.full_messages` or `errors.to_a` methods to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). `add` receives the name of the attribute you want to add the message to, and the message itself. + +```ruby +class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors.add(:name, "cannot contain the characters !@#%*()_-+=") + end +end + +person = Person.create(name: "!@#") + +person.errors[:name] + # => ["cannot contain the characters !@#%*()_-+="] + +person.errors.full_messages + # => ["Name cannot contain the characters !@#%*()_-+="] +``` + +Another way to do this is using `[]=` setter + +```ruby + class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors[:name] = "cannot contain the characters !@#%*()_-+=" + end + end + + person = Person.create(name: "!@#") + + person.errors[:name] + # => ["cannot contain the characters !@#%*()_-+="] + + person.errors.to_a + # => ["Name cannot contain the characters !@#%*()_-+="] +``` + +### `errors[:base]` + +You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message. + +```ruby +class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors[:base] << "This person is invalid because ..." + end +end +``` + +### `errors.clear` + +The `clear` method is used when you intentionally want to clear all the messages in the `errors` collection. Of course, calling `errors.clear` upon an invalid object won't actually make it valid: the `errors` collection will now be empty, but the next time you call `valid?` or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the `errors` collection will be filled again. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true, length: { minimum: 3 } +end + +person = Person.new +person.valid? # => false +person.errors[:name] + # => ["can't be blank", "is too short (minimum is 3 characters)"] + +person.errors.clear +person.errors.empty? # => true + +p.save # => false + +p.errors[:name] +# => ["can't be blank", "is too short (minimum is 3 characters)"] +``` + +### `errors.size` + +The `size` method returns the total number of error messages for the object. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true, length: { minimum: 3 } +end + +person = Person.new +person.valid? # => false +person.errors.size # => 2 + +person = Person.new(name: "Andrea", email: "andrea@example.com") +person.valid? # => true +person.errors.size # => 0 +``` + Displaying Validation Errors in Views ------------------------------------- @@ -923,15 +1138,15 @@ generating a scaffold, Rails will put some ERB into the `_form.html.erb` that it generates that displays the full list of errors on that model. Assuming we have a model that's been saved in an instance variable named -`@post`, it looks like this: +`@article`, it looks like this: ```ruby -<% if @post.errors.any? %> +<% if @article.errors.any? %> <div id="error_explanation"> - <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> + <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2> <ul> - <% @post.errors.full_messages.each do |msg| %> + <% @article.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> @@ -945,7 +1160,7 @@ the entry. ``` <div class="field_with_errors"> - <input id="post_title" name="post[title]" size="30" type="text" value=""> + <input id="article_title" name="article[title]" size="30" type="text" value=""> </div> ``` |