From 1eee0d41f9bb6f3b912227db45abc9b52e60bc49 Mon Sep 17 00:00:00 2001 From: Trevor Turk Date: Wed, 18 Feb 2009 23:16:10 -0600 Subject: Validation and Callbacks cleanup, finishing up Validations --- .../activerecord_validations_callbacks.textile | 155 +++++++++++++-------- 1 file changed, 95 insertions(+), 60 deletions(-) (limited to 'railties/guides/source/activerecord_validations_callbacks.textile') diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/activerecord_validations_callbacks.textile index 3b9b9701b8..01e52bf01e 100644 --- a/railties/guides/source/activerecord_validations_callbacks.textile +++ b/railties/guides/source/activerecord_validations_callbacks.textile @@ -4,18 +4,21 @@ This guide teaches you how to hook into the lifecycle of your Active Record obje After reading this guide and trying out the presented concepts, we hope that you'll be able to: +* Understand the lifecycle of Active Record objects * Use the built-in Active Record validation helpers * Create your own custom validation methods * Work with the error messages generated by the validation process * Create callback methods that respond to events in the object lifecycle * Create special classes that encapsulate common behavior for your callbacks -* Create Observers +* Create Observers that respond to lifecycle events outside of the original class endprologue. -# TODO consider starting with an overview of what validations and callbacks are, and the object lifecycle -# http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html -# http://api.rubyonrails.org/classes/ActiveRecord/Base.html +h3. The Object Lifecycle + +During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this object lifecycle so that you can control your application and its data. + +Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger logic before or after an alteration of an object's state. h3. Validations Overview @@ -23,7 +26,7 @@ Before you dive into the detail of validations in Rails, you should understand a h4. Why Use Validations? -Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides and email address and valid postal code. +Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address There are several ways to validate data before it is saved into your database, including native database constraints, client-side validations, controller-level validations, and model-level validations. @@ -44,8 +47,8 @@ end We can see how it works by looking at some script/console output: ->> p = Person.new(:name => "John Doe", :birthdate => Date.parse("09/03/1979")) -=> # +>> p = Person.new(:name => "John Doe") +=> # >> p.new_record? => true >> p.save @@ -87,7 +90,7 @@ Note that +save+ also has the ability to skip validations (and callbacks!) if pa h4. Object#valid? and Object#invalid? -To verify whether or not an object is valid, you can use the +valid?+ method. This runs validations and returns true if no errors were added to the object, and false otherwise. +To verify whether or not an object is valid, Rails uses the +valid?+ method. You can also use this method on your own. +valid?+ triggers your validations and returns true if no errors were added to the object, and false otherwise. class Person < ActiveRecord::Base @@ -95,12 +98,12 @@ class Person < ActiveRecord::Base end Person.create(:name => "John Doe").valid? # => true -Person.create.valid? # => false +Person.create(:name => nil).valid? # => false When Active Record is performing validations, any errors found are collected into an +errors+ instance variable and can be accessed through an +errors+ instance method. An object is considered invalid if it has errors, and calling +save+ or +save!+ will not save it to the database. -However, note that an object instantiated with +new+ will not report errors even if it's technically invalid, because validations are not run when using +new+. +Note that an object instantiated with +new+ will not report errors even if it's technically invalid, because validations are not run when using +new+. class Person < ActiveRecord::Base @@ -110,20 +113,25 @@ end >> p = Person.new => # >> p.errors -=> #, @errors={}> +=> # + >> p.valid? => false >> p.errors -=> #, @errors={"name"=>["can't be blank"]}> +=> #["can't be blank"]}> + >> p = Person.create => # >> p.errors -=> #, @errors={"name"=>["can't be blank"]}> +=> #["can't be blank"]}> + >> p.save => false + >> p.save! => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank ->> p = Person.create! + +>> Person.create! => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank @@ -138,6 +146,8 @@ end >> Person.create.errors.invalid?(:name) # => true +We'll cover validation errors in greater depth in the *Working with Validation Errors* section. For now, let's turn to the built-in validation helpers that Rails provides by default. + h3. Validation Helpers Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers create validation rules that are commonly used. Every time a validation fails, an error message is added to the object's +errors+ collection, and this message is associated with the field being validated. @@ -319,7 +329,7 @@ class Person < ActiveRecord::Base end -NOTE: 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. +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. class LineItem < ActiveRecord::Base @@ -328,7 +338,7 @@ class LineItem < ActiveRecord::Base end -NOTE: If you want to validate the presence of a boolean field (where the real values are true and false), you should use validates_inclusion_of :field_name, :in => [true, false] This is due to the way Object#blank? handles boolean values. false.blank? # => true +If you want to validate the presence of a boolean field (where the real values are true and false), you should use +validates_inclusion_of :field_name, :in => [true, false]+. This is due to the way that +Object#blank?+ handles boolean values (+false.blank? # => true+). The default error message for +validates_presence_of+ is "_can't be empty_". @@ -430,7 +440,7 @@ h3. Conditional Validation Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a Ruby Proc. You may use the +:if+ option when you want to specify when the validation *should* happen. If you want to specify when the validation *should not* happen, then you may use the +:unless+ option. -h4. Using a symbol with :if and :unless +h4. Using a Symbol with :if and :unless You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option. @@ -444,7 +454,7 @@ class Order < ActiveRecord::Base end -h4. Using a string with the :if and :unless +h4. Using a String with :if and :unless You can also use a string that will be evaluated using +:eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. @@ -454,7 +464,7 @@ class Person < ActiveRecord::Base end -h4. Using a Proc object with :if and :unless +h4. Using a Proc with :if and :unless Finally, it's possible to associate +:if+ and +:unless+ with a Ruby Proc object which will be called. Using a Proc object can give you the ability to write a condition that will be executed only when the validation happens and not when your code is loaded by the Ruby interpreter. This option is best suited when writing short validation methods, usually one-liners. @@ -465,9 +475,13 @@ class Account < ActiveRecord::Base end -h3. Custom Validation Methods +h3. Creating Custom Validation Methods + +When the built-in validation helpers are not enough for your needs, you can write your own validation methods. + +Simply create methods that verify the state of your models and add messages to the +errors+ collection when they are invalid. You must then register these methods by using one or more of the +validate+, +validate_on_create+ or +validate_on_update+ class methods, passing in the symbols for the validation methods' names. -When the built-in validation helpers are not enough for your needs, you can write your own validation methods. You can do that by implementing methods that verify the state of your models and add messages to their +errors+ collection when they are invalid. You must then register those methods by using one or more of the +validate+, +validate_on_create+ or +validate_on_update+ class methods, passing in the symbols for the validation methods' names. You can pass more than one symbol for each class method and the respective validations will be ran in the same order as they were registered. +You can pass more than one symbol for each class method and the respective validations will be ran in the same order as they were registered. class Invoice < ActiveRecord::Base @@ -503,7 +517,7 @@ module ActiveRecord end -The recipe is simple: just create a new validation method inside the +ActiveRecord::Validations::ClassMethods+ module. You can put this code in a file inside your application's *lib* folder, and then requiring it from your *environment.rb* or any other file inside *config/initializers*. You can use this helper like this: +Simply create a new validation method inside the +ActiveRecord::Validations::ClassMethods+ module. You can put this code in a file inside your application's *lib* folder, and then requiring it from your *environment.rb* or any other file inside *config/initializers*. You can use this helper like this: class Person < ActiveRecord::Base @@ -511,14 +525,15 @@ class Person < ActiveRecord::Base end -h3. Validation Errors +h3. Working with Validation Errors -# Consider combining with new section at the top with Object#valid? and Object#invalid? -# Consider moving this stuff above the current 2-6 sections (e.g. save the validation details until after this?) +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. -You can do more than just call +valid?+ upon your objects based on the existence of the +errors+ collection. Here is a list of the other available methods that you can use to manipulate errors or ask for an object's state. +The following is a list of the most commonly used methods. Please refer to the ActiveRecord::Errors documentation for an exhaustive list that covers all of the available methods. -* +add_to_base+ lets you add errors 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 it's attributes. +add_to_base+ receives a string with the message. +h4. errors.add_to_base + ++add_to_base+ lets you add errors 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 it's attributes. +add_to_base+ simply receives a string and uses this as the error message. class Person < ActiveRecord::Base @@ -528,28 +543,29 @@ class Person < ActiveRecord::Base end -* +add+ lets you manually add messages that are related to particular attributes. When writing those messages, keep in mind that Rails will prepend them with the name of the attribute that holds the error, so write it in a way that makes sense. +add+ receives a symbol with the name of the attribute that you want to add the message to and the message itself. +h4. errors.add + ++add+ lets you manually add messages that are related to particular attributes. Note that Rails will prepend the name of the attribute to the error message you pass it. You can use the +full_messages+ method to view the messages in the form they might be displayed to a user. +add+ receives a symbol with the name of the attribute that you want to add the message to, and the message itself. class Person < ActiveRecord::Base def a_method_used_for_validation_purposes - errors.add(:name, "can't have the characters !@#$%*()_-+=") + errors.add(:name, "cannot contain the characters !@#$%*()_-+=") end end - -* +invalid?+ is used when you want to check if a particular attribute is invalid. It receives a symbol with the name of the attribute that you want to check. +person = Person.create(:name => "!@#$") - -class Person < ActiveRecord::Base - validates_presence_of :name, :email -end +person.errors.on(:name) +# => "is too short (minimum is 3 characters)" -person = Person.new(:name => "John Doe") -person.errors.invalid?(:email) # => true +person.errors.full_messages +# => ["Name is too short (minimum is 3 characters)"] -* +on+ is used when you want to check the error messages for a specific attribute. It will return different kinds of objects depending on the state of the +errors+ collection for the given attribute. If there are no errors related to the attribute, +on+ will return +nil+. If there is just one errors message for this attribute, +on+ will return a string with the message. When +errors+ holds two or more error messages for the attribute, +on+ will return an array of strings, each one with one error message. +h4. errors.on + ++on+ is used when you want to check the error messages for a specific attribute. It will return different kinds of objects depending on the state of the +errors+ collection for the given attribute. If there are no errors related to the attribute, +on+ will return +nil+. If there is just one errors message for this attribute, +on+ will return a string with the message. When +errors+ holds two or more error messages for the attribute, +on+ will return an array of strings, each one with one error message. class Person < ActiveRecord::Base @@ -572,7 +588,9 @@ person.errors.on(:name) # => ["can't be blank", "is too short (minimum is 3 characters)"] -* +clear+ is used when you intentionally want to clear all the messages in the +errors+ collection. However, calling +errors.clear+ upon an invalid object won't 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. If any of them fails, the +errors+ collection will get filled again. +h4. errors.clear + ++clear+ 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. class Person < ActiveRecord::Base @@ -587,17 +605,35 @@ person.errors.on(:name) person.errors.clear person.errors.empty? # => true + p.save # => false + p.errors.on(:name) # => ["can't be blank", "is too short (minimum is 3 characters)"] -# TODO consider discussing other methods (e.g. errors.size) -# http://api.rubyonrails.org/classes/ActiveRecord/Errors.html +h4. errors.size -h3. Displaying Validation Errors the View ++size+ returns the total number of errors added. Two errors added to the same object will be counted as such. -Rails provides built-in helpers to display the error messages of your models in your view templates. When creating a form with the form_for helper, you can use the error_messages method on the form builder to render all failed validation messages for the current model instance. + +class Person < ActiveRecord::Base + validates_presence_of :name + validates_length_of :name, :minimum => 3 +end + +person = Person.new +person.valid? # => false +person.errors.size # => 2 + + +h3. Displaying Validation Errors in the View + +Rails provides built-in helpers to display the error messages of your models in your view templates. + +h4. error_messages and error_messages_for + +When creating a form with the form_for helper, you can use the error_messages method on the form builder to render all failed validation messages for the current model instance. class Product < ActiveRecord::Base @@ -647,6 +683,8 @@ Which results in the following content If you pass +nil+ to any of these options, it will get rid of the respective section of the +div+. +h4. Customizing the Error Messages CSS + It's also possible to change the CSS classes used by the +error_messages+ helper. These classes are automatically defined at the *scaffold.css* file, generated by the scaffold script. If you're not using scaffolding, you can still define those CSS classes at your CSS files. Here is a list of the default CSS classes. * +.fieldWithErrors+ - Style for the form fields with errors. @@ -655,9 +693,11 @@ It's also possible to change the CSS classes used by the +error_messages+ helper * +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element. * +#errorExplanation ul li+ - Style for the list of error messages. -h4. Changing the Way Form Fields with Errors are Displayed +h4. Customizing the Error Messages HTML + +By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, it's possible to override the way Rails treats those fields by default. -By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, we can write some Ruby code to override the way Rails treats those fields by default. Here is a simple example where we change the Rails behaviour to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a +span+ element with a +validation-error+ CSS class. There will be no +div+ element enclosing the +input+ element, so we get rid of that red border around the text field. You can use the +validation-error+ CSS class to style it anyway you want. +Here is a simple example where we change the Rails behaviour to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a +span+ element with a +validation-error+ CSS class. There will be no +div+ element enclosing the +input+ element, so we get rid of that red border around the text field. You can use the +validation-error+ CSS class to style it anyway you want. ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| @@ -768,46 +808,41 @@ end h3. Available Callbacks -# TODO consider moving above the code examples and details, possibly combining with intro lifecycle stuff? - Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations. -h4. Callbacks called both when creating or updating a record. - -# TODO consider just listing these in the possible lifecycle of an object, roughly as they do here: -# http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html +h4. Creating and/or Updating an Object * +before_validation+ * +after_validation+ * +before_save+ -* *INSERT OR UPDATE OPERATION* +* INSERT OR UPDATE OPERATION * +after_save+ -h4. Callbacks called only when creating a new record. +h4. Creating an Object * +before_validation_on_create+ * +after_validation_on_create+ * +before_create+ -* *INSERT OPERATION* +* INSERT OPERATION * +after_create+ -h4. Callbacks called only when updating an existing record. +h4. Updating an Object * +before_validation_on_update+ * +after_validation_on_update+ * +before_update+ -* *UPDATE OPERATION* +* UPDATE OPERATION * +after_update+ -h4. Callbacks called when removing a record from the database. +h4. Destroying an Object * +before_destroy+ -* *DELETE OPERATION* +* DELETE OPERATION * +after_destroy+ -The +before_destroy+ and +after_destroy+ callbacks will only be called if you delete the model using either the +destroy+ instance method or one of the +destroy+ or +destroy_all+ class methods of your Active Record class. If you use +delete+ or +delete_all+ no callback operations will run, since Active Record will not instantiate any objects, accessing the records to be deleted directly in the database. +CAUTION: The +before_destroy+ and +after_destroy+ callbacks will only be called if you delete the model using either the +destroy+ instance method or one of the +destroy+ or +destroy_all+ class methods of your Active Record class. If you use +delete+ or +delete_all+ no callback operations will run, since Active Record will not instantiate any objects, accessing the records to be deleted directly in the database. -h4. after_initialize and after_find callbacks +h4. after_initialize and after_find The +after_initialize+ callback will be called whenever an Active Record object is instantiated, either by directly using +new+ or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record +initialize+ method. -- cgit v1.2.3