Understanding `inverse_of` In Rails Associations.

ActiveRecord offers a range of powerful features, and one often overlooked yet highly useful option is inverse_of. This simple association option enables us to explicitly define bi-directional relationships between models.

Bi-directional Associations

In Rails, bi-directional associations refer to relationships where two models reference each other, allowing data to flow in both directions. It makes easier to query, manipulate, and maintain data integrity.

For instance, a Project has many Tasks, and each Task belongs to a Project. This allows us to traverse from a Project to its Tasks, and from a Task back to its Project.

class Project < ApplicationRecord
  has_many :tasks
end

class Task < ApplicationRecord
  belongs_to :project
end

ActiveRecord will try to automatically recognize the bi-directional association between two models based on the names of their associations.

project = Project.first
task = project.tasks.first
project.name == task.project.name
=> true

project.name = "Changed Name"
project.name == task.project.name
=> true

However, bi-directional associations that contain the :through or :foreign_key options will not be automatically identified.

class Project < ApplicationRecord
  has_many :tasks
end

class Task < ApplicationRecord
  belongs_to :project, class_name: 'Project', foreign_key: 'project_id'
end
project = Project.first
task = project.tasks.first
project.name == task.project.name  # This executes query 

Project Load (0.4ms)  SELECT "projects".* FROM "projects" WHERE "projects"."client_id" = $1 ORDER BY "projects"."id" ASC LIMIT $2  [["client_id", 1], ["LIMIT", 1]]

=> true

project.name = "Changed Name"
project.name == task.project.name
=> false

In the above code snippet, after changing project.name, task.project.name still shows the old value because Rails doesn’t automatically update the associated object’s state. This happens because the association isn’t fully bi-directional here, so task.project holds stale data until it’s explicitly reloaded.

inverse_of option

ActiveRecord provides the :inverse_of option so we can explicitly declare bi-directional associations.

The inverse_of option explicitly declares that two associated models are inversely related.

Essentially, it helps ActiveRecord understand that a model and its associated records reference each other in memory, preventing redundant database queries and keeping object states in sync.

Now let’s fix our previous bi-directional association that contain the :foreign_key option by using inverse_of.

class Project < ApplicationRecord
  has_many :tasks, inverse_of: :project
end

class Task < ApplicationRecord
  belongs_to :project, class_name: 'Project', foreign_key: 'project_id'
end

Including the :inverse_of option in the has_many declaration allows ActiveRecord to recognize the bi-directional association.

project = Project.first
task = project.tasks.first
project.name == task.project.name
=> true

project.name = "Changed Name"
project.name == task.project.name
=> true

Rails automatically detects inverse associations. If we do not set the :inverse_of option on the association, then ActiveRecord will guess the inverse association based on naming conventions.

Automatic inverse detection only works on has_many, has_one, and belongs_to associations.

However, as we mentioned earlier bi-directional associations that contain the :through or :foreign_key options will not be automatically identified. Adding inverse_of enables Rails to recognize the association as bi-directional. Refer further details in the ActiveRecord associations guide.

Recently, Rails 7 added automatic inverse_of detection for associations with scopes.

Benefits of inverse_of

  • Reduces redundant database queries.
  • Maintains object consistency.
  • Improves performance in large applications.

Limitations and Pitfalls of inverse_of

  • Not compatible with custom scopes.
  • Not Supports polymorphic associations.
  • Won’t automatically works with :through or :foreign_key option

Conclusion

The inverse_of option in Rails is a powerful tool for managing model associations efficiently.

By providing a clear path for bi-directional navigation and optimizing memory usage, it enhances application’s performance and data integrity.

Need help on your Ruby on Rails or React project?

Join Our Newsletter