Skip to content

Commit 246b3b6

Browse files
authored
Merge pull request rails#50883 from Shopify/infer-inverse-of-deprecation
Put plural inverse association inference behind a configuration flag
2 parents 9e18cee + 7a8e58b commit 246b3b6

File tree

8 files changed

+84
-2
lines changed

8 files changed

+84
-2
lines changed

activerecord/lib/active_record/reflection.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Reflection # :nodoc:
1111
class_attribute :_reflections, instance_writer: false, default: {}
1212
class_attribute :aggregate_reflections, instance_writer: false, default: {}
1313
class_attribute :automatic_scope_inversing, instance_writer: false, default: false
14+
class_attribute :automatically_invert_plural_associations, instance_writer: false, default: false
1415
end
1516

1617
class << self
@@ -700,10 +701,25 @@ def inverse_name
700701
def automatic_inverse_of
701702
if can_find_inverse_of_automatically?(self)
702703
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
703-
plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
704704

705705
begin
706-
reflection = klass._reflect_on_association(inverse_name) || klass._reflect_on_association(plural_inverse_name)
706+
reflection = klass._reflect_on_association(inverse_name)
707+
if !reflection
708+
plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
709+
reflection = klass._reflect_on_association(plural_inverse_name)
710+
711+
if reflection && !active_record.automatically_invert_plural_associations
712+
ActiveRecord.deprecator.warn(
713+
"The `#{active_record.name}##{name}` inverse association could have been automatically" \
714+
" inferred as `#{klass.name}##{plural_inverse_name}` but wasn't because `automatically_invert_plural_associations`" \
715+
" is disabled.\n\n" \
716+
"If automatic inference is intended, you can consider enabling" \
717+
" `config.active_record.automatically_invert_plural_associations`.\n\n" \
718+
"If automatic inference is not intended, you can silence this warning by defining the association with `inverse_of: false`."
719+
)
720+
reflection = nil
721+
end
722+
end
707723
rescue NameError => error
708724
raise unless error.name.to_s == class_name
709725

activerecord/test/cases/associations/inverse_associations_test.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ def test_has_many_and_belongs_to_automatic_inverse_shares_objects_on_comment
201201
end
202202

203203
def test_belongs_to_should_find_inverse_has_many_automatically
204+
assert_equal true, Subscription.automatically_invert_plural_associations
205+
204206
book = Book.create!
205207
subscriber = book.subscribers.new nick: "Nickname"
206208

activerecord/test/cases/helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
# Quote "type" if it's a reserved word for the current connection.
3333
QUOTED_TYPE = ActiveRecord::Base.lease_connection.quote_column_name("type")
3434

35+
ActiveRecord::Base.automatically_invert_plural_associations = true
36+
3537
ActiveRecord.raise_on_assign_to_attr_readonly = true
3638
ActiveRecord.belongs_to_required_validates_foreign_key = false
3739

activerecord/test/models/subscriber.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
class Subscriber < ActiveRecord::Base
44
self.primary_key = "nick"
5+
56
has_many :subscriptions
67
has_many :books, through: :subscriptions
78
end

activerecord/test/models/subscription.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

33
class Subscription < ActiveRecord::Base
4+
self.automatically_invert_plural_associations = true
5+
46
belongs_to :subscriber, counter_cache: :books_count
57
belongs_to :book, -> { author_visibility_visible }
68

guides/source/configuring.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Below are the default values associated with each target version. In cases of co
6060

6161
#### Default Values for Target Version 7.2
6262

63+
- [`config.active_record.automatically_invert_plural_associations`](#config-active-record-automatically-invert-plural-associations): `true`
6364
- [`config.active_record.validate_migration_timestamps`](#config-active-record-validate-migration-timestamps): `true`
6465
- [`config.active_storage.web_image_content_types`](#config-active-storage-web-image-content-types): `%w[image/png image/jpeg image/gif image/webp]`
6566

@@ -1051,6 +1052,49 @@ Specifies if an error should be raised if the order of a query is ignored during
10511052
10521053
Controls whether migrations are numbered with serial integers or with timestamps. The default is `true`, to use timestamps, which are preferred if there are multiple developers working on the same application.
10531054
1055+
#### `config.active_record.automatically_invert_plural_associations`
1056+
1057+
Controls whether Active Record will automatically look for an inverse relations with a pluralized name.
1058+
1059+
Example:
1060+
1061+
```ruby
1062+
class Post < ApplicationRecord
1063+
has_many :comments
1064+
end
1065+
1066+
class Comment < ApplicationRecord
1067+
belongs_to :post
1068+
end
1069+
```
1070+
1071+
In the above case Active Record used to only look for a `:comment` (singular) association in `Post`, and won't find it.
1072+
1073+
With this option enabled, it will also look for a `:comments` association. In the vast majority of cases
1074+
having the inverse association discovered is beneficial as it can prevent some useless queries, but
1075+
it may cause backward compatibility issues with legacy code that doesn't expect it.
1076+
1077+
This behavior can be disable on a per-model basis:
1078+
1079+
1080+
```ruby
1081+
class Comment < ApplicationRecord
1082+
self.automatically_invert_plural_associations = false
1083+
1084+
belongs_to :post
1085+
end
1086+
```
1087+
1088+
And on a per-association basis:
1089+
1090+
```ruby
1091+
class Comment < ApplicationRecord
1092+
self.automatically_invert_plural_associations = false
1093+
1094+
belongs_to :post, inverse_of: false
1095+
end
1096+
```
1097+
10541098
#### `config.active_record.validate_migration_timestamps`
10551099
10561100
Controls whether to validate migration timestamps. When set, an error will be raised if the

railties/lib/rails/application/configuration.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ def load_defaults(target_version)
328328

329329
if respond_to?(:active_record)
330330
active_record.validate_migration_timestamps = true
331+
active_record.automatically_invert_plural_associations = true
331332
end
332333
else
333334
raise "Unknown version #{target_version.to_s.inspect}"

railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_2.rb.tt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,17 @@
2727
# expected format can disable validation by setting this config to `false`.
2828
#++
2929
# Rails.application.config.active_record.validate_migration_timestamps = true
30+
31+
###
32+
# Controls whether Active Record will automatically look for an inverse relations
33+
# with a pluralized name.
34+
#
35+
# Example:
36+
# class Comment < ActiveRecord::Base
37+
# belongs_to :post
38+
# end
39+
#
40+
# In the above case Active Record will now try to infer a `:comments` relation
41+
# on `Post`, while previously it would only look for `:comment`.
42+
#++
43+
# Rails.application.config.active_record.automatically_invert_plural_associations = true

0 commit comments

Comments
 (0)