The Life‑Changing Magic of Ruby and Rails

Understanding Active Model: Attribute Assignment

In this post, we will explore the AttributeAssignment module in Active Model, which allows you to set the model's attributes by passing in a hash, a feature commonly used by Active Record models.

Model Hierarchy in Rails
Model Hierarchy in Rails

Initializing Objects

Here's a regular Ruby class with two properties and a constructor that initializes them. To create a new instance of the class, you have to pass the arguments expected by the constructor, in the same order.

class Person
  attr_accessor :name, :age
  
  def initialize(name, age)
    @name = name
    @age = age
  end
end

# to create the object
akshay = Person.new('Akshay', 30)

However, if you've seen the Active Record classes, you must have noticed that they don't include the initialize method. Still, you can create them using the new method, passing in a hash of attributes in any order.

class Post < ApplicationRecord
end

# create a new post
post = Post.new(title: 'hello world', body: 'how are you?')

If you try this with a plain Ruby class, it will throw the following error:

akshay = Person.new(name: 'Akshay', age: 30)

app/models/person.rb:4:in `initialize': wrong number of arguments (given 1, expected 2) (ArgumentError)

How does this work?

The AttributeAssignment Module

The answer is contained in the ActiveModel::AttributeAssignment module, which includes the assign_atributes method. It allows you to set all the attributes by passing in a hash of attributes (the hash keys must match the attribute names).

The Active Model framework in Rails allows you to write classes that have to work with other Rails frameworks, such as Active Record and Action Pack.
class Language
  include ActiveModel::AttributeAssignment
  attr_accessor :title, :author
end

ruby = Language.new
ruby.assign_attributes(title: "Ruby", author: "Matz")

ruby.title # => 'Ruby'
ruby.author # => 'Matz'

ruby.assign_attributes(author: "Yukihiro Matsumoto")

ruby.title # => 'Ruby'
ruby.author # => 'Yukihiro Matsumoto'

Under the hood, the assign_attributes method calls the assign_attribute method for each key-value pair. Here's the internal implementation of this method.

def _assign_attribute(k, v)
  setter = :"#{k}="
  if respond_to?(setter)
    public_send(setter, v)
  else
    raise UnknownAttributeError.new(self, k.to_s)
  end
end

Let's try to understand what's going on when we pass name: 'Akshay' when initializing the object.

  1. The setter will be set to :name=
  2. The respond_to? method checks if our class already includes the name= method, which sets the name. If it does, it calls that method.
  3. Since our class uses the attr_accessor :name directive, the name= method is present.  Hence it calls the setter method, seeting the value 'Akshay' for the name attribute.
  4. For attributes not defined via attr_accessor , attr_writer or via overridden methods, it raises the UnknownAttributeError.

That's nice, but how can you create new objects?

On its own, the AttributeAssignment module won't allow you to call the new method to whip up new objects. For that, you need the ActiveModel::API module.

The API module includes the AttributeAssignment module and provides an initialize method. This method takes a attributes hash argument and calls the assign_attributes method, passing the hash.

module ActiveModel
  module API
    include ActiveModel::AttributeAssignment
    
    def initialize(attributes = {})
      assign_attributes(attributes) if attributes
      super()
    end
  end
end

If you include the ActiveModel::API module in your plain Ruby classes and remove the constructor, you can create new instances like Active Record models.

class Person
  include ActiveModel::API
  
  attr_accessor :name, :age
end

# akshay = Person.new(name: 'Akshay', age: 30)
=> #<Person:0x000000010d045b60 @age=30, @name="Akshay">

How do Rails models get this behavior?

All Active Record models inherit from the ApplicationRecord class, which inherits from the ActiveRecord::Base class. The Base class includes the ActiveRecord::AttributeAssignment module. Finally, this module includes the ActiveModel::AttributeAssignment module.

Model Hierarchy in Rails
Model Hierarchy in Rails

Now, the ActiveRecord::AttributeAssignment module provides its own internal implementation for the private methods, but that's a topic for another blog post.


I hope this post helped you gain a deeper understanding of the AttributeAssignment module. You can use it with plain Ruby classes to simplify your code. You can even use this feature outside Rails by only using the ActiveModel framework and including the ActiveModel::API module.

In the next post, we will explore another module in the ActiveModel framework. If you have any questions or feedback, please let me know, I look forward to it.

If you'd like to receive these articles by email, please subscribe below.

Subscribe to Akshay's Blog

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe