aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/README189
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb141
-rw-r--r--activemodel/lib/active_model/errors.rb109
-rw-r--r--activemodel/lib/active_model/lint.rb14
-rw-r--r--activemodel/lib/active_model/naming.rb17
-rw-r--r--activemodel/lib/active_model/observing.rb23
-rw-r--r--activemodel/lib/active_model/serialization.rb58
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb4
-rw-r--r--activemodel/lib/active_model/state_machine.rb149
-rw-r--r--activemodel/lib/active_model/translation.rb19
-rw-r--r--activemodel/lib/active_model/validations.rb39
-rw-r--r--activemodel/lib/active_model/validator.rb5
12 files changed, 707 insertions, 60 deletions
diff --git a/activemodel/README b/activemodel/README
index 7c9c754a8e..cf103b8d6d 100644
--- a/activemodel/README
+++ b/activemodel/README
@@ -12,32 +12,55 @@ 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
+* Adding attribute magic to your objects
- class MyClass
- extend ActiveModel::Callbacks
- define_model_callbacks :create
+ 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
- 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.
+ 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...
@@ -55,3 +78,141 @@ functionality from the following modules:
{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]
+
+ \ No newline at end of file
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 977a101277..32ddf1d579 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -5,10 +5,51 @@ 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
- # Declare and check for suffixed attribute methods.
module ClassMethods
# Defines an "attribute" method (like +inheritance_column+ or
# +table_name+). A new (class) method will be created with the
@@ -22,12 +63,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 +110,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 +148,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 +186,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 +219,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 +265,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) }
@@ -183,6 +281,7 @@ module ActiveModel
end
end
+ # Returns true if the attribute methods defined have been generated.
def attribute_methods_generated?
@attribute_methods_generated ||= nil
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 2e5bcab070..76e6ad93a7 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -2,9 +2,68 @@ 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 +72,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 +84,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 +167,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..e8a39130a6 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,9 +54,8 @@ 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.
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 4cd68a0c89..b9fb5fe0c8 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,6 +1,7 @@
require 'active_support/inflector'
module ActiveModel
+
class Name < String
attr_reader :singular, :plural, :element, :collection, :partial_path
alias_method :cache_key, :collection
@@ -35,6 +36,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.
@@ -42,4 +58,5 @@ module ActiveModel
@_model_name ||= ActiveModel::Name.new(self)
end
end
+
end
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..28f95f0cdc 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -2,6 +2,64 @@ 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/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 86149f1e5f..a185204680 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -85,8 +85,8 @@ module ActiveModel
@options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
end
- # To replicate the behavior in ActiveRecord#attributes,
- # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
+ # To replicate the behavior in ActiveRecord#attributes, <tt>:except</tt>
+ # takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
# for a N level model but is set for the N+1 level models,
# then because <tt>:except</tt> is set to a default value, the second
# level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
diff --git a/activemodel/lib/active_model/state_machine.rb b/activemodel/lib/active_model/state_machine.rb
index 527794b34d..64b91c1659 100644
--- a/activemodel/lib/active_model/state_machine.rb
+++ b/activemodel/lib/active_model/state_machine.rb
@@ -1,4 +1,153 @@
module ActiveModel
+
+ # ActiveModel::StateMachine provides methods that turn your object into a
+ # finite state machine, able to move from one state to another.
+ #
+ # A minimal implementation could be:
+ #
+ # class EmailMessage
+ # include ActiveModel::StateMachine
+ #
+ # state_machine do
+ # state :unread
+ # state :read
+ # end
+ #
+ # event :open_email do
+ # transitions :to => :read, :from => :unread
+ # end
+ # end
+ #
+ # === Examples
+ #
+ # class TrafficLight
+ # include ActiveModel::StateMachine
+ #
+ # attr_reader :runners_caught
+ #
+ # def initialize
+ # @runners_caught = 0
+ # end
+ #
+ # state_machine do
+ # state :red
+ # state :green
+ # state :yellow
+ # state :blink
+ #
+ # event :change_color do
+ # transitions :to => :red, :from => [:yellow],
+ # :on_transition => :catch_runners
+ # transitions :to => :green, :from => [:red]
+ # transitions :to => :yellow, :from => [:green]
+ # end
+ #
+ # event :defect do
+ # transitions :to => :blink, :from => [:yellow, :red, :green]
+ # end
+ #
+ # event :repair do
+ # transitions :to => :red, :from => [:blink]
+ # end
+ # end
+ #
+ # def catch_runners
+ # @runners_caught += 1
+ # end
+ # end
+ #
+ # light = TrafficLight.new
+ # light.current_state # => :red
+ # light.change_color! # => true
+ # light.current_state # => :green
+ # light.green? # => true
+ # light.change_color! # => true
+ # light.current_state # => :yellow
+ # light.red? # => false
+ # light.change_color! # => true
+ # light.runners_caught # => 1
+ #
+ # * The initial state for TrafficLight is red which is the first state defined.
+ #
+ # TrafficLight.state_machine.initial_state # => :red
+ #
+ # * Call an event to transition a state machine, e.g. <tt>change_color!</tt>.
+ # You can call the event with or without the exclamation mark, however, the common Ruby
+ # idiom is to name methods that directly change the state of the receivier with
+ # an exclamation mark, so <tt>change_color!</tt> is preferred over <tt>change_color</tt>.
+ #
+ # light.current_state #=> :green
+ # light.change_color! #=> true
+ # light.current_state #=> :yellow
+ #
+ # * On a succesful transition to red (from yellow), the local +catch_runners+
+ # method is executed
+ #
+ # light.current_state #=> :red
+ # light.change_color! #=> true
+ # light.runners_caught #=> 1
+ #
+ # * The object acts differently depending on its current state, for instance,
+ # the change_color! method has a different action depending on the current
+ # color of the light
+ #
+ # light.change_color! #=> true
+ # light.current_state #=> :red
+ # light.change_color! #=> true
+ # light.current_state #=> :green
+ #
+ # * Get the possible events for a state
+ #
+ # TrafficLight.state_machine.events_for(:red) # => [:change_color, :defect]
+ # TrafficLight.state_machine.events_for(:blink) # => [:repair]
+ #
+ # The StateMachine also supports the following features :
+ #
+ # * Success callbacks on event transition
+ #
+ # event :sample, :success => :we_win do
+ # ...
+ # end
+ #
+ # * Enter and exit callbacks par state
+ #
+ # state :open, :enter => [:alert_twitter, :send_emails], :exit => :alert_twitter
+ #
+ # * Guards on transition
+ #
+ # event :close do
+ # # You may only close the store if the safe is locked!!
+ # transitions :to => :closed, :from => :open, :guard => :safe_locked?
+ # end
+ #
+ # * Setting the initial state
+ #
+ # state_machine :initial => :yellow do
+ # ...
+ # end
+ #
+ # * Named the state machine, to have more than one
+ #
+ # class Stated
+ # include ActiveModel::StateMachine
+ #
+ # strate_machine :name => :ontest do
+ # end
+ #
+ # state_machine do
+ # end
+ # end
+ #
+ # # Get the state of the <tt>:ontest</tt> state machine
+ # stat.current_state(:ontest)
+ # # Get the initial state
+ # Stated.state_machine(:ontest).initial_state
+ #
+ # * Changing the state
+ #
+ # stat.current_state(:default, :astate) # => :astate
+ # # But you must give the name of the state machine, here <tt>:default</tt>
+ #
module StateMachine
autoload :Event, 'active_model/state_machine/event'
autoload :Machine, 'active_model/state_machine/machine'
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