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.