Introduction to ActiveRecord and ActiveModel Attributes API

Dec 4th, 2016

Rails 5.0 is without a doubt a great release with plenty of useful changes and additions. The most notable change was probably ActionCable - the layer responsible for integrating your app with websockets. However, there were also other additions that could bring some substantial improvements to your Rails apps, but were a bit outshined by bigger changes. One of such features is Attributes API.

ActiveRecord Attributes And Defaults - The Old Way

Imagine that you are in a vacation rental industry and you are adding a new model for handling reservations for rentals, let’s call it Reservation. To keep it simple for the purpose of this example, let’s assume that we need start_date and end_date date fields for handling the duration of the reservations and price field, which is pretty useful unless you are developing an app for a charity organization ;). Let’s say we want to provide some defaults for the start_date and end_date attributes to be 1 day from now and 8 days from know accordingly when initializing a new instance of Reservation and the price should be converted to integer, so in fact it is going to be price in cents, and the expected format of the input is going to look like "$1000.12". How could we handle it inside ActiveRecord models?

For default values, one option would be to add after_initialize callbacks which would assign the given defaults unless the values were already set in the initializer. For price we can simply override the attribute writer which is Reservation#price= method. We would most likely end up with something looking like this:

Well, the above code works, but it can get repetitive across many models and doesn’t read that well, would be much better to handle it with more of a declarative approach. But is there any built-in solution for that problem in ActiveRecord?

Then answer is yes! Time to meet your new friend in Rails world: ActiveRecord Attributes API.

ActiveRecord Attributes And Defaults - The New Way - Attributes API

Since Rails 5.0 we can use awesome Attributes API in our models. Just declare the name of the attribute with attribute class method, its type and provide optional default (either a raw value or a lambda). The great thing is that you are not limited only to attributes backed by database, you can use it for virtual attributes as well!

For our Reservation model, we could apply the following refactoring with Attributes API:

That’s exactly what we needed. What about our conversion for price? As we can specify the type for given attribute, we may expect that it would be possible to define our own types. Turns out it is possible and quite simple actually. Just create a class inheriting from ActiveRecord::Type::Value or already existing type, e.g. ActiveRecord::Type::Integer, define cast method and register the new type. In our use case let’s register a new price type:

As expected, the price used for query was the one after serialization.

If you want to check the list of built-in types or learn more, check the official docs.

What About ActiveModel?

So far I’ve discussed only the ActiveRecord Attributes API, but the title clearly mentions ActiveModel part, so what about it? There is a bad news and good news.

The bad news is that it is not yet supported in Rails core, but most likely it is going to be the part of ActiveModel eventually.

The good news is that you can use it today, even though it’s not a part of Rails! I’ve released ActiveModelAttributes gem which provides Attributes API for ActiveModel and it works in a very similar way to ActiveRecord Attributes.

Just define your ActiveModel model, include ActiveModel::Model and ActiveModelAttributes modules and define attributes and their types using attribute class method:

You can also add your custom types. Just create a class inheriting from ActiveModel::Type::Value or already existing type, e.g. ActiveModel::Type::Integer, define cast method and register the new type:

Wrapping up

ActiveRecord Attributes API is defintely a great feature introduced in Rails 5.0. Even though it is not yet supported in ActiveModel in Rails core, ActiveModelAttributes can be easily added to your Rails apps to provide almost the same functionality.

Comments

About Me

Hi, my name is Karol Galanciak, I'm a technical polyglot and domain expert in vacation rental and fitness industries.

I specialize in building APIs, microservices architecture, working with legacy applications, building ambitious Single Page Applications and building payment processors for credit cards processing. Currently I'm working as CTO at BookingSync, so at this moment I'm not available for Ruby on Rails or Ember.js consulting.

Overwhelmed by too much information?

Following all the newsletters, RSS feeds, Twitter and other sources might be overwhelming. Stop wasting your valuable time - subscribe now and by the end of every month I will send you an email with the list of the most interesting Ruby / JavaScript (Ember.js) / PostgreSQL articles with a quick overview.