From e235d39e0438b9ee9a5aa6de5c73804ceb10e65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Fri, 26 Jan 2024 14:49:24 +0100 Subject: [PATCH 1/6] Support association strict_loading option `strict_loading` was added to [Rails 6.1] to prevent lazy loading of associations. As adding it to an association declaration can have a massive impact on the way the record and its association is treated, it can be useful to ensure in a test suite the presence of this option. This adds support for adding the `strict_loading` option to an association declaration. [Rails 6.1]: /~https://github.com/rails/rails/pull/37400 Co-authored-by: Jose Blanco @laicuRoot --- .../active_record/association_matcher.rb | 39 +++++++++++++++++++ .../active_record/association_matcher_spec.rb | 17 ++++++++ 2 files changed, 56 insertions(+) diff --git a/lib/shoulda/matchers/active_record/association_matcher.rb b/lib/shoulda/matchers/active_record/association_matcher.rb index d285688c7..ec3f54d41 100644 --- a/lib/shoulda/matchers/active_record/association_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matcher.rb @@ -217,6 +217,24 @@ module ActiveRecord # should belong_to(:organization).touch(true) # end # + # ##### strict_loading + # + # Use `strict_loading` to assert that the `:strict_loading` option was specified. + # + # class Organization < ActiveRecord::Base + # has_many :people, strict_loading: true + # end + # + # # RSpec + # RSpec.describe Organization, type: :model do + # it { should have_many(:people).strict_loading(true) } + # end + # + # # Minitest (Shoulda) + # class OrganizationTest < ActiveSupport::TestCase + # should have_many(:people).strict_loading(true) + # end + # # ##### autosave # # Use `autosave` to assert that the `:autosave` option was specified. @@ -1128,6 +1146,11 @@ def touch(touch = true) self end + def strict_loading(strict_loading = true) + @options[:strict_loading] = strict_loading + self + end + def join_table(join_table_name) @options[:join_table_name] = join_table_name self @@ -1170,6 +1193,7 @@ def matches?(subject) conditions_correct? && validate_correct? && touch_correct? && + strict_loading_correct? && submatchers_match? end @@ -1414,6 +1438,21 @@ def touch_correct? end end + def strict_loading_correct? + return true unless options.key?(:strict_loading) + + if option_verifier.correct_for_boolean?(:strict_loading, options[:strict_loading]) + return true + end + + @missing = [ + "#{name} should have strict_loading set to ", + options[:strict_loading].to_s, + ].join + + false + end + def class_has_foreign_key?(klass) @missing = validate_foreign_key(klass) diff --git a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb index c1b7d4af1..e7cb834d3 100644 --- a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb @@ -1199,6 +1199,23 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) expect(Parent.new).to have_many(:children) end + it 'accepts an association with a matching :strict_loading option' do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(true) + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + expect { + expect(having_many_children). + to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end + def having_many_children(options = {}) define_model :child, parent_id: :integer define_model(:parent).tap do |model| From bf00caaab94e91ee71579232d01862ed1cab9bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Fri, 2 Feb 2024 14:45:28 +0100 Subject: [PATCH 2/6] Document when no argument is passed to `strict_loading` --- .../matchers/active_record/association_matcher.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/shoulda/matchers/active_record/association_matcher.rb b/lib/shoulda/matchers/active_record/association_matcher.rb index ec3f54d41..8948aae9c 100644 --- a/lib/shoulda/matchers/active_record/association_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matcher.rb @@ -235,6 +235,18 @@ module ActiveRecord # should have_many(:people).strict_loading(true) # end # + # Default value is true when no argument is specified + # + # # RSpec + # RSpec.describe Organization, type: :model do + # it { should have_many(:people).strict_loading } + # end + # + # # Minitest (Shoulda) + # class OrganizationTest < ActiveSupport::TestCase + # should have_many(:people).strict_loading + # end + # # ##### autosave # # Use `autosave` to assert that the `:autosave` option was specified. From 56df66c8b10995bd3b8bde0cf8f6da592b84a90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Fri, 2 Feb 2024 15:55:47 +0100 Subject: [PATCH 3/6] Start implementing multiple configuration combinations --- .../application_configuration_helpers.rb | 18 ++ .../active_record/association_matcher_spec.rb | 184 ++++++++++++++++-- 2 files changed, 189 insertions(+), 13 deletions(-) diff --git a/spec/support/unit/helpers/application_configuration_helpers.rb b/spec/support/unit/helpers/application_configuration_helpers.rb index 87d013bc1..f7f10342e 100644 --- a/spec/support/unit/helpers/application_configuration_helpers.rb +++ b/spec/support/unit/helpers/application_configuration_helpers.rb @@ -18,6 +18,24 @@ def with_belongs_to_as_optional_by_default(&block) ) end + def with_strict_loading_by_default_enabled(&block) + configuring_application( + ::ActiveRecord::Base, + :strict_loading_by_default, + true, + &block + ) + end + + def with_strict_loading_by_default_disabled(&block) + configuring_application( + ::ActiveRecord::Base, + :strict_loading_by_default, + false, + &block + ) + end + private def configuring_application(config, name, value) diff --git a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb index e7cb834d3..08a3d588e 100644 --- a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb @@ -1199,21 +1199,179 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) expect(Parent.new).to have_many(:children) end - it 'accepts an association with a matching :strict_loading option' do - expect(having_many_children(strict_loading: true)). - to have_many(:children).strict_loading(true) - end + context 'when the application is configured with strict_loading disabled by default' do + context 'when the association is configured with a strict_loading constraint' do + context 'when qualified with strict_loading(true)' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(true) + end + end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_disabled do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading + end + end - expect { - expect(having_many_children). - to have_many(:children).strict_loading(true) - }.to fail_with_message(message) + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join + + expect { + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end + end + end + + context 'when qualified with strict_loading(false)' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading(false) + end + end + + it 'rejects an association with a matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + expect { + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading + }.to fail_with_message(message) + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + expect { + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end + end + end + end + + context 'when strict_loading is defined on the model level' do + context 'when it is set to true' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new + + expect(parent).to have_many(:children).strict_loading(true) + end + end + + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_disabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new + + expect(parent).to have_many(:children).strict_loading + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new + + expect { + expect(parent).to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end + end + end + + context 'when it is set to false' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new + + expect(parent).to have_many(:children).strict_loading(false) + end + end + + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join + + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new + + expect { + expect(parent).to have_many(:children).strict_loading + }.to fail_with_message(message) + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join + + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new + + expect { + expect(parent).to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end + end + end + end + + # TODO: tests with self.strict_loading_by_default = true on model + end + + context 'when the application is configured with strict_loading enabled by default' do end def having_many_children(options = {}) From 5bdbb2369be24136a727b48afed0990f9c2a6ac2 Mon Sep 17 00:00:00 2001 From: laicuRoot Date: Sat, 10 Feb 2024 16:16:44 +0000 Subject: [PATCH 4/6] Support strict_loading in model and application This PR adds the tests and logic for support at model level for strict_loading. --- .../association_matchers/model_reflection.rb | 10 +- .../active_record/association_matcher_spec.rb | 175 +++++++++++++++++- 2 files changed, 179 insertions(+), 6 deletions(-) diff --git a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb index 13e41e55f..da3a2a74f 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb @@ -13,7 +13,15 @@ def initialize(reflection) end def associated_class - reflection.klass + associated_class = reflection.klass + + if subject.strict_loading_by_default + unless reflection.options[:strict_loading] == false + reflection.options[:strict_loading] = true + end + end + + associated_class end def polymorphic? diff --git a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb index 08a3d588e..1b9eedede 100644 --- a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb @@ -1299,7 +1299,7 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) with_strict_loading_by_default_disabled do message = [ 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', + '(children should have strict_loading set to false)', ].join define_model :child, parent_id: :integer @@ -1332,7 +1332,7 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) with_strict_loading_by_default_disabled do message = [ 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to false)', + '(children should have strict_loading set to true)', ].join define_model :child, parent_id: :integer @@ -1351,7 +1351,7 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) with_strict_loading_by_default_disabled do message = [ 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to false)', + '(children should have strict_loading set to true)', ].join define_model :child, parent_id: :integer @@ -1367,11 +1367,176 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) end end end - - # TODO: tests with self.strict_loading_by_default = true on model end context 'when the application is configured with strict_loading enabled by default' do + context 'when the association is configured with a strict_loading constraint' do + context 'when qualified with strict_loading(true)' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(true) + end + end + + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_enabled do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join + + expect { + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end + end + end + + context 'when qualified with strict_loading(false)' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading(false) + end + end + + it 'rejects an association with a matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + expect { + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading + }.to fail_with_message(message) + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + expect { + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end + end + end + end + + context 'when strict_loading is defined on the model level' do + context 'when it is set to true' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new + + expect(parent).to have_many(:children).strict_loading(true) + end + end + + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_enabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new + + expect(parent).to have_many(:children).strict_loading + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join + + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new + + expect { + expect(parent).to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end + end + end + + context 'when it is set to false' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new + + expect(parent).to have_many(:children).strict_loading(false) + end + end + + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new + + expect { + expect(parent).to have_many(:children).strict_loading + }.to fail_with_message(message) + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new + + expect { + expect(parent).to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end + end + end + end end def having_many_children(options = {}) From dedadd9c7b188feba96881e5f1abae65e1a679fa Mon Sep 17 00:00:00 2001 From: laicuRoot Date: Fri, 16 Feb 2024 15:00:12 +0000 Subject: [PATCH 5/6] Refactor implementation and add application level tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rémy Hannequin --- .../association_matchers/model_reflection.rb | 6 +- .../active_record/association_matcher_spec.rb | 67 ++++++++++++++++++- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb index da3a2a74f..92e1f394b 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb @@ -15,10 +15,8 @@ def initialize(reflection) def associated_class associated_class = reflection.klass - if subject.strict_loading_by_default - unless reflection.options[:strict_loading] == false - reflection.options[:strict_loading] = true - end + if subject.strict_loading_by_default && !(reflection.options[:strict_loading] == false) + reflection.options[:strict_loading] = true end associated_class diff --git a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb index 1b9eedede..a5754298e 100644 --- a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb @@ -1200,6 +1200,41 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) end context 'when the application is configured with strict_loading disabled by default' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + expect(having_many_children). + to have_many(:children).strict_loading(false) + end + end + + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + expect { + expect(having_many_children). + to have_many(:children).strict_loading + }.to fail_with_message(message) + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join + + expect { + expect(having_many_children). + to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end + end + context 'when the association is configured with a strict_loading constraint' do context 'when qualified with strict_loading(true)' do it 'accepts an association with a matching :strict_loading option' do @@ -1239,7 +1274,7 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) end end - it 'rejects an association with a matching :strict_loading option without explicit value with the correct message' do + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do with_strict_loading_by_default_disabled do message = [ 'Expected Parent to have a has_many association called children ', @@ -1370,6 +1405,34 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) end context 'when the application is configured with strict_loading enabled by default' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + expect(having_many_children). + to have_many(:children).strict_loading(true) + end + end + + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_enabled do + expect(having_many_children). + to have_many(:children).strict_loading + end + end + + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join + + expect { + expect(having_many_children). + to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end + end + context 'when the association is configured with a strict_loading constraint' do context 'when qualified with strict_loading(true)' do it 'accepts an association with a matching :strict_loading option' do @@ -1409,7 +1472,7 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) end end - it 'rejects an association with a matching :strict_loading option without explicit value with the correct message' do + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do with_strict_loading_by_default_enabled do message = [ 'Expected Parent to have a has_many association called children ', From 75b2c5c7a247bb09be54848ea0adbe555f14af05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Fri, 23 Feb 2024 16:08:15 +0100 Subject: [PATCH 6/6] Apply reviewer's comments - Refactor verifier logic - Nest all specs in one `describe` block Co-authored-by: Jose Blanco --- .../association_matchers/model_reflection.rb | 12 +- .../association_matchers/option_verifier.rb | 4 + .../active_record/association_matcher_spec.rb | 604 +++++++++--------- 3 files changed, 312 insertions(+), 308 deletions(-) diff --git a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb index 92e1f394b..a415d84cf 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb @@ -13,13 +13,7 @@ def initialize(reflection) end def associated_class - associated_class = reflection.klass - - if subject.strict_loading_by_default && !(reflection.options[:strict_loading] == false) - reflection.options[:strict_loading] = true - end - - associated_class + reflection.klass end def polymorphic? @@ -76,6 +70,10 @@ def has_and_belongs_to_many_name reflection.options[:through] end + def strict_loading? + reflection.options.fetch(:strict_loading, subject.strict_loading_by_default) + end + protected attr_reader :reflection, :subject diff --git a/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb b/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb index ecf7818b1..15cf0032d 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb @@ -122,6 +122,10 @@ def actual_value_for_class_name reflector.associated_class end + def actual_value_for_strict_loading + reflection.strict_loading? + end + def actual_value_for_option(name) option_value = reflection.options[name] diff --git a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb index a5754298e..2a7e96499 100644 --- a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb @@ -1199,403 +1199,405 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {}) expect(Parent.new).to have_many(:children) end - context 'when the application is configured with strict_loading disabled by default' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_disabled do - expect(having_many_children). - to have_many(:children).strict_loading(false) + describe 'strict_loading' do + context 'when the application is configured with strict_loading disabled by default' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + expect(having_many_children). + to have_many(:children).strict_loading(false) + end end - end - it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do - with_strict_loading_by_default_disabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - expect { - expect(having_many_children). - to have_many(:children).strict_loading - }.to fail_with_message(message) + expect { + expect(having_many_children). + to have_many(:children).strict_loading + }.to fail_with_message(message) + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_disabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - expect { - expect(having_many_children). - to have_many(:children).strict_loading(true) - }.to fail_with_message(message) + expect { + expect(having_many_children). + to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end end - end - context 'when the association is configured with a strict_loading constraint' do - context 'when qualified with strict_loading(true)' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_disabled do - expect(having_many_children(strict_loading: true)). - to have_many(:children).strict_loading(true) + context 'when the association is configured with a strict_loading constraint' do + context 'when qualified with strict_loading(true)' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(true) + end end - end - it 'accepts an association with a matching :strict_loading option without explicit value' do - with_strict_loading_by_default_disabled do - expect(having_many_children(strict_loading: true)). - to have_many(:children).strict_loading + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_disabled do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_disabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to false)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join - expect { - expect(having_many_children(strict_loading: true)). - to have_many(:children).strict_loading(false) - }.to fail_with_message(message) + expect { + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end end end - end - context 'when qualified with strict_loading(false)' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_disabled do - expect(having_many_children(strict_loading: false)). - to have_many(:children).strict_loading(false) + context 'when qualified with strict_loading(false)' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading(false) + end end - end - it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do - with_strict_loading_by_default_disabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - expect { - expect(having_many_children(strict_loading: false)). - to have_many(:children).strict_loading - }.to fail_with_message(message) + expect { + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading + }.to fail_with_message(message) + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_disabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - expect { - expect(having_many_children(strict_loading: false)). - to have_many(:children).strict_loading(true) - }.to fail_with_message(message) + expect { + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end end end end - end - context 'when strict_loading is defined on the model level' do - context 'when it is set to true' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_disabled do - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = true - model.has_many :children - end.new + context 'when strict_loading is defined on the model level' do + context 'when it is set to true' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new - expect(parent).to have_many(:children).strict_loading(true) + expect(parent).to have_many(:children).strict_loading(true) + end end - end - it 'accepts an association with a matching :strict_loading option without explicit value' do - with_strict_loading_by_default_disabled do - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = true - model.has_many :children - end.new + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_disabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new - expect(parent).to have_many(:children).strict_loading + expect(parent).to have_many(:children).strict_loading + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_disabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to false)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = true - model.has_many :children - end.new + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new - expect { - expect(parent).to have_many(:children).strict_loading(false) - }.to fail_with_message(message) + expect { + expect(parent).to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end end end - end - context 'when it is set to false' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_disabled do - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = false - model.has_many :children - end.new + context 'when it is set to false' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_disabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new - expect(parent).to have_many(:children).strict_loading(false) + expect(parent).to have_many(:children).strict_loading(false) + end end - end - it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do - with_strict_loading_by_default_disabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = false - model.has_many :children - end.new + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new - expect { - expect(parent).to have_many(:children).strict_loading - }.to fail_with_message(message) + expect { + expect(parent).to have_many(:children).strict_loading + }.to fail_with_message(message) + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_disabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_disabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = false - model.has_many :children - end.new + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new - expect { - expect(parent).to have_many(:children).strict_loading(true) - }.to fail_with_message(message) + expect { + expect(parent).to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end end end end end - end - context 'when the application is configured with strict_loading enabled by default' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_enabled do - expect(having_many_children). - to have_many(:children).strict_loading(true) + context 'when the application is configured with strict_loading enabled by default' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + expect(having_many_children). + to have_many(:children).strict_loading(true) + end end - end - it 'accepts an association with a matching :strict_loading option without explicit value' do - with_strict_loading_by_default_enabled do - expect(having_many_children). - to have_many(:children).strict_loading + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_enabled do + expect(having_many_children). + to have_many(:children).strict_loading + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_enabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to false)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join - expect { - expect(having_many_children). - to have_many(:children).strict_loading(false) - }.to fail_with_message(message) + expect { + expect(having_many_children). + to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end end - end - context 'when the association is configured with a strict_loading constraint' do - context 'when qualified with strict_loading(true)' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_enabled do - expect(having_many_children(strict_loading: true)). - to have_many(:children).strict_loading(true) + context 'when the association is configured with a strict_loading constraint' do + context 'when qualified with strict_loading(true)' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(true) + end end - end - it 'accepts an association with a matching :strict_loading option without explicit value' do - with_strict_loading_by_default_enabled do - expect(having_many_children(strict_loading: true)). - to have_many(:children).strict_loading + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_enabled do + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_enabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to false)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join - expect { - expect(having_many_children(strict_loading: true)). - to have_many(:children).strict_loading(false) - }.to fail_with_message(message) + expect { + expect(having_many_children(strict_loading: true)). + to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end end end - end - context 'when qualified with strict_loading(false)' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_enabled do - expect(having_many_children(strict_loading: false)). - to have_many(:children).strict_loading(false) + context 'when qualified with strict_loading(false)' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading(false) + end end - end - it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do - with_strict_loading_by_default_enabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - expect { - expect(having_many_children(strict_loading: false)). - to have_many(:children).strict_loading - }.to fail_with_message(message) + expect { + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading + }.to fail_with_message(message) + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_enabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - expect { - expect(having_many_children(strict_loading: false)). - to have_many(:children).strict_loading(true) - }.to fail_with_message(message) + expect { + expect(having_many_children(strict_loading: false)). + to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end end end end - end - context 'when strict_loading is defined on the model level' do - context 'when it is set to true' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_enabled do - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = true - model.has_many :children - end.new + context 'when strict_loading is defined on the model level' do + context 'when it is set to true' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new - expect(parent).to have_many(:children).strict_loading(true) + expect(parent).to have_many(:children).strict_loading(true) + end end - end - it 'accepts an association with a matching :strict_loading option without explicit value' do - with_strict_loading_by_default_enabled do - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = true - model.has_many :children - end.new + it 'accepts an association with a matching :strict_loading option without explicit value' do + with_strict_loading_by_default_enabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new - expect(parent).to have_many(:children).strict_loading + expect(parent).to have_many(:children).strict_loading + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_enabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to false)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to false)', + ].join - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = true - model.has_many :children - end.new + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = true + model.has_many :children + end.new - expect { - expect(parent).to have_many(:children).strict_loading(false) - }.to fail_with_message(message) + expect { + expect(parent).to have_many(:children).strict_loading(false) + }.to fail_with_message(message) + end end end - end - context 'when it is set to false' do - it 'accepts an association with a matching :strict_loading option' do - with_strict_loading_by_default_enabled do - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = false - model.has_many :children - end.new + context 'when it is set to false' do + it 'accepts an association with a matching :strict_loading option' do + with_strict_loading_by_default_enabled do + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new - expect(parent).to have_many(:children).strict_loading(false) + expect(parent).to have_many(:children).strict_loading(false) + end end - end - it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do - with_strict_loading_by_default_enabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option without explicit value with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = false - model.has_many :children - end.new + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new - expect { - expect(parent).to have_many(:children).strict_loading - }.to fail_with_message(message) + expect { + expect(parent).to have_many(:children).strict_loading + }.to fail_with_message(message) + end end - end - it 'rejects an association with a non-matching :strict_loading option with the correct message' do - with_strict_loading_by_default_enabled do - message = [ - 'Expected Parent to have a has_many association called children ', - '(children should have strict_loading set to true)', - ].join + it 'rejects an association with a non-matching :strict_loading option with the correct message' do + with_strict_loading_by_default_enabled do + message = [ + 'Expected Parent to have a has_many association called children ', + '(children should have strict_loading set to true)', + ].join - define_model :child, parent_id: :integer - parent = define_model(:parent) do |model| - model.strict_loading_by_default = false - model.has_many :children - end.new + define_model :child, parent_id: :integer + parent = define_model(:parent) do |model| + model.strict_loading_by_default = false + model.has_many :children + end.new - expect { - expect(parent).to have_many(:children).strict_loading(true) - }.to fail_with_message(message) + expect { + expect(parent).to have_many(:children).strict_loading(true) + }.to fail_with_message(message) + end end end end