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.