1. What is Active Model?
To understand Active Model, you need to know a little about Active Record. Active Record is an ORM (Object Relational Mapper) that connects objects whose data requires persistent storage to a relational database. However, it has functionality that is useful outside of the ORM, some of these include validations, callbacks, translations, the ability to create custom attributes, etc.
Some of this functionality was abstracted from Active Record to form Active Model. Active Model is a library containing various modules that can be used on plain Ruby objects that require model-like features but are not tied to any table in a database.
In summary, while Active Record provides an interface for defining models that correspond to database tables, Active Model provides functionality for building model-like Ruby classes that don't necessarily need to be backed by a database. Active Model can be used independently of Active Record.
Some of these modules are explained below.
1.1. API
ActiveModel::API
adds the ability for a class to work with Action
Pack and Action
View right out of the box.
When including ActiveModel::API
, other modules are included by default which
enables you to get features like:
Here is an example of a class that includes ActiveModel::API
and how it can be
used:
class EmailContact
include ActiveModel::API
attr_accessor :name, :email, :message
validates :name, :email, :message, presence: true
def deliver
if valid?
# Deliver email
end
end
end
irb> email_contact = EmailContact.new(name: "David", email: "david@example.com", message: "Hello World")
irb> email_contact.name # Attribute Assignment
=> "David"
irb> email_contact.to_model == email_contact # Conversion
=> true
irb> email_contact.model_name.name # Naming
=> "EmailContact"
irb> EmailContact.human_attribute_name("name") # Translation if the locale is set
=> "Name"
irb> email_contact.valid? # Validations
=> true
irb> empty_contact = EmailContact.new
irb> empty_contact.valid?
=> false
Any class that includes ActiveModel::API
can be used with form_with
,
render
and any other Action View helper
methods, just like
Active Record objects.
For example, form_with
can be used to create a form for an EmailContact
object as follows:
<%= form_with model: EmailContact.new do |form| %>
<%= form.text_field :name %>
<% end %>
which results in the following HTML:
<form action="/email_contacts" method="post">
<input type="text" name="email_contact[name]" id="email_contact_name">
</form>
render
can be used to render a partial with the object:
<%= render @email_contact %>
You can learn more about how to use form_with
and render
with
ActiveModel::API
compatible objects in the Action View Form
Helpers and Layouts and
Rendering
guides, respectively.
1.2. Model
ActiveModel::Model
includes ActiveModel::API to interact with Action Pack and Action View
by default, and is the recommended approach to implement model-like Ruby
classes. It will be extended in the future to add more functionality.
class Person
include ActiveModel::Model
attr_accessor :name, :age
end
irb> person = Person.new(name: 'bob', age: '18')
irb> person.name # => "bob"
irb> person.age # => "18"
1.3. Attributes
ActiveModel::Attributes
allows you to define data types, set default values, and handle casting and
serialization on plain Ruby objects. This can be useful for form data which will
produce Active Record-like conversion for things like dates and booleans on
regular objects.
To use Attributes
, include the module in your model class and define your
attributes using the attribute
macro. It accepts a name, a cast type, a
default value, and any other options supported by the attribute type.
class Person
include ActiveModel::Attributes
attribute :name, :string
attribute :date_of_birth, :date
attribute :active, :boolean, default: true
end
irb> person = Person.new
irb> person.name = "Jane"
irb> person.name
=> "Jane"
# Casts the string to a date set by the attribute
irb> person.date_of_birth = "2020-01-01"
irb> person.date_of_birth
=> Wed, 01 Jan 2020
irb> person.date_of_birth.class
=> Date
# Uses the default value set by the attribute
irb> person.active
=> true
# Casts the integer to a boolean set by the attribute
irb> person.active = 0
irb> person.active
=> false
Some additional methods described below are available when using
ActiveModel::Attributes
.
1.3.1. Method: attribute_names
The attribute_names
method returns an array of attribute names.
irb> Person.attribute_names
=> ["name", "date_of_birth", "active"]
1.3.2. Method: attributes
The attributes
method returns a hash of all the attributes with their names as
keys and the values of the attributes as values.
irb> person.attributes
=> {"name" => "Jane", "date_of_birth" => Wed, 01 Jan 2020, "active" => false}
1.4. Attribute Assignment
ActiveModel::AttributeAssignment
allows you to set an object's attributes by passing in a hash of attributes with
keys matching the attribute names. This is useful when you want to set multiple
attributes at once.
Consider the following class:
class Person
include ActiveModel::AttributeAssignment
attr_accessor :name, :date_of_birth, :active
end
irb> person = Person.new
# Set multiple attributes at once
irb> person.assign_attributes(name: "John", date_of_birth: "1998-01-01", active: false)
irb> person.name
=> "John"
irb> person.date_of_birth
=> Thu, 01 Jan 1998
irb> person.active
=> false
If the passed hash responds to the permitted?
method and the return value of
this method is false
, an ActiveModel::ForbiddenAttributesError
exception is
raised.
permitted?
is used for strong
params
integration whereby you are assigning a params attribute from a request.
irb> person = Person.new
# Using strong parameters checks, build a hash of attributes similar to params from a request
irb> params = ActionController::Parameters.new(name: "John")
=> #<ActionController::Parameters {"name" => "John"} permitted: false>
irb> person.assign_attributes(params)
=> # Raises ActiveModel::ForbiddenAttributesError
irb> person.name
=> nil
# Permit the attributes we want to allow assignment
irb> permitted_params = params.permit(:name)
=> #<ActionController::Parameters {"name" => "John"} permitted: true>
irb> person.assign_attributes(permitted_params)
irb> person.name
=> "John"
1.4.1. Method alias: attributes=
The assign_attributes
method has an alias attributes=
.
A method alias is a method that performs the same action as another method, but is called something different. Aliases exist for the sake of readability and convenience.
The following example demonstrates the use of the attributes=
method to set
multiple attributes at once:
irb> person = Person.new
irb> person.attributes = { name: "John", date_of_birth: "1998-01-01", active: false }
irb> person.name
=> "John"
irb> person.date_of_birth
=> "1998-01-01"
assign_attributes
and attributes=
are both method calls, and accept
the hash of attributes to assign as an argument. In many cases, Ruby allows
parens ()
from method calls, and curly braces {}
from hash definitions, to
be omitted.
"Setter" methods like attributes=
are commonly written without ()
, even
though including them works the same, and they require the hash to always
include {}
. person.attributes=({ name: "John" })
is fine, but
person.attributes = name: "John"
results in a SyntaxError
.
Other method calls like assign_attributes
may or may not contain both parens
()
and {}
for the hash argument. For example, assign_attributes name:
"John"
and assign_attributes({ name: "John" })
are both perfectly valid Ruby
code, however, assign_attributes { name: "John" }
is not, because Ruby can't
differentiate that hash argument from a block, and will raise a SyntaxError
.
1.5. Attribute Methods
ActiveModel::AttributeMethods
provides a way to define methods dynamically for attributes of a model. This
module is particularly useful to simplify attribute access and manipulation, and
it can add custom prefixes and suffixes to the methods of a class. You can
define the prefixes and suffixes and which methods on the object will use them
as follows:
- Include
ActiveModel::AttributeMethods
in your class. - Call each of the methods you want to add, such as
attribute_method_suffix
,attribute_method_prefix
,attribute_method_affix
. - Call
define_attribute_methods
after the other methods to declare the attribute(s) that should be prefixed and suffixed. - Define the various generic
_attribute
methods that you have declared. The parameterattribute
in these methods will be replaced by the argument passed indefine_attribute_methods
. In the example below it'sname
.
attribute_method_prefix
and attribute_method_suffix
are used to define
the prefixes and suffixes that will be used to create the methods.
attribute_method_affix
is used to define both the prefix and suffix at the
same time.
class Person
include ActiveModel::AttributeMethods
attribute_method_affix prefix: "reset_", suffix: "_to_default!"
attribute_method_prefix "first_", "last_"
attribute_method_suffix "_short?"
define_attribute_methods "name"
attr_accessor :name
private
# Attribute method call for 'first_name'
def first_attribute(attribute)
public_send(attribute).split.first
end
# Attribute method call for 'last_name'
def last_attribute(attribute)
public_send(attribute).split.last
end
# Attribute method call for 'name_short?'
def attribute_short?(attribute)
public_send(attribute).length < 5
end
# Attribute method call 'reset_name_to_default!'
def reset_attribute_to_default!(attribute)
public_send("#{attribute}=", "Default Name")
end
end
irb> person = Person.new
irb> person.name = "Jane Doe"
irb> person.first_name
=> "Jane"
irb> person.last_name
=> "Doe"
irb> person.name_short?
=> false
irb> person.reset_name_to_default!
=> "Default Name"
If you call a method that is not defined, it will raise a NoMethodError
error.
1.5.1. Method: alias_attribute
ActiveModel::AttributeMethods
provides aliasing of attribute methods using
alias_attribute
.
The example below creates an alias attribute for name
called full_name
. They
return the same value, but the alias full_name
better reflects that the
attribute includes a first name and last name.
class Person
include ActiveModel::AttributeMethods
attribute_method_suffix "_short?"
define_attribute_methods :name
attr_accessor :name
alias_attribute :full_name, :name
private
def attribute_short?(attribute)
public_send(attribute).length < 5
end
end
irb> person = Person.new
irb> person.name = "Joe Doe"
irb> person.name
=> "Joe Doe"
# `full_name` is the alias for `name`, and returns the same value
irb> person.full_name
=> "Joe Doe"
irb> person.name_short?
=> false
# `full_name_short?` is the alias for `name_short?`, and returns the same value
irb> person.full_name_short?
=> false
1.6. Callbacks
ActiveModel::Callbacks
gives plain Ruby objects Active Record style
callbacks. The
callbacks allow you to hook into model lifecycle events, such as before_update
and after_create
, as well as to define custom logic to be executed at specific
points in the model's lifecycle.
You can implement ActiveModel::Callbacks
by following the steps below:
- Extend
ActiveModel::Callbacks
within your class. - Employ
define_model_callbacks
to establish a list of methods that should have callbacks associated with them. When you designate a method such as:update
, it will automatically include all three default callbacks (before
,around
, andafter
) for the:update
event. - Inside the defined method, utilize
run_callbacks
, which will execute the callback chain when the specific event is triggered. - In your class, you can then utilize the
before_update
,after_update
, andaround_update
methods like how you would use them in an Active Record model.
class Person
extend ActiveModel::Callbacks
define_model_callbacks :update
before_update :reset_me
after_update :finalize_me
around_update :log_me
# `define_model_callbacks` method containing `run_callbacks` which runs the callback(s) for the given event
def update
run_callbacks(:update) do
puts "update method called"
end
end
private
# When update is called on an object, then this method is called by `before_update` callback
def reset_me
puts "reset_me method: called before the update method"
end
# When update is called on an object, then this method is called by `after_update` callback
def finalize_me
puts "finalize_me method: called after the update method"
end
# When update is called on an object, then this method is called by `around_update` callback
def log_me
puts "log_me method: called around the update method"
yield
puts "log_me method: block successfully called"
end
end
The above class will yield the following which indicates the order in which the callbacks are being called:
irb> person = Person.new
irb> person.update
reset_me method: called before the update method
log_me method: called around the update method
update method called
log_me method: block successfully called
finalize_me method: called after the update method
=> nil
As per the above example, when defining an 'around' callback remember to yield
to the block, otherwise, it won't be executed.
method_name
passed to define_model_callbacks
must not end with !
,
?
or =
. In addition, defining the same callback multiple times will
overwrite previous callback definitions.
1.6.1. Defining Specific Callbacks
You can choose to create specific callbacks by passing the only
option to the
define_model_callbacks
method:
define_model_callbacks :update, :create, only: [:after, :before]
This will create only the before_create
/ after_create
and before_update
/
after_update
callbacks, but skip the around_*
ones. The option will apply
to all callbacks defined on that method call. It's possible to call
define_model_callbacks
multiple times, to specify different lifecycle events:
define_model_callbacks :create, only: :after
define_model_callbacks :update, only: :before
define_model_callbacks :destroy, only: :around
This will create after_create
, before_update
, and around_destroy
methods
only.
1.6.2. Defining Callbacks with a Class
You can pass a class to before_<type>
, after_<type>
and around_<type>
for
more control over when and in what context your callbacks are triggered. The
callback will trigger that class's <action>_<type>
method, passing an instance
of the class as an argument.
class Person
extend ActiveModel::Callbacks
define_model_callbacks :create
before_create PersonCallbacks
end
class PersonCallbacks
def self.before_create(obj)
# `obj` is the Person instance that the callback is being called on
end
end
1.6.3. Aborting Callbacks
The callback chain can be aborted at any point in time by throwing :abort
.
This is similar to how Active Record callbacks work.
In the example below, since we throw :abort
before an update in the reset_me
method, the remaining callback chain including before_update
will be aborted,
and the body of the update
method won't be executed.
class Person
extend ActiveModel::Callbacks
define_model_callbacks :update
before_update :reset_me
after_update :finalize_me
around_update :log_me
def update
run_callbacks(:update) do
puts "update method called"
end
end
private
def reset_me
puts "reset_me method: called before the update method"
throw :abort
puts "reset_me method: some code after abort"
end
def finalize_me
puts "finalize_me method: called after the update method"
end
def log_me
puts "log_me method: called around the update method"
yield
puts "log_me method: block successfully called"
end
end
irb> person = Person.new
irb> person.update
reset_me method: called before the update method
=> false
1.7. Conversion
ActiveModel::Conversion
is a collection of methods that allow you to convert your object to different
forms for different purposes. A common use case is to convert your object to a
string or an integer to build URLs, form fields, and more.
The ActiveModel::Conversion
module adds the following methods: to_model
,
to_key
, to_param
, and to_partial_path
to classes.
The return values of the methods depend on whether persisted?
is defined and
if an id
is provided. The persisted?
method should return true
if the
object has been saved to the database or store, otherwise, it should return
false
. The id
should reference the id of the object or nil if the object is
not saved.
class Person
include ActiveModel::Conversion
attr_accessor :id
def initialize(id)
@id = id
end
def persisted?
id.present?
end
end
1.7.1. to_model
The to_model
method returns the object itself.
irb> person = Person.new(1)
irb> person.to_model == person
=> true
If your model does not act like an Active Model object, then you should define
:to_model
yourself returning a proxy object that wraps your object with Active
Model compliant methods.
class Person
def to_model
# A proxy object that wraps your object with Active Model compliant methods.
PersonModel.new(self)
end
end
1.7.2. to_key
The to_key
method returns an array of the object's key attributes if any of
the attributes are set, whether or not the object is persisted. Returns nil if
there are no key attributes.
irb> person.to_key
=> [1]
A key attribute is an attribute that is used to identify the object. For example, in a database-backed model, the key attribute is the primary key.
1.7.3. to_param
The to_param
method returns a string
representation of the object's key
suitable for use in URLs, or nil
in the case where persisted?
is false
.
irb> person.to_param
=> "1"
1.7.4. to_partial_path
The to_partial_path
method returns a string
representing the path associated
with the object. Action Pack uses this to find a suitable partial to represent
the object.
irb> person.to_partial_path
=> "people/person"
1.8. Dirty
ActiveModel::Dirty
is useful for tracking changes made to model attributes before they are saved.
This functionality allows you to determine which attributes have been modified,
what their previous and current values are, and perform actions based on those
changes. It's particularly handy for auditing, validation, and conditional logic
within your application. It provides a way to track changes in your object in
the same way as Active Record.
An object becomes dirty when it has gone through one or more changes to its attributes and has not been saved. It has attribute-based accessor methods.
To use ActiveModel::Dirty
, you need to:
- Include the module in your class.
- Define the attribute methods that you want to track changes for, using
define_attribute_methods
. - Call
[attr_name]_will_change!
before each change to the tracked attribute. - Call
changes_applied
after the changes are persisted. - Call
clear_changes_information
when you want to reset the changes information. - Call
restore_attributes
when you want to restore previous data.
You can then use the methods provided by ActiveModel::Dirty
to query the
object for its list of all changed attributes, the original values of the
changed attributes, and the changes made to the attributes.
Let's consider a Person
class with attributes first_name
and last_name
and
determine how we can use ActiveModel::Dirty
to track changes to these
attributes.
class Person
include ActiveModel::Dirty
attr_reader :first_name, :last_name
define_attribute_methods :first_name, :last_name
def initialize
@first_name = nil
@last_name = nil
end
def first_name=(value)
first_name_will_change! unless value == @first_name
@first_name = value
end
def last_name=(value)
last_name_will_change! unless value == @last_name
@last_name = value
end
def save
# Persist data - clears dirty data and moves `changes` to `previous_changes`.
changes_applied
end
def reload!
# Clears all dirty data: current changes and previous changes.
clear_changes_information
end
def rollback!
# Restores all previous data of the provided attributes.
restore_attributes
end
end
1.8.1. Querying an Object Directly for its List of All Changed Attributes
irb> person = Person.new
# A newly instantiated `Person` object is unchanged:
irb> person.changed?
=> false
irb> person.first_name = "Jane Doe"
irb> person.first_name
=> "Jane Doe"
changed?
returns true
if any of the attributes have unsaved changes,
false
otherwise.
irb> person.changed?
=> true
changed
returns an array with the name of the attributes containing
unsaved changes.
irb> person.changed
=> ["first_name"]
changed_attributes
returns a hash of the attributes with unsaved changes
indicating their original values like attr => original value
.
irb> person.changed_attributes
=> {"first_name" => nil}
changes
returns a hash of changes, with the attribute names as the keys,
and the values as an array of the original and new values like attr => [original value, new value]
.
irb> person.changes
=> {"first_name" => [nil, "Jane Doe"]}
previous_changes
returns a hash of attributes that were changed before the
model was saved (i.e. before changes_applied
is called).
irb> person.previous_changes
=> {}
irb> person.save
irb> person.previous_changes
=> {"first_name" => [nil, "Jane Doe"]}
1.8.2. Attribute-based Accessor Methods
irb> person = Person.new
irb> person.changed?
=> false
irb> person.first_name = "John Doe"
irb> person.first_name
=> "John Doe"
[attr_name]_changed?
checks whether the particular attribute has been
changed or not.
irb> person.first_name_changed?
=> true
[attr_name]_was
tracks the previous value of the attribute.
irb> person.first_name_was
=> nil
[attr_name]_change
tracks both the previous and current values of the
changed attribute. Returns an array with [original value, new value]
if
changed, otherwise returns nil
.
irb> person.first_name_change
=> [nil, "John Doe"]
irb> person.last_name_change
=> nil
[attr_name]_previously_changed?
checks whether the particular attribute
has been changed before the model was saved (i.e. before changes_applied
is
called).
irb> person.first_name_previously_changed?
=> false
irb> person.save
irb> person.first_name_previously_changed?
=> true
[attr_name]_previous_change
tracks both previous and current values of the
changed attribute before the model was saved (i.e. before changes_applied
is
called). Returns an array with [original value, new value]
if changed,
otherwise returns nil
.
irb> person.first_name_previous_change
=> [nil, "John Doe"]
1.9. Naming
ActiveModel::Naming
adds a class method and helper methods to make naming and routing easier to
manage. The module defines the model_name
class method which will define
several accessors using some
ActiveSupport::Inflector
methods.
class Person
extend ActiveModel::Naming
end
name
returns the name of the model.
irb> Person.model_name.name
=> "Person"
singular
returns the singular class name of a record or class.
irb> Person.model_name.singular
=> "person"
plural
returns the plural class name of a record or class.
irb> Person.model_name.plural
=> "people"
element
removes the namespace and returns the singular snake_cased name.
It is generally used by Action Pack and/or Action View helpers to aid in
rendering the name of partials/forms.
irb> Person.model_name.element
=> "person"
human
transforms the model name into a more human format, using I18n. By
default, it will underscore and then humanize the class name.
irb> Person.model_name.human
=> "Person"
collection
removes the namespace and returns the plural snake_cased name.
It is generally used by Action Pack and/or Action View helpers to aid in
rendering the name of partials/forms.
irb> Person.model_name.collection
=> "people"
param_key
returns a string to use for params names.
irb> Person.model_name.param_key
=> "person"
i18n_key
returns the name of the i18n key. It underscores the model name
and then returns it as a symbol.
irb> Person.model_name.i18n_key
=> :person
route_key
returns a string to use while generating route names.
irb> Person.model_name.route_key
=> "people"
singular_route_key
returns a string to use while generating route names.
irb> Person.model_name.singular_route_key
=> "person"
uncountable?
identifies whether the class name of a record or class is
uncountable.
irb> Person.model_name.uncountable?
=> false
Some Naming
methods, like param_key
, route_key
and
singular_route_key
, differ for namespaced models based on whether it's inside
an isolated Engine.
1.9.1. Customize the Name of the Model
Sometimes you may want to customize the name of the model that is used in form helpers and URL generation. This can be useful in situations where you want to use a more user-friendly name for the model, while still being able to reference it using its full namespace.
For example, let's say you have a Person
namespace in your Rails application,
and you want to create a form for a new Person::Profile
.
By default, Rails would generate the form with the URL /person/profiles
, which
includes the namespace person
. However, if you want the URL to simply point to
profiles
without the namespace, you can customize the model_name
method like
this:
module Person
class Profile
include ActiveModel::Model
def self.model_name
ActiveModel::Name.new(self, nil, "Profile")
end
end
end
With this setup, when you use the form_with
helper to create a form for
creating a new Person::Profile
, Rails will generate the form with the URL
/profiles
instead of /person/profiles
, because the model_name
method has
been overridden to return Profile
.
In addition, the path helpers will be generated without the namespace, so you
can use profiles_path
instead of person_profiles_path
to generate the URL
for the profiles
resource. To use the profiles_path
helper, you need to
define the routes for the Person::Profile
model in your config/routes.rb
file like this:
Rails.application.routes.draw do
resources :profiles
end
Consequently, you can expect the model to return the following values for methods that were described in the previous section:
irb> name = ActiveModel::Name.new(Person::Profile, nil, "Profile")
=> #<ActiveModel::Name:0x000000014c5dbae0
irb> name.singular
=> "profile"
irb> name.singular_route_key
=> "profile"
irb> name.route_key
=> "profiles"
1.10. SecurePassword
ActiveModel::SecurePassword
provides a way to securely store any password in an encrypted form. When you
include this module, a has_secure_password
class method is provided which
defines a password
accessor with certain validations on it by default.
ActiveModel::SecurePassword
depends on
bcrypt
, so include this
gem in your Gemfile
to use it.
gem "bcrypt"
ActiveModel::SecurePassword
requires you to have a password_digest
attribute.
The following validations are added automatically:
- Password must be present on creation.
- Confirmation of password (using a
password_confirmation
attribute). - The maximum length of a password is 72 bytes (required as
bcrypt
truncates the string to this size before encrypting it).
If password confirmation validation is not needed, simply leave out the
value for password_confirmation
(i.e. don't provide a form field for it). When
this attribute has a nil
value, the validation will not be triggered.
For further customization, it is possible to suppress the default validations by
passing validations: false
as an argument.
class Person
include ActiveModel::SecurePassword
has_secure_password
has_secure_password :recovery_password, validations: false
attr_accessor :password_digest, :recovery_password_digest
end
irb> person = Person.new
# When password is blank.
irb> person.valid?
=> false
# When the confirmation doesn't match the password.
irb> person.password = "aditya"
irb> person.password_confirmation = "nomatch"
irb> person.valid?
=> false
# When the length of password exceeds 72.
irb> person.password = person.password_confirmation = "a" * 100
irb> person.valid?
=> false
# When only password is supplied with no password_confirmation.
irb> person.password = "aditya"
irb> person.valid?
=> true
# When all validations are passed.
irb> person.password = person.password_confirmation = "aditya"
irb> person.valid?
=> true
irb> person.recovery_password = "42password"
# `authenticate` is an alias for `authenticate_password`
irb> person.authenticate("aditya")
=> #<Person> # == person
irb> person.authenticate("notright")
=> false
irb> person.authenticate_password("aditya")
=> #<Person> # == person
irb> person.authenticate_password("notright")
=> false
irb> person.authenticate_recovery_password("aditya")
=> false
irb> person.authenticate_recovery_password("42password")
=> #<Person> # == person
irb> person.authenticate_recovery_password("notright")
=> false
irb> person.password_digest
=> "$2a$04$gF8RfZdoXHvyTjHhiU4ZsO.kQqV9oonYZu31PRE4hLQn3xM2qkpIy"
irb> person.recovery_password_digest
=> "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
1.11. Serialization
ActiveModel::Serialization
provides basic serialization for your object. You need to declare an attributes
hash that contains the attributes you want to serialize. Attributes must be
strings, not symbols.
class Person
include ActiveModel::Serialization
attr_accessor :name, :age
def attributes
# Declaration of attributes that will be serialized
{ "name" => nil, "age" => nil }
end
def capitalized_name
# Declared methods can be later included in the serialized hash
name&.capitalize
end
end
Now you can access a serialized hash of your object using the
serializable_hash
method. Valid options for serializable_hash
include
:only
, :except
, :methods
and :include
.
irb> person = Person.new
irb> person.serializable_hash
=> {"name" => nil, "age" => nil}
# Set the name and age attributes and serialize the object
irb> person.name = "bob"
irb> person.age = 22
irb> person.serializable_hash
=> {"name" => "bob", "age" => 22}
# Use the methods option to include the capitalized_name method
irb> person.serializable_hash(methods: :capitalized_name)
=> {"name" => "bob", "age" => 22, "capitalized_name" => "Bob"}
# Use the only method to include only the name attribute
irb> person.serializable_hash(only: :name)
=> {"name" => "bob"}
# Use the except method to exclude the name attribute
irb> person.serializable_hash(except: :name)
=> {"age" => 22}
The example to utilize the includes
option requires a slightly more complex
scenario as defined below:
class Person
include ActiveModel::Serialization
attr_accessor :name, :notes # Emulate has_many :notes
def attributes
{ "name" => nil }
end
end
class Note
include ActiveModel::Serialization
attr_accessor :title, :text
def attributes
{ "title" => nil, "text" => nil }
end
end
irb> note = Note.new
irb> note.title = "Weekend Plans"
irb> note.text = "Some text here"
irb> person = Person.new
irb> person.name = "Napoleon"
irb> person.notes = [note]
irb> person.serializable_hash
=> {"name" => "Napoleon"}
irb> person.serializable_hash(include: { notes: { only: "title" }})
=> {"name" => "Napoleon", "notes" => [{"title" => "Weekend Plans"}]}
1.11.1. ActiveModel::Serializers::JSON
Active Model also provides the
ActiveModel::Serializers::JSON
module for JSON serializing / deserializing.
To use the JSON serialization, change the module you are including from
ActiveModel::Serialization
to ActiveModel::Serializers::JSON
. It already
includes the former, so there is no need to explicitly include it.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name
def attributes
{ "name" => nil }
end
end
The as_json
method, similar to serializable_hash
, provides a hash
representing the model with its keys as a string. The to_json
method returns a
JSON string representing the model.
irb> person = Person.new
# A hash representing the model with its keys as a string
irb> person.as_json
=> {"name" => nil}
# A JSON string representing the model
irb> person.to_json
=> "{\"name\":null}"
irb> person.name = "Bob"
irb> person.as_json
=> {"name" => "Bob"}
irb> person.to_json
=> "{\"name\":\"Bob\"}"
You can also define the attributes for a model from a JSON string. To do that,
first define the attributes=
method in your class:
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name
def attributes=(hash)
hash.each do |key, value|
public_send("#{key}=", value)
end
end
def attributes
{ "name" => nil }
end
end
Now it is possible to create an instance of Person
and set attributes using
from_json
.
irb> json = { name: "Bob" }.to_json
=> "{\"name\":\"Bob\"}"
irb> person = Person.new
irb> person.from_json(json)
=> #<Person:0x00000100c773f0 @name="Bob">
irb> person.name
=> "Bob"
1.12. Translation
ActiveModel::Translation
provides integration between your object and the Rails internationalization
(i18n) framework.
class Person
extend ActiveModel::Translation
end
With the human_attribute_name
method, you can transform attribute names into a
more human-readable format. The human-readable format is defined in your locale
file(s).
# config/locales/app.pt-BR.yml
pt-BR:
activemodel:
attributes:
person:
name: "Nome"
irb> Person.human_attribute_name("name")
=> "Name"
irb> I18n.locale = :"pt-BR"
=> :"pt-BR"
irb> Person.human_attribute_name("name")
=> "Nome"
1.13. Validations
ActiveModel::Validations
adds the ability to validate objects and it is important for ensuring data
integrity and consistency within your application. By incorporating validations
into your models, you can define rules that govern the correctness of attribute
values, and prevent invalid data.
class Person
include ActiveModel::Validations
attr_accessor :name, :email, :token
validates :name, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validates! :token, presence: true
end
irb> person = Person.new
irb> person.token = "2b1f325"
irb> person.valid?
=> false
irb> person.name = "Jane Doe"
irb> person.email = "me"
irb> person.valid?
=> false
irb> person.email = "jane.doe@gmail.com"
irb> person.valid?
=> true
# `token` uses validate! and will raise an exception when not set.
irb> person.token = nil
irb> person.valid?
=> "Token can't be blank (ActiveModel::StrictValidationFailed)"
1.13.1. Validation Methods and Options
You can add validations using some of the following methods:
validate
: Adds validation through a method or a block to the class.validates
: An attribute can be passed to thevalidates
method and it provides a shortcut to all default validators.validates!
or settingstrict: true
: Used to define validations that cannot be corrected by end users and are considered exceptional. Each validator defined with a bang or:strict
option set to true will always raiseActiveModel::StrictValidationFailed
instead of adding to the errors when validation fails.validates_with
: Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions.validates_each
: Validates each attribute against a block.
Some of the options below can be used with certain validators. To determine if the option you're using can be used with a specific validator, read through the validation documentation.
:on
: Specifies the context in which to add the validation. You can pass a symbol or an array of symbols. (e.g.on: :create
oron: :custom_validation_context
oron: [:create, :custom_validation_context]
). Validations without an:on
option will run no matter the context. Validations with some:on
option will only run in the specified context. You can pass the context when validating viavalid?(:context)
.:if
: Specifies a method, proc or string to call to determine if the validation should occur (e.g.if: :allow_validation
, orif: -> { signup_step > 2 }
). The method, proc or string should return or evaluate to atrue
orfalse
value.:unless
: Specifies a method, proc or string to call to determine if the validation should not occur (e.g.unless: :skip_validation
, orunless: Proc.new { |user| user.signup_step <= 2 }
). The method, proc or string should return or evaluate to atrue
orfalse
value.:allow_nil
: Skip the validation if the attribute isnil
.:allow_blank
: Skip the validation if the attribute is blank.:strict
: If the:strict
option is set to true, it will raiseActiveModel::StrictValidationFailed
instead of adding the error.:strict
option can also be set to any other exception.
Calling validate
multiple times on the same method will overwrite
previous definitions.
1.13.2. Errors
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 do this manually.
Run valid?
on the object to check if the object is valid or not. If the object
is not valid, it will return false
and the errors will be added to the
errors
object.
irb> person = Person.new
irb> person.email = "me"
irb> person.valid?
=> # Raises Token can't be blank (ActiveModel::StrictValidationFailed)
irb> person.errors.to_hash
=> {:name => ["can't be blank"], :email => ["is invalid"]}
irb> person.errors.full_messages
=> ["Name can't be blank", "Email is invalid"]
1.14. Lint Tests
ActiveModel::Lint::Tests
allows you to test whether an object is compliant with the Active Model API. By
including ActiveModel::Lint::Tests
in your TestCase, it will include tests
that tell you whether your object is fully compliant, or if not, which aspects
of the API are not implemented.
These tests do not attempt to determine the semantic correctness of the returned
values. For instance, you could implement valid?
to always return true
, and
the tests would pass. It is up to you to ensure that the values are semantically
meaningful.
Objects you pass in are expected to return a compliant object from a call to
to_model
. It is perfectly fine for to_model
to return self
.
app/models/person.rb
class Person include ActiveModel::API end
test/models/person_test.rb
require "test_helper" class PersonTest < ActiveSupport::TestCase include ActiveModel::Lint::Tests setup do @model = Person.new end end
See the test methods documentation for more details.
To run the tests you can use the following command:
$ bin/rails test
Run options: --seed 14596
# Running:
......
Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s.
6 runs, 30 assertions, 0 failures, 0 errors, 0 skips