Rails framework is famous for developers' happiness and making things simpler due to its magic, provided developers follow proper conventions.
To extend this magic and to make things simple further, Rails 7 has introduced a change with this PR after which, inverse_of would be inferred automatically for model associations having scopes.
In this article, we'll dive into understanding it with examples.
Let's say we have a Project model with many assigned tasks.
# app/models/project.rb
class Project < ActiveRecord::Base
  has_many :tasks, -> { assigned }
end
# app/models/task.rb
class Task < ActiveRecord::Base
  belongs_to :project
  
  scope :assigned, -> { where.not(assigned_user: nil) }
  scope :unassigned, -> { where(assigned_user: nil) }
end
Verification using tests
Our expectation here is that even with the presence of a scope, bidirectional association should work such that task.project returns the project instance.
# test/models/project_test.rb
class ProjectTest < ActiveSupport::TestCase
  def test_project_inverse_of_scoped_task
    project = Project.new
    task = project.tasks.build
    assert_equal task.project, project
  end
end
Before Rails 7
Automatic detection of inverse_of worked only on a simple has_many: belongs_to / has_one: belongs_to association. If the same association had other options like a custom scope or a custom foreign_key, the automatic detection would not work.
irb(main):001:0> project = Project.first
irb(main):002:0> task = project.tasks.first
irb(main):003:0> project == task.project
Project Load (0.2ms)  SELECT "projects".* FROM "projects" WHERE "projects"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> true
As we can see, a SQL query is fired when we access the project using the task object. If we wanted Rails to not fire the query and instead fetch the original project object from memory, we'd have to pass inverse_of: :project option manually. Rails would not detect it because of the assigned scope.
The test below fails as task.project returns nil instead of the project instance.
$ rails test test/models/project_test.rb --verbose
# Running:
ProjectTest#test_project_inverse_of_scoped_task = 0.24 s = F
Failure:
ProjectTest#test_project_inverse_of_scoped_task [/test/models/project_test.rb:8]:
--- expected
+++ actual
@@ -1 +1 @@
-nil
+#<Project id: nil, name: nil, archived: nil, created_at: nil, updated_at: nil>
rails test test/models/project_test.rb:4
Finished in 1.004950s, 0.9951 runs/s, 0.9951 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
With Rails >= 7
Despite the scope applied to the tasks association, Rails would automatically detect inverse_of.
irb(main):001:0> project = Project.first
irb(main):002:0> task = project.tasks.first
irb(main):003:0> project == task.project
=> true
Here, as expected Rails fetched the project object from the cache instead of making any SQL queries. The same is validated by the passing test below.
$ rails test test/models/project_test.rb --verbose
# Running:
ProjectTest#test_project_inverse_of_scoped_task = 0.18 s = .
Finished in 0.629270s, 1.5891 runs/s, 1.5891 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Points to Note
- 
The automatic detection of
inverse_ofwould not work with an unscoped/scopedbelongs_toassociation. - 
The automatic detection still won't work for scoped associations that contain the
:throughor:foreign_keyoptions. - 
The default value of
automatic_scope_inversingforconfig.load_defaults 7.0is true. Forconfig.load_defaults < 7.0we need to add the following configuration to opt-in:# config/environments/application.rb config.active_record.automatic_scope_inversing = true 
Conclusion
You don't have to mention inverse_of explicitly for each one of your scoped associations anymore! Even if you never really added inverse_of to your scoped associations, Rails 7 will identify it for you automatically and save some of your queries.