2010-01-16 16:56:20 -05:00
|
|
|
= Active Model - defined interfaces for Rails
|
2010-01-31 18:08:20 -05:00
|
|
|
|
2010-01-16 16:56:20 -05:00
|
|
|
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.
|
2010-01-31 18:08:20 -05:00
|
|
|
|
2010-01-16 16:56:20 -05:00
|
|
|
Active Model is a solution for this problem.
|
2010-01-31 18:08:20 -05:00
|
|
|
|
2010-01-16 16:56:20 -05:00
|
|
|
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:
|
2010-01-31 18:08:20 -05:00
|
|
|
|
|
|
|
* 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.
|
2010-01-16 16:56:20 -05:00
|
|
|
|
|
|
|
{Learn more}[link:classes/ActiveModel/CallBacks.html]
|
2010-01-31 18:08:20 -05:00
|
|
|
|
2010-01-16 16:56:20 -05:00
|
|
|
* For classes that already look like an Active Record object
|
2010-01-31 18:08:20 -05:00
|
|
|
|
|
|
|
class Person
|
2010-01-16 16:56:20 -05:00
|
|
|
include ActiveModel::Conversion
|
|
|
|
end
|
|
|
|
|
|
|
|
...returns the class itself when sent :to_model
|
2010-01-31 18:08:20 -05:00
|
|
|
|
|
|
|
{Learn more}[link:classes/ActiveModel/Conversion.html]
|
|
|
|
|
2010-01-16 16:56:20 -05:00
|
|
|
* Tracking changes in your object
|
2010-01-31 18:08:20 -05:00
|
|
|
|
2010-01-16 16:56:20 -05:00
|
|
|
Provides all the value tracking features implemented by ActiveRecord...
|
|
|
|
|
|
|
|
person = Person.new
|
|
|
|
person.name # => nil
|
|
|
|
person.changed? # => false
|
|
|
|
person.name = 'bob'
|
|
|
|
person.changed? # => true
|
|
|
|
person.changed # => ['name']
|
|
|
|
person.changes # => { 'name' => [nil, 'bob'] }
|
|
|
|
person.name = 'robert'
|
|
|
|
person.save
|
|
|
|
person.previous_changes # => {'name' => ['bob, 'robert']}
|
2010-01-31 18:08:20 -05:00
|
|
|
|
2010-01-16 16:56:20 -05:00
|
|
|
{Learn more}[link:classes/ActiveModel/Dirty.html]
|
2010-01-31 18:08:20 -05:00
|
|
|
|
|
|
|
* 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]
|