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:
classEmailContactincludeActiveModel::APIattr_accessor:name,:email,:messagevalidates:name,:email,:message,presence: truedefdeliverifvalid?# Deliver emailendendend
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=>trueirb>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=>trueirb>empty_contact=EmailContact.newirb>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_withmodel: EmailContact.newdo|form|%><%=form.text_field:name%><%end%>
which results in the following HTML:
<formaction="/email_contacts"method="post"><inputtype="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.
classPersonincludeActiveModel::Modelattr_accessor:name,:ageend
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.
classPersonincludeActiveModel::Attributesattribute:name,:stringattribute:date_of_birth,:dateattribute:active,:boolean,default: trueend
irb>person=Person.newirb>person.name="Jane"irb>person.name=>"Jane"# Casts the string to a date set by the attributeirb>person.date_of_birth="2020-01-01"irb>person.date_of_birth=>Wed,01Jan2020irb>person.date_of_birth.class=>Date# Uses the default value set by the attributeirb>person.active=>true# Casts the integer to a boolean set by the attributeirb>person.active=0irb>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,01Jan2020,"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:
classPersonincludeActiveModel::AttributeAssignmentattr_accessor:name,:date_of_birth,:activeend
irb>person=Person.new# Set multiple attributes at onceirb>person.assign_attributes(name: "John",date_of_birth: "1998-01-01",active: false)irb>person.name=>"John"irb>person.date_of_birth=>Thu,01Jan1998irb>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 requestirb>params=ActionController::Parameters.new(name: "John")=>#<ActionController::Parameters{"name"=>"John"}permitted: false>irb>person.assign_attributes(params)=># Raises ActiveModel::ForbiddenAttributesErrorirb>person.name=>nil# Permit the attributes we want to allow assignmentirb>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.newirb>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.
classPersonincludeActiveModel::AttributeMethodsattribute_method_affixprefix: "reset_",suffix: "_to_default!"attribute_method_prefix"first_","last_"attribute_method_suffix"_short?"define_attribute_methods"name"attr_accessor:nameprivate# Attribute method call for 'first_name'deffirst_attribute(attribute)public_send(attribute).split.firstend# Attribute method call for 'last_name'deflast_attribute(attribute)public_send(attribute).split.lastend# Attribute method call for 'name_short?'defattribute_short?(attribute)public_send(attribute).length<5end# Attribute method call 'reset_name_to_default!'defreset_attribute_to_default!(attribute)public_send("#{attribute}=","Default Name")endend
irb>person=Person.newirb>person.name="Jane Doe"irb>person.first_name=>"Jane"irb>person.last_name=>"Doe"irb>person.name_short?=>falseirb>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.
classPersonincludeActiveModel::AttributeMethodsattribute_method_suffix"_short?"define_attribute_methods:nameattr_accessor:namealias_attribute:full_name,:nameprivatedefattribute_short?(attribute)public_send(attribute).length<5endend
irb>person=Person.newirb>person.name="Joe Doe"irb>person.name=>"Joe Doe"# `full_name` is the alias for `name`, and returns the same valueirb>person.full_name=>"Joe Doe"irb>person.name_short?=>false# `full_name_short?` is the alias for `name_short?`, and returns the same valueirb>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.
classPersonextendActiveModel::Callbacksdefine_model_callbacks:updatebefore_update:reset_meafter_update:finalize_mearound_update:log_me# `define_model_callbacks` method containing `run_callbacks` which runs the callback(s) for the given eventdefupdaterun_callbacks(:update)doputs"update method called"endendprivate# When update is called on an object, then this method is called by `before_update` callbackdefreset_meputs"reset_me method: called before the update method"end# When update is called on an object, then this method is called by `after_update` callbackdeffinalize_meputs"finalize_me method: called after the update method"end# When update is called on an object, then this method is called by `around_update` callbackdeflog_meputs"log_me method: called around the update method"yieldputs"log_me method: block successfully called"endend
The above class will yield the following which indicates the order in which the callbacks are being called:
irb>person=Person.newirb>person.updatereset_me method: called before the update methodlog_me method: called around the update methodupdate method calledlog_me method: block successfully calledfinalize_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: :afterdefine_model_callbacks:update,only: :beforedefine_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.
classPersonextendActiveModel::Callbacksdefine_model_callbacks:createbefore_createPersonCallbacksendclassPersonCallbacksdefself.before_create(obj)# `obj` is the Person instance that the callback is being called onendend
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.
classPersonextendActiveModel::Callbacksdefine_model_callbacks:updatebefore_update:reset_meafter_update:finalize_mearound_update:log_medefupdaterun_callbacks(:update)doputs"update method called"endendprivatedefreset_meputs"reset_me method: called before the update method"throw:abortputs"reset_me method: some code after abort"enddeffinalize_meputs"finalize_me method: called after the update method"enddeflog_meputs"log_me method: called around the update method"yieldputs"log_me method: block successfully called"endend
irb>person=Person.newirb>person.updatereset_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.
classPersonincludeActiveModel::Conversionattr_accessor:iddefinitialize(id)@id=idenddefpersisted?id.present?endend
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.
classPersondefto_model# A proxy object that wraps your object with Active Model compliant methods.PersonModel.new(self)endend
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.
classPersonincludeActiveModel::Dirtyattr_reader:first_name,:last_namedefine_attribute_methods:first_name,:last_namedefinitialize@first_name=nil@last_name=nilenddeffirst_name=(value)first_name_will_change!unlessvalue==@first_name@first_name=valueenddeflast_name=(value)last_name_will_change!unlessvalue==@last_name@last_name=valueenddefsave# Persist data - clears dirty data and moves `changes` to `previous_changes`.changes_appliedenddefreload!# Clears all dirty data: current changes and previous changes.clear_changes_informationenddefrollback!# Restores all previous data of the provided attributes.restore_attributesendend
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?=>falseirb>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.saveirb>person.previous_changes=>{"first_name"=>[nil,"Jane Doe"]}
1.8.2. Attribute-based Accessor Methods
irb>person=Person.newirb>person.changed?=>falseirb>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?=>falseirb>person.saveirb>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.
classPersonextendActiveModel::Namingend
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:
modulePersonclassProfileincludeActiveModel::Modeldefself.model_nameActiveModel::Name.new(self,nil,"Profile")endendend
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.drawdoresources:profilesend
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:0x000000014c5dbae0irb>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.
classPersonincludeActiveModel::SecurePasswordhas_secure_passwordhas_secure_password:recovery_password,validations: falseattr_accessor:password_digest,:recovery_password_digestend
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"*100irb>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?=>trueirb>person.recovery_password="42password"# `authenticate` is an alias for `authenticate_password`irb>person.authenticate("aditya")=>#<Person># == personirb>person.authenticate("notright")=>falseirb>person.authenticate_password("aditya")=>#<Person># == personirb>person.authenticate_password("notright")=>falseirb>person.authenticate_recovery_password("aditya")=>falseirb>person.authenticate_recovery_password("42password")=>#<Person># == personirb>person.authenticate_recovery_password("notright")=>falseirb>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.
classPersonincludeActiveModel::Serializationattr_accessor:name,:agedefattributes# Declaration of attributes that will be serialized{"name"=>nil,"age"=>nil}enddefcapitalized_name# Declared methods can be later included in the serialized hashname&.capitalizeendend
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.newirb>person.serializable_hash=>{"name"=>nil,"age"=>nil}# Set the name and age attributes and serialize the objectirb>person.name="bob"irb>person.age=22irb>person.serializable_hash=>{"name"=>"bob","age"=>22}# Use the methods option to include the capitalized_name methodirb>person.serializable_hash(methods: :capitalized_name)=>{"name"=>"bob","age"=>22,"capitalized_name"=>"Bob"}# Use the only method to include only the name attributeirb>person.serializable_hash(only: :name)=>{"name"=>"bob"}# Use the except method to exclude the name attributeirb>person.serializable_hash(except: :name)=>{"age"=>22}
The example to utilize the includes
option requires a slightly more complex scenario as defined below:
classPersonincludeActiveModel::Serializationattr_accessor:name,:notes# Emulate has_many :notesdefattributes{"name"=>nil}endendclassNoteincludeActiveModel::Serializationattr_accessor:title,:textdefattributes{"title"=>nil,"text"=>nil}endend
irb>note=Note.newirb>note.title="Weekend Plans"irb>note.text="Some text here"irb>person=Person.newirb>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.
classPersonincludeActiveModel::Serializers::JSONattr_accessor:namedefattributes{"name"=>nil}endend
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 stringirb>person.as_json=>{"name"=>nil}# A JSON string representing the modelirb>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:
classPersonincludeActiveModel::Serializers::JSONattr_accessor:namedefattributes=(hash)hash.eachdo|key,value|public_send("#{key}=",value)endenddefattributes{"name"=>nil}endend
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.newirb>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.
classPersonextendActiveModel::Translationend
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.ymlpt-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.
classPersonincludeActiveModel::Validationsattr_accessor:name,:email,:tokenvalidates:name,presence: truevalidates:email,format: {with: URI::MailTo::EMAIL_REGEXP}validates!:token,presence: trueend
irb>person=Person.newirb>person.token="2b1f325"irb>person.valid?=>falseirb>person.name="Jane Doe"irb>person.email="me"irb>person.valid?=>falseirb>person.email="jane.doe@gmail.com"irb>person.valid?=>true# `token` uses validate! and will raise an exception when not set.irb>person.token=nilirb>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.newirb>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
classPersonincludeActiveModel::APIend
test/models/person_test.rb
require"test_helper"classPersonTest<ActiveSupport::TestCaseincludeActiveModel::Lint::Testssetupdo@model=Person.newendend
See the test methods documentation for more details.
To run the tests you can use the following command:
$bin/rails testRun 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