aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib
diff options
context:
space:
mode:
authorMikel Lindsaar <raasdnil@gmail.com>2010-02-01 10:08:20 +1100
committerMikel Lindsaar <raasdnil@gmail.com>2010-02-01 10:08:20 +1100
commita07d0f87863e01ef931c87bd35bd36c564c20cd3 (patch)
tree308f9e66ad1164cb4a8d3cd9ab9d6263a4828b0d /activemodel/lib
parentc493370f332715dee0ef795a66e896d7f0471cbe (diff)
downloadrails-a07d0f87863e01ef931c87bd35bd36c564c20cd3.tar.gz
rails-a07d0f87863e01ef931c87bd35bd36c564c20cd3.tar.bz2
rails-a07d0f87863e01ef931c87bd35bd36c564c20cd3.zip
Full update on ActiveModel documentation
Diffstat (limited to 'activemodel/lib')
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb140
-rw-r--r--activemodel/lib/active_model/errors.rb107
-rw-r--r--activemodel/lib/active_model/lint.rb16
-rw-r--r--activemodel/lib/active_model/naming.rb15
-rw-r--r--activemodel/lib/active_model/observing.rb23
-rw-r--r--activemodel/lib/active_model/serialization.rb57
-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
9 files changed, 377 insertions, 44 deletions
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