Embedded associations in Rails using JSON fields

Learn how to improve the performance and scalability of your Rails applications by using embedded associations with JSON fields. Discover the benefits and limitations of this technique, and learn how to implement it in your own projects.

Arnaud Lachaume
Arnaud Lachaume LinkedIn
3 min read
Embedded associations in Rails using JSON fields

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 Rails attributes API

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.

Book and Chapters (first version)

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.

The Book model

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

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:

  • Serializable attributes: required to know what to save in database
  • Equality operator: required to detect changes on the list of chapters
  • Type interface: how to serialize/deserialize our set of chapters

The Chapter model looks like this:

That's all we need to do to define a simple nested association.

Testing our new 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.

Book and Chapters (refactored)

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.

About us

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

Ready to Transform Your Analytics?

Join teams already using AI to make data-driven decisions faster than ever.

Most Recent Articles

Data Ownership, Governance, and Inter-Departmental Silos: Who Owns the Definition of a Story Point?

Data Ownership, Governance, and Inter-Departmental Silos: Who Owns the Definition of a Story Point?

Centralizing SDLC data across ten business units exposes a governance vacuum long before it delivers insight: seven competing "story point" scales, four business units with no named data owner, and a workflow taxonomy nobody was ever assigned to define. This isn't a GDPR problem — it's a political and technical negotiation over who owns what data, and who is accountable when a poorly maintained JIRA board quietly skews a portfolio-wide metric. Discover how Keypup MCP makes data ownership, definition consistency, and governance maturity measurable across every business unit.

Stephane Ibos
Change Management and Cross-Functional Alignment: Getting HR, Product, and Marketing to Trust Engineering Metrics

Change Management and Cross-Functional Alignment: Getting HR, Product, and Marketing to Trust Engineering Metrics

Rolling out SDLC analytics across an enterprise takes more than an engineering VP's buy-in. If HR misreads flow metrics in performance reviews, or Product Management keeps demanding features while ignoring system health, the initiative quietly becomes an "engineering-only project" that never earns organization-wide trust. Discover how Keypup MCP builds the guardrails, translations, and cross-functional accountability that make SDLC analytics stick beyond engineering.

Liam Davis
Strategic Vendor Selection and Platform Fatigue: How to Add an SEI Platform Without Adding Another Dashboard

Strategic Vendor Selection and Platform Fatigue: How to Add an SEI Platform Without Adding Another Dashboard

Enterprises already juggle dozens of tools for project management, CI/CD, monitoring, and HR. Before adding a Software Engineering Intelligence platform, IT architects and procurement teams need to weigh true integration cost, training burden, and vendor lock-in risk — not just the license fee. Discover how Keypup MCP delivers engineering insight through the tools your teams already use, with no new dashboard, no new login, and a fraction of the total cost of ownership.

Thomas Williams