Embedded associations in Rails using JSON fields
Leverage JSON fields and Rails attributes API to create embedded associations.
Leverage JSON fields and Rails attributes API to create embedded associations.
When it comes to associations in Rails we are all familiar with the traditional directives such as has_many, belongs_to etc.
But sometimes creating a whole table for an associated model feels like overkill because that associated model will only be used in the context of its parent. What you would like is: be able to embed that association within the parent the same way it can be done with NoSQL databases.
The above becomes even more relevant when the association is actually an ordered list of objects which is always saved as a whole.
Fortunately Rails supports storing attributes as JSON and offers a convenient attributes API which can be leveraged to create nested associations.
The attributes API was shipped in Rails 5.
It allows you to define custom types for database fields such as:
The example above uses a prebuilt type but custom types can also be defined for more complex, object-oriented serialization/deserialization.
Let's see how to do this with a concrete example.
Let's assume we want to create Book model and associate to each book a list of chapters. I'll be assuming we're using Postgres as a database. The same can be done with MySQL.
Let's start by creating the migration:
Then let's setup the Book ActiveRecord model:
The model uses the attribute directive to specify that the chapters will be of type Chapter::ListType.
This is the only thing we need to do in the parent model. All the nesting logic will actually be done in the Chapter model.
The Chapter model will be implemented as a plain ActiveModel::Model. There is no need for ActiveRecord as the list of chapters will be saved on the parent model.
There are three things we need to define on this model:
The Chapter model looks like this:
That's all we need to do to define a simple nested association.
Let's open a Rails console and test our new association. The chapters attribute behaves exactly like any other ActiveRecord attribute.
Alright it works but this implementation is a bit of a one-off. Let's see how we can generalise the implementation.
It feels a bit overkill to define a nested class called ListType on all the models we wish to embed. From one model to another the only parameter that will change is the actual model class.
Let's extract that nested type class into a dedicated class and parameterize it:
Now let's remove the old code from the Chapter model:
Finally, let's change the attribute declaration in our Book model:
That's all we need. With that refactored approach you can reuse this nested association pattern on various models within your app. All you need to do on associated models is define the attributes and equality methods.
Keypup's SaaS solution allows engineering teams and all software development stakeholders to gain a better understanding of their engineering efforts by combining real-time insights from their development and project management platforms. The solution integrates multiple data sources into a unified database along with a user-friendly dashboard and insights builder interface. Keypup users can customize tried-and-true templates or create their own reports, insights and dashboards to get a full picture of their development operations at a glance, tailored to their specific needs.
---
Code snippets hosted with ❤ by GitHub
Banner designed by starline / Freepik