diff options
Diffstat (limited to 'activemodel')
-rw-r--r-- | activemodel/README | 189 | ||||
-rw-r--r-- | activemodel/lib/active_model/attribute_methods.rb | 141 | ||||
-rw-r--r-- | activemodel/lib/active_model/errors.rb | 109 | ||||
-rw-r--r-- | activemodel/lib/active_model/lint.rb | 14 | ||||
-rw-r--r-- | activemodel/lib/active_model/naming.rb | 17 | ||||
-rw-r--r-- | activemodel/lib/active_model/observing.rb | 23 | ||||
-rw-r--r-- | activemodel/lib/active_model/serialization.rb | 58 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializers/xml.rb | 4 | ||||
-rw-r--r-- | activemodel/lib/active_model/state_machine.rb | 149 | ||||
-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 |
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 |