Attributes API in Rails

1 min read

Sometimes, you want to set default values on your ActiveRecord objects and convert them to a different type. Rails provides a handy attribute method that lets us do that, and much more. In this post, we will try to understand how to use the attributes API to achieve this.

First, let's examine a real-life scenario where we want to use the attributes API. We have a legacy database with a items table that has a price column. The application used to store the prices as floating-point numbers, but now it needs to read them as integers.

The ideal way is to update the database schema, but for whatever reason, we can't touch the database. We could try to round the price wherever we access it.

book = Item.find(36)
puts "The price of the book is: #{book.price.round}"

However, this places the burden of conversion on the developer, who has to remember to call round each time they access the price.

Using the attributes we can instruct Rails to automatically convert the price to an integer.

class Item < ApplicationRecord
  attribute :price, :integer, default: 0

end

Now, whenever we access an item's price, Rails will convert it to an integer. It will also set the default price to 0 if it's missing in the database.

Another nice thing about attributes is that they don't need to be backed by a database column.

# app/models/author.rb
class Author < ActiveRecord::Base
  attribute :genre, :string
  attribute :articles, :integer, array: true    # postgres-only
  attribute :income_range, :float, range: true  # postgres-only
end

model = Author.new(
  genre: "fiction",
  articles: ["1", "2", "3"],
  income_range: "[100,500.5]",
)

model.attributes
# => { genre: "fiction", articles: [1, 2, 3], income_range: 100.0..500.5 }

The attribute method defines an attribute with a type on the model, overriding the type of existing attributes if needed. This allows control over how values are converted to and from SQL when assigned to a model.