Back to Blog

Rails 7 adds disable_joins for associations

on October 26, 2021
This blog is part of our Rails 7 series.

Rails 7 introduces disable_joins for database associations to avoid join errors in multi tenant applications where the two tables are located in different database clusters.

Rails commonly perform lazy loading while fetching records for better efficiency and internally build join queries to fetch the records faster.

But when we deal with different database clusters within the same application, the lazy loading nature of Active Record causes errors when databases can't perform join queries between different clusters.

As a resolution for this, Rails 7 has newly added 'disable_joins' option to tell Rails upfront that the queries have to be performed without joining tables. In this case two or more queries will be generated and used to fetch the results from different database clusters.

The disable_joins option is available for both has_many :through and has_one :through associations. In some cases, if order or limit is applied, it will be performed in-memory due to database limitations.

1class Employee
2  has_many :projects
3  has_many :tasks, through: :projects, disable_joins: true
4end
5
6class Project
7  belongs_to :employee
8  has_many :tasks
9end
10
11class Task
12  belongs_to :project
13end

Before Rails 7, @employee.tasks without disable_joins option will raise error because database clusters can't handle join queries here. If we provide the disable_joins option as true (by default the value is set to false) then Rails will make two or more separate queries to fetch the results from different database clusters.

1SELECT "project"."id" FROM "projects" WHERE "projects"."employee_id" = ? [["employee_id", 1]]
2SELECT "tasks".* FROM "tasks" WHERE "tasks"."project_id" IN (?, ?, ?) [["project_id", 1], ["project_id", 2], ["project_id", 3]]

Similarly for has_one through association:

1class Publisher
2  has_one :author
3  has_one :book, through: :author, disable_joins: true
4end
5
6class Author
7  belongs_to :publisher
8  has_one :book
9end
10
11class Book
12  belongs_to :author
13end

The @publisher.book will make the following two queries to fetch the results.

1SELECT "author"."id" FROM "authors" WHERE "authors"."publisher_id" = ? [["publisher_id", 1]]
2SELECT "books".* FROM "books" WHERE "books"."author_id" = ? [["author_id", 1]]

Please be aware that enabling this option without realising the actual need will result in performance implications since two or more queries are performed here. Also queries with order or limit will be done in-memory since the order from one database can't be applied to another database query.

Please checkout the disable_joins pull requests for has_many through & has_one through associations for more details and discussions.