[go: up one dir, main page]

More at rubyonrails.org:

Active Model Basics

This guide will provide you with what you need to get started using Active Model. Active Model provides a way for Action Pack and Action View helpers to interact with plain Ruby objects. It also helps to build custom ORMs for use outside of the Rails framework.

After reading this guide, you will know:

  • What Active Model is, and how it relates to Active Record.
  • The different modules that are included in Active Model.
  • How to use Active Model in your classes.

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:

  1. Include ActiveModel::AttributeMethods in your class.
  2. Call each of the methods you want to add, such as attribute_method_suffix, attribute_method_prefix, attribute_method_affix.
  3. Call define_attribute_methods after the other methods to declare the attribute(s) that should be prefixed and suffixed.
  4. Define the various generic _attribute methods that you have declared. The parameter attribute in these methods will be replaced by the argument passed in define_attribute_methods. In the example below it's name.

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:

  1. Extend ActiveModel::Callbacks within your class.
  2. 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, and after) for the :update event.
  3. Inside the defined method, utilize run_callbacks, which will execute the callback chain when the specific event is triggered.
  4. In your class, you can then utilize the before_update, after_update, and around_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:

  1. Include the module in your class.
  2. Define the attribute methods that you want to track changes for, using define_attribute_methods.
  3. Call [attr_name]_will_change! before each change to the tracked attribute.
  4. Call changes_applied after the changes are persisted.
  5. Call clear_changes_information when you want to reset the changes information.
  6. 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:

  1. Password must be present on creation.
  2. Confirmation of password (using a password_confirmation attribute).
  3. 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 the validates method and it provides a shortcut to all default validators.

  • validates! or setting strict: 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 raise ActiveModel::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 or on: :custom_validation_context or on: [: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 via valid?(:context).

  • :if: Specifies a method, proc or string to call to determine if the validation should occur (e.g. if: :allow_validation, or if: -> { signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.

  • :unless: Specifies a method, proc or string to call to determine if the validation should not occur (e.g. unless: :skip_validation, or unless: Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should return or evaluate to a true or false value.

  • :allow_nil: Skip the validation if the attribute is nil.

  • :allow_blank: Skip the validation if the attribute is blank.

  • :strict: If the :strict option is set to true, it will raise ActiveModel::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


Back to top