diff options
Diffstat (limited to 'activemodel')
-rw-r--r-- | activemodel/README | 211 | ||||
-rw-r--r-- | activemodel/lib/active_model/attribute_methods.rb | 140 | ||||
-rw-r--r-- | activemodel/lib/active_model/errors.rb | 107 | ||||
-rw-r--r-- | activemodel/lib/active_model/lint.rb | 16 | ||||
-rw-r--r-- | activemodel/lib/active_model/naming.rb | 15 | ||||
-rw-r--r-- | activemodel/lib/active_model/observing.rb | 23 | ||||
-rw-r--r-- | activemodel/lib/active_model/serialization.rb | 57 | ||||
-rw-r--r-- | activemodel/lib/active_model/translation.rb | 19 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations.rb | 39 | ||||
-rw-r--r-- | activemodel/lib/active_model/validator.rb | 5 |
10 files changed, 562 insertions, 70 deletions
diff --git a/activemodel/README b/activemodel/README index 7c9c754a8e..0d6fd1f21c 100644 --- a/activemodel/README +++ b/activemodel/README @@ -1,45 +1,68 @@ = Active Model - defined interfaces for Rails - + Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have an object interact with Action Pack helpers, it was required to either copy chunks of code from Rails, or monkey patch entire helpers to make them handle objects that did not look like Active Record. This generated code duplication and fragile applications that broke on upgrades. - + Active Model is a solution for this problem. - + Active Model provides a known set of interfaces that your objects can implement to then present a common interface to the Action Pack helpers. You can include functionality from the following modules: - -* Adding callbacks to your class - - class MyClass - extend ActiveModel::Callbacks - define_model_callbacks :create - - def create - _run_create_callbacks do - # Your create action methods here - end - end - end - - ...gives you before_create, around_create and after_create class methods that - wrap your create method. + +* Adding attribute magic to your objects + + Add prefixes and suffixes to defined attribute methods... + + class Person + include ActiveModel::AttributeMethods + + attribute_method_prefix 'clear_' + define_attribute_methods [:name, :age] + + attr_accessor :name, :age + + def clear_attribute(attr) + send("#{attr}=", nil) + end + end + + ...gives you clear_name, clear_age. + + {Learn more}[link:classes/ActiveModel/AttributeMethods.html] + +* Adding callbacks to your objects + + class Person + extend ActiveModel::Callbacks + define_model_callbacks :create + + def create + _run_create_callbacks do + # Your create action methods here + end + end + end + + ...gives you before_create, around_create and after_create class methods that + wrap your create method. {Learn more}[link:classes/ActiveModel/CallBacks.html] - + * For classes that already look like an Active Record object - - class MyClass + + class Person include ActiveModel::Conversion end ...returns the class itself when sent :to_model - + + {Learn more}[link:classes/ActiveModel/Conversion.html] + * Tracking changes in your object - + Provides all the value tracking features implemented by ActiveRecord... person = Person.new @@ -52,6 +75,142 @@ functionality from the following modules: person.name = 'robert' person.save person.previous_changes # => {'name' => ['bob, 'robert']} - + {Learn more}[link:classes/ActiveModel/Dirty.html] - + +* Adding +errors+ support to your object + + Provides the error messages to allow your object to interact with Action Pack + helpers seamlessly... + + class Person + + def initialize + @errors = ActiveModel::Errors.new(self) + end + + attr_accessor :name + attr_reader :errors + + def validate! + errors.add(:name, "can not be nil") if name == nil + end + + def ErrorsPerson.human_attribute_name(attr, options = {}) + "Name" + end + + end + + ... gives you... + + person.errors.full_messages + # => ["Name Can not be nil"] + person.errors.full_messages + # => ["Name Can not be nil"] + + {Learn more}[link:classes/ActiveModel/Errors.html] + +* Testing the compliance of your object + + Use ActiveModel::Lint to test the compliance of your object to the + basic ActiveModel API... + + {Learn more}[link:classes/ActiveModel/Lint/Tests.html] + +* Providing a human face to your object + + ActiveModel::Naming provides your model with the model_name convention + and a human_name attribute... + + class NamedPerson + extend ActiveModel::Naming + end + + ...gives you... + + NamedPerson.model_name #=> "NamedPerson" + NamedPerson.model_name.human #=> "Named person" + + {Learn more}[link:classes/ActiveModel/Naming.html] + +* Adding observer support to your objects + + ActiveModel::Observers allows your object to implement the Observer + pattern in a Rails App and take advantage of all the standard observer + functions. + + {Learn more}[link:classes/ActiveModel/Observer.html] + +* Making your object serializable + + ActiveModel::Serialization provides a standard interface for your object + to provide to_json or to_xml serialization... + + s = SerialPerson.new + s.serializable_hash # => {"name"=>nil} + s.to_json # => "{\"name\":null}" + s.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... + + {Learn more}[link:classes/ActiveModel/Serialization.html] + + +* Turning your object into a finite State Machine + + ActiveModel::StateMachine provides a clean way to include all the methods + you need to transform your object into a finite State Machine... + + light = TrafficLight.new + light.current_state #=> :red + light.change_color! #=> true + light.current_state #=> :green + + {Learn more}[link:classes/ActiveModel/StateMachine.html] + +* Integrating with Rail's internationalization (i18n) handling through + ActiveModel::Translations... + + class Person + extend ActiveModel::Translation + end + + {Learn more}[link:classes/ActiveModel/Translation.html] + +* Providing a full Validation stack for your objects... + + class Person + include ActiveModel::Validations + + attr_accessor :first_name, :last_name + + validates_each :first_name, :last_name do |record, attr, value| + record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + end + end + + person = Person.new(:first_name => 'zoolander') + person.valid? #=> false + + {Learn more}[link:classes/ActiveModel/Validations.html] + +* Make custom validators + + class Person + include ActiveModel::Validations + validates_with HasNameValidator + attr_accessor :name + end + + class HasNameValidator < ActiveModel::Validator + def validate(record) + record.errors[:name] = "must exist" if record.name.blank? + end + end + + p = ValidatorPerson.new + p.valid? #=> false + p.errors.full_messages #=> ["Name must exist"] + p.name = "Bob" + p.valid? #=> true + + {Learn more}[link:classes/ActiveModel/Validator.html] diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 977a101277..200a6afbf0 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -5,6 +5,48 @@ module ActiveModel class MissingAttributeError < NoMethodError end + # <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and suffixes + # to your methods as well as handling the creation of Active Record like class methods + # such as +table_name+. + # + # The requirements to implement ActiveModel::AttributeMethods are: + # + # * <tt>include ActiveModel::AttributeMethods</tt> in your object + # * Call each Attribute Method module method you want to add, such as + # attribute_method_suffix or attribute_method_prefix + # * Call <tt>define_attribute_methods</tt> after the other methods are + # called. + # * Define the various generic +_attribute+ methods that you have declared + # + # A minimal implementation could be: + # + # class Person + # + # include ActiveModel::AttributeMethods + # + # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!' + # attribute_method_suffix '_contrived?' + # attribute_method_prefix 'clear_' + # define_attribute_methods ['name'] + # + # attr_accessor :name + # + # private + # + # def attribute_contrived?(attr) + # true + # end + # + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end + # + # def reset_attribute_to_default!(attr) + # send("#{attr}=", "Default Name") + # end + # + # end + # module AttributeMethods extend ActiveSupport::Concern @@ -22,12 +64,27 @@ module ActiveModel # # Example: # - # class A < ActiveRecord::Base + # class Person + # + # include ActiveModel::AttributeMethods + # + # cattr_accessor :primary_key + # cattr_accessor :inheritance_column + # # define_attr_method :primary_key, "sysid" # define_attr_method( :inheritance_column ) do # original_inheritance_column + "_id" # end + # # end + # + # Provivdes you with: + # + # AttributePerson.primary_key + # # => "sysid" + # AttributePerson.inheritance_column = 'address' + # AttributePerson.inheritance_column + # # => 'address_id' def define_attr_method(name, value=nil, &block) sing = metaclass sing.send :alias_method, "original_#{name}", name @@ -54,19 +111,25 @@ module ActiveModel # # For example: # - # class Person < ActiveRecord::Base + # class Person + # + # include ActiveModel::AttributeMethods + # attr_accessor :name # attribute_method_prefix 'clear_' + # define_attribute_methods [:name] # # private - # def clear_attribute(attr) - # ... - # end + # + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end # end # - # person = Person.find(1) - # person.name # => 'Gem' + # person = Person.new + # person.name = "Bob" + # person.name # => "Bob" # person.clear_name - # person.name # => '' + # person.name # => nil def attribute_method_prefix(*prefixes) attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }) undefine_attribute_methods @@ -86,18 +149,24 @@ module ActiveModel # # For example: # - # class Person < ActiveRecord::Base + # class Person + # + # include ActiveModel::AttributeMethods + # attr_accessor :name # attribute_method_suffix '_short?' + # define_attribute_methods [:name] # # private - # def attribute_short?(attr) - # ... - # end + # + # def attribute_short?(attr) + # send(attr).length < 5 + # end # end # - # person = Person.find(1) - # person.name # => 'Gem' - # person.name_short? # => true + # person = Person.new + # person.name = "Bob" + # person.name # => "Bob" + # person.name_short? # => true def attribute_method_suffix(*suffixes) attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }) undefine_attribute_methods @@ -118,16 +187,21 @@ module ActiveModel # # For example: # - # class Person < ActiveRecord::Base + # class Person + # + # include ActiveModel::AttributeMethods + # attr_accessor :name # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!' + # define_attribute_methods [:name] # # private - # def reset_attribute_to_default!(attr) - # ... - # end + # + # def reset_attribute_to_default!(attr) + # ... + # end # end # - # person = Person.find(1) + # person = Person.new # person.name # => 'Gem' # person.reset_name_to_default! # person.name # => 'Gemma' @@ -146,6 +220,30 @@ module ActiveModel end end + # Declares a the attributes that should be prefixed and suffixed by + # ActiveModel::AttributeMethods. + # + # To use, pass in an array of attribute names (as strings or symbols), + # be sure to declare +define_attribute_methods+ after you define any + # prefix, suffix or affix methods, or they will not hook in. + # + # class Person + # + # include ActiveModel::AttributeMethods + # attr_accessor :name, :age, :address + # attribute_method_prefix 'clear_' + # + # # Call to define_attribute_methods must appear after the + # # attribute_method_prefix, attribute_method_suffix or + # # attribute_method_affix declares. + # define_attribute_methods [:name, :age, :address] + # + # private + # + # def clear_attribute(attr) + # ... + # end + # end def define_attribute_methods(attr_names) return if attribute_methods_generated? attr_names.each do |attr_name| @@ -168,6 +266,7 @@ module ActiveModel @attribute_methods_generated = true end + # Removes all the preiously dynamically defined methods from the class def undefine_attribute_methods generated_attribute_methods.module_eval do instance_methods.each { |m| undef_method(m) } @@ -175,6 +274,7 @@ module ActiveModel @attribute_methods_generated = nil end + # Returns true if the attribute methods defined have been generated. def generated_attribute_methods #:nodoc: @generated_attribute_methods ||= begin mod = Module.new diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index ff11ddc605..d8320275df 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -2,9 +2,66 @@ require 'active_support/core_ext/string/inflections' require 'active_support/ordered_hash' module ActiveModel + # Provides a modified +OrderedHash+ that you can include in your object + # for handling error messages and interacting with Action Pack helpers. + # + # A minimal implementation could be: + # + # class Person + # + # # Required dependency for ActiveModel::Errors + # extend ActiveModel::Naming + # + # def initialize + # @errors = ActiveModel::Errors.new(self) + # end + # + # attr_accessor :name + # attr_reader :errors + # + # def validate! + # errors.add(:name, "can not be nil") if name == nil + # end + # + # # The following methods are needed to be minimally implemented + # + # def read_attribute_for_validation(attr) + # send(attr) + # end + # + # def ErrorsPerson.human_attribute_name(attr, options = {}) + # attr + # end + # + # def ErrorsPerson.lookup_ancestors + # [self] + # end + # + # end + # + # The last three methods are required in your object for Errors to be + # able to generate error messages correctly and also handle multiple + # languages. Of course, if you extend your object with ActiveModel::Translations + # you will not need to implement the last two. Likewise, using + # ActiveModel::Validations will handle the validation related methods + # for you. + # + # The above allows you to do: + # + # p = Person.new + # p.validate! # => ["can not be nil"] + # p.errors.full_messages # => ["name can not be nil"] + # # etc.. class Errors < ActiveSupport::OrderedHash include DeprecatedErrorMethods + # Pass in the instance of the object that is using the errors object. + # + # class Person + # def initialize + # @errors = ActiveModel::Errors.new(self) + # end + # end def initialize(base) @base = base super() @@ -13,6 +70,10 @@ module ActiveModel alias_method :get, :[] alias_method :set, :[]= + # When passed a symbol or a name of a method, returns an array of errors for the method. + # + # p.errors[:name] #=> ["can not be nil"] + # p.errors['name'] #=> ["can not be nil"] def [](attribute) if errors = get(attribute.to_sym) errors @@ -21,28 +82,73 @@ module ActiveModel end end + # Adds to the supplied attribute the supplied error message. + # + # p.errors[:name] = "must be set" + # p.errors[:name] #=> ['must be set'] def []=(attribute, error) self[attribute.to_sym] << error end + # Iterates through each error key, value pair in the error messages hash. + # Yields the attribute and the error for that attribute. If the attribute + # has more than one error message, yields once for each error message. + # + # p.errors.add(:name, "can't be blank") + # p.errors.each do |attribute, errors_array| + # # Will yield :name and "can't be blank" + # end + # + # p.errors.add(:name, "must be specified") + # p.errors.each do |attribute, errors_array| + # # Will yield :name and "can't be blank" + # # then yield :name and "must be specified" + # end def each each_key do |attribute| self[attribute].each { |error| yield attribute, error } end end + # Returns the number of error messages. + # + # p.errors.add(:name, "can't be blank") + # p.errors.size #=> 1 + # p.errors.add(:name, "must be specified") + # p.errors.size #=> 2 def size values.flatten.size end + # Returns an array of error messages, with the attribute name included + # + # p.errors.add(:name, "can't be blank") + # p.errors.add(:name, "must be specified") + # p.errors.to_a #=> ["name can't be blank", "name must be specified"] def to_a full_messages end + # Returns the number of error messages. + # p.errors.add(:name, "can't be blank") + # p.errors.count #=> 1 + # p.errors.add(:name, "must be specified") + # p.errors.count #=> 2 def count to_a.size end + # Returns an xml formatted representation of the Errors hash. + # + # p.errors.add(:name, "can't be blank") + # p.errors.add(:name, "must be specified") + # p.errors.to_xml #=> Produces: + # + # # <?xml version=\"1.0\" encoding=\"UTF-8\"?> + # # <errors> + # # <error>name can't be blank</error> + # # <error>name must be specified</error> + # # </errors> def to_xml(options={}) require 'builder' unless defined? ::Builder options[:root] ||= "errors" @@ -59,6 +165,7 @@ module ActiveModel # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>. # If no +messsage+ is supplied, :invalid is assumed. + # # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error). # If +message+ is a Proc, it will be called, allowing for things like Time.now to be used within an error def add(attribute, message = nil, options = {}) diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 1330bf7042..7bf0ad712d 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -13,8 +13,7 @@ module ActiveModel module Lint module Tests - # valid? - # ------ + # == Responds to <tt>valid?</tt> # # Returns a boolean that specifies whether the object is in a valid or invalid # state. @@ -23,8 +22,7 @@ module ActiveModel assert_boolean model.valid?, "valid?" end - # new_record? - # ----------- + # == Responds to <tt>new_record?</tt> # # Returns a boolean that specifies whether the object has been persisted yet. # This is used when calculating the URL for an object. If the object is @@ -41,8 +39,7 @@ module ActiveModel assert_boolean model.destroyed?, "destroyed?" end - # naming - # ------ + # == Naming # # Model.model_name must returns a string with some convenience methods as # :human and :partial_path. Check ActiveModel::Naming for more information. @@ -57,12 +54,11 @@ module ActiveModel assert_kind_of String, model_name.plural end - # errors - # ------ - # + # == Errors Testing + # # Returns an object that has :[] and :full_messages defined on it. See below # for more details. - + # # Returns an Array of Strings that are the errors for the attribute in # question. If localization is used, the Strings should be localized # for the current locale. If no error is present, this method should diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 4cd68a0c89..89e8f8b1ea 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -35,6 +35,21 @@ module ActiveModel end end + # ActiveModel::Naming is a module that creates a +model_name+ method on your + # object. + # + # To implement, just extend ActiveModel::Naming in your object: + # + # class BookCover + # exten ActiveModel::Naming + # end + # + # BookCover.model_name #=> "BookCover" + # BookCover.model_name.human #=> "Book cover" + # + # Providing the functionality that ActiveModel::Naming provides in your object + # is required to pass the ActiveModel Lint test. So either extending the provided + # method below, or rolling your own is required.. module Naming # Returns an ActiveModel::Name object for module. It can be # used to retrieve all kinds of naming-related information. diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index d9d1ab8967..ed6fb47c7e 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -24,8 +24,9 @@ module ActiveModel # # Same as above, just using explicit class references # ActiveRecord::Base.observers = Cacher, GarbageCollector # - # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is - # called during startup, and before each development request. + # Note: Setting this does not instantiate the observers yet. + # +instantiate_observers+ is called during startup, and before + # each development request. def observers=(*values) @observers = values.flatten end @@ -102,10 +103,12 @@ module ActiveModel # # == Observing a class that can't be inferred # - # Observers will by default be mapped to the class with which they share a name. So CommentObserver will - # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer - # differently than the class you're interested in observing, you can use the Observer.observe class method which takes - # either the concrete class (Product) or a symbol for that class (:product): + # Observers will by default be mapped to the class with which they share a + # name. So CommentObserver will be tied to observing Comment, ProductManagerObserver + # to ProductManager, and so on. If you want to name your observer differently than + # the class you're interested in observing, you can use the Observer.observe class + # method which takes either the concrete class (Product) or a symbol for that + # class (:product): # # class AuditObserver < ActiveModel::Observer # observe :account @@ -115,7 +118,8 @@ module ActiveModel # end # end # - # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments: + # If the audit observer needs to watch more than one kind of object, this can be + # specified with multiple arguments: # # class AuditObserver < ActiveModel::Observer # observe :account, :balance @@ -125,7 +129,8 @@ module ActiveModel # end # end # - # The AuditObserver will now act on both updates to Account and Balance by treating them both as records. + # The AuditObserver will now act on both updates to Account and Balance by treating + # them both as records. # class Observer include Singleton @@ -144,7 +149,7 @@ module ActiveModel # # class AuditObserver < ActiveModel::Observer # def self.observed_classes - # [AccountObserver, BalanceObserver] + # [Account, Balance] # end # end def observed_classes diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 4c0073f687..1c48d4613a 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -2,6 +2,63 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/slice' module ActiveModel + # Provides a basic serialization to a serializable_hash for your object. + # + # A minimal implementation could be: + # + # class Person + # + # include ActiveModel::Serialization + # + # attr_accessor :name + # + # def attributes + # @attributes ||= {'name' => 'nil'} + # end + # + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # + # You need to declare some sort of attributes hash which contains the attributes + # you want to serialize and their current value. + # + # Most of the time though, you will want to include the JSON or XML + # serializations. Both of these modules automatically include the + # ActiveModel::Serialization module, so there is no need to explicitly + # include it. + # + # So a minimal implementation including XML and JSON would be: + # + # class Person + # + # include ActiveModel::Serializers::JSON + # include ActiveModel::Serializers::Xml + # + # attr_accessor :name + # + # def attributes + # @attributes ||= {'name' => 'nil'} + # end + # + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.to_json # => "{\"name\":null}" + # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... + # + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # person.to_json # => "{\"name\":\"Bob\"}" + # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... module Serialization def serializable_hash(options = nil) options ||= {} diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 2d2df269d0..2ab342ffac 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -1,6 +1,23 @@ require 'active_support/core_ext/hash/reverse_merge' module ActiveModel + + # ActiveModel::Translation provides integration between your object and + # the Rails internationalization (i18n) framework. + # + # A minimal implementation could be: + # + # class TranslatedPerson + # extend ActiveModel::Translation + # end + # + # TranslatedPerson.human_attribute_name('my_attribue') + # #=> "My attribute" + # + # This also provides the required class methods for hooking into the + # Rails internationalization API, including being able to define a + # class based i18n_scope and lookup_ancestors to find translations in + # parent classes. module Translation include ActiveModel::Naming @@ -18,8 +35,6 @@ module ActiveModel # Transforms attributes names into a more human format, such as "First name" instead of "first_name". # - # Example: - # # Person.human_attribute_name("first_name") # => "First name" # # Specify +options+ with additional translating options. diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 276472ea46..03733a9c89 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -3,6 +3,41 @@ require 'active_support/core_ext/hash/keys' require 'active_model/errors' module ActiveModel + + # Provides a full validation framework to your objects. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :first_name, :last_name + # + # validates_each :first_name, :last_name do |record, attr, value| + # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + # end + # end + # + # Which provides you with the full standard validation stack that you + # know from ActiveRecord. + # + # person = Person.new + # person.valid? + # #=> true + # person.invalid? + # #=> false + # person.first_name = 'zoolander' + # person.valid? + # #=> false + # person.invalid? + # #=> true + # person.errors + # #=> #<OrderedHash {:first_name=>["starts with z."]}> + # + # Note that ActiveModel::Validations automatically adds an +errors+ method + # to your instances initialized with a new ActiveModel::Errors object, so + # there is no need for you to add this manually. + # module Validations extend ActiveSupport::Concern include ActiveSupport::Callbacks @@ -18,8 +53,10 @@ module ActiveModel # class Person # include ActiveModel::Validations # + # attr_accessor :first_name, :last_name + # # validates_each :first_name, :last_name do |record, attr, value| - # record.errors.add attr, 'starts with z.' if value[0] == ?z + # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z # end # end # diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 382a4cc98d..ad9729de00 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,5 +1,6 @@ module ActiveModel #:nodoc: - # A simple base class that can be used along with ActiveModel::Validations::ClassMethods.validates_with + # A simple base class that can be used along with + # +ActiveModel::Validations::ClassMethods.validates_with+ # # class Person # include ActiveModel::Validations @@ -28,7 +29,7 @@ module ActiveModel #:nodoc: # end # # class MyValidator < ActiveModel::Validator - # def validate + # def validate(record) # record # => The person instance being validated # options # => Any non-standard options passed to validates_with # end |