From 7cad5feaa757b4bde066fef89b584bebffad1fc6 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Sat, 1 Jun 2019 16:39:10 -0600 Subject: [PATCH] Clear up confusion w/ presence around belongs_to associations Now that `belongs_to` associations add a presence validation automatically, if you have a test such as the following: class StudentBook < ApplicationRecord belongs_to :student end RSpec.describe StudentBook do it { is_expected.not_to be_valid } it { is_expected.to validate_presence_of(:student) } end then the test for presence of :student will fail, because the validation message on the automatic presence validation is different than the usual message. The solution here, of course, is to just use `belong_to`, but that is not very obvious. So this commit updates the presence matcher to remind users about using `belong_to` in a case such as this. --- .../validate_presence_of_matcher.rb | 97 +++++- lib/shoulda/matchers/util/word_wrap.rb | 7 +- .../application_configuration_helpers.rb | 31 ++ spec/support/unit/matchers/match_against.rb | 151 +++++++++ .../validate_presence_of_matcher_spec.rb | 300 +++++++++++++++++- .../active_record/association_matcher_spec.rb | 49 +-- 6 files changed, 587 insertions(+), 48 deletions(-) create mode 100644 spec/support/unit/helpers/application_configuration_helpers.rb create mode 100644 spec/support/unit/matchers/match_against.rb diff --git a/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb index f87337b0..7bf8de84 100644 --- a/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb @@ -181,6 +181,27 @@ module Shoulda "validate that :#{@attribute} cannot be empty/falsy" end + def failure_message + message = super + + if should_add_footnote_about_belongs_to? + message << "\n\n" + message << Shoulda::Matchers.word_wrap(<<-MESSAGE.strip, indent: 2) +You're getting this error because #{reason_for_existing_presence_validation}. +*This* presence validation doesn't use "can't be blank", the usual validation +message, but "must exist" instead. + +With that said, did you know that the `belong_to` matcher can test this +validation for you? Instead of using `validate_presence_of`, try the following +instead: + + it { should #{representation_of_belongs_to} } + MESSAGE + end + + message + end + private def secure_password_being_validated? @@ -197,7 +218,7 @@ module Shoulda def allows_and_double_checks_value_of!(value) allows_value_of(value, @expected_message) rescue ActiveModel::AllowValueMatcher::AttributeChangedValueError - raise ActiveModel::CouldNotSetPasswordError.create(@subject.class) + raise ActiveModel::CouldNotSetPasswordError.create(model) end def allows_original_or_typecast_value?(value) @@ -207,7 +228,7 @@ module Shoulda def disallows_and_double_checks_value_of!(value) disallows_value_of(value, @expected_message) rescue ActiveModel::AllowValueMatcher::AttributeChangedValueError - raise ActiveModel::CouldNotSetPasswordError.create(@subject.class) + raise ActiveModel::CouldNotSetPasswordError.create(model) end def disallows_original_or_typecast_value?(value) @@ -218,25 +239,79 @@ module Shoulda if collection? [Array.new] else - [''].tap do |disallowed| - if !expects_to_allow_nil? - disallowed << nil - end + values = [] + + if !association_being_validated? + values << '' end + + if !expects_to_allow_nil? + values << nil + end + + values end end def collection? - if reflection - [:has_many, :has_and_belongs_to_many].include?(reflection.macro) + if association_reflection + [:has_many, :has_and_belongs_to_many].include?( + association_reflection.macro, + ) else false end end - def reflection - @subject.class.respond_to?(:reflect_on_association) && - @subject.class.reflect_on_association(@attribute) + def should_add_footnote_about_belongs_to? + belongs_to_association_being_validated? && + presence_validation_exists_on_attribute? + end + + def reason_for_existing_presence_validation + if belongs_to_association_configured_to_be_required? + "you've instructed your `belongs_to` association to add a " + + 'presence validation to the attribute' + else + # assume ::ActiveRecord::Base.belongs_to_required_by_default == true + 'ActiveRecord is configured to add a presence validation to all ' + + '`belongs_to` associations, and this includes yours' + end + end + + def representation_of_belongs_to + 'belong_to(:parent)'.tap do |str| + if association_reflection.options.include?(:optional) + str << ".optional(#{association_reflection.options[:optional]})" + end + end + end + + def belongs_to_association_configured_to_be_required? + association_reflection.options[:optional] == false || + association_reflection.options[:required] == true + end + + def belongs_to_association_being_validated? + association_being_validated? && + association_reflection.macro == :belongs_to + end + + def association_being_validated? + !!association_reflection + end + + def association_reflection + model.respond_to?(:reflect_on_association) && + model.reflect_on_association(@attribute) + end + + def presence_validation_exists_on_attribute? + model._validators.include?(@attribute) + end + + def model + @subject.class end end end diff --git a/lib/shoulda/matchers/util/word_wrap.rb b/lib/shoulda/matchers/util/word_wrap.rb index 7e73f6ac..8f6077d2 100644 --- a/lib/shoulda/matchers/util/word_wrap.rb +++ b/lib/shoulda/matchers/util/word_wrap.rb @@ -2,6 +2,8 @@ module Shoulda module Matchers # @private module WordWrap + TERMINAL_WIDTH = 72 + def word_wrap(document, options = {}) Document.new(document, options).wrap end @@ -112,7 +114,6 @@ module Shoulda # @private class Line - TERMINAL_WIDTH = 72 OFFSETS = { left: -1, right: +1 } def initialize(line, indent: 0) @@ -171,7 +172,7 @@ module Shoulda def wrap_line(line, direction: :left) index = nil - if line.length > TERMINAL_WIDTH + if line.length > Shoulda::Matchers::WordWrap::TERMINAL_WIDTH index = determine_where_to_break_line(line, direction: :left) if index == -1 @@ -192,7 +193,7 @@ module Shoulda def determine_where_to_break_line(line, args) direction = args.fetch(:direction) - index = TERMINAL_WIDTH + index = Shoulda::Matchers::WordWrap::TERMINAL_WIDTH offset = OFFSETS.fetch(direction) while line[index] !~ /\s/ && (0...line.length).cover?(index) diff --git a/spec/support/unit/helpers/application_configuration_helpers.rb b/spec/support/unit/helpers/application_configuration_helpers.rb new file mode 100644 index 00000000..87d013bc --- /dev/null +++ b/spec/support/unit/helpers/application_configuration_helpers.rb @@ -0,0 +1,31 @@ +module UnitTests + module ApplicationConfigurationHelpers + def with_belongs_to_as_required_by_default(&block) + configuring_application( + ::ActiveRecord::Base, + :belongs_to_required_by_default, + true, + &block + ) + end + + def with_belongs_to_as_optional_by_default(&block) + configuring_application( + ::ActiveRecord::Base, + :belongs_to_required_by_default, + false, + &block + ) + end + + private + + def configuring_application(config, name, value) + previous_value = config.send(name) + config.send("#{name}=", value) + yield + ensure + config.send("#{name}=", previous_value) + end + end +end diff --git a/spec/support/unit/matchers/match_against.rb b/spec/support/unit/matchers/match_against.rb new file mode 100644 index 00000000..9614c5b0 --- /dev/null +++ b/spec/support/unit/matchers/match_against.rb @@ -0,0 +1,151 @@ +module UnitTests + module Matchers + def match_against(object) + MatchAgainstMatcher.new(object) + end + + class MatchAgainstMatcher + DIVIDER = ('-' * Shoulda::Matchers::WordWrap::TERMINAL_WIDTH).freeze + + attr_reader :failure_message, :failure_message_when_negated + + def initialize(object) + @object = object + @failure_message = nil + @failure_message_when_negated = nil + end + + def and_fail_with(message) + @message = message.strip + self + end + alias_method :or_fail_with, :and_fail_with + + def matches?(generate_matcher) + @positive_matcher = generate_matcher.call + @negative_matcher = generate_matcher.call + + if positive_matcher.matches?(object) + !message || matcher_fails_in_negative? + else + @failure_message = <<-MESSAGE +Expected the matcher to match in the positive, but it failed with this message: + +#{DIVIDER} +#{positive_matcher.failure_message} +#{DIVIDER} + MESSAGE + false + end + end + + def does_not_match?(generate_matcher) + @positive_matcher = generate_matcher.call + @negative_matcher = generate_matcher.call + + if negative_matcher.does_not_match?(object) + !message || matcher_fails_in_positive? + else + @failure_message_when_negated = <<-MESSAGE +Expected the matcher to match in the negative, but it failed with this message: + +#{DIVIDER} +#{negative_matcher.failure_message_when_negated} +#{DIVIDER} + MESSAGE + false + end + end + + def supports_block_expectations? + true + end + + private + + attr_reader :object, :message, :positive_matcher, :negative_matcher + + def matcher_fails_in_negative? + if !negative_matcher.does_not_match?(object) + if message == negative_matcher.failure_message_when_negated.strip + true + else + diff_result = diff( + message, + negative_matcher.failure_message_when_negated.strip, + ) + @failure_message = <<-MESSAGE +Expected the negative version of the matcher not to match and for the failure +message to be: + +#{DIVIDER} +#{message.chomp} +#{DIVIDER} + +However, it was: + +#{DIVIDER} +#{negative_matcher.failure_message_when_negated} +#{DIVIDER} + +Diff: + +#{Shoulda::Matchers::Util.indent(diff_result, 2)} + MESSAGE + false + end + else + @failure_message = + 'Expected the negative version of the matcher not to match, ' + + 'but it did.' + false + end + end + + def matcher_fails_in_positive? + if !positive_matcher.matches?(object) + if message == positive_matcher.failure_message.strip + true + else + diff_result = diff( + message, + positive_matcher.failure_message.strip, + ) + @failure_message_when_negated = <<-MESSAGE +Expected the positive version of the matcher not to match and for the failure +message to be: + +#{DIVIDER} +#{message.chomp} +#{DIVIDER} + +However, it was: + +#{DIVIDER} +#{positive_matcher.failure_message} +#{DIVIDER} + +Diff: + +#{Shoulda::Matchers::Util.indent(diff_result, 2)} + MESSAGE + false + end + else + @failure_message_when_negated = + 'Expected the positive version of the matcher not to match, ' + + 'but it did.' + false + end + end + + def diff(expected, actual) + differ.diff(expected, actual)[1..-1] + end + + def differ + @_differ ||= RSpec::Support::Differ.new + end + end + end +end diff --git a/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb index 86adc9f2..b0e06ced 100644 --- a/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb @@ -1,6 +1,8 @@ require 'unit_spec_helper' describe Shoulda::Matchers::ActiveModel::ValidatePresenceOfMatcher, type: :model do + include UnitTests::ApplicationConfigurationHelpers + context 'a model with a presence validation' do it 'accepts' do expect(validating_presence).to matcher @@ -176,7 +178,7 @@ could not be proved. end end - context 'a required has_and_belongs_to_many association' do + context 'a has_and_belongs_to_many association with a presence validation on it' do it 'accepts' do expect(build_record_having_and_belonging_to_many). to validate_presence_of(:children) @@ -228,7 +230,7 @@ could not be proved. end end - context 'an optional has_and_belongs_to_many association' do + context 'a has_and_belongs_to_many association without a presence validation on it' do before do define_model :child @model = define_model :parent do @@ -256,6 +258,300 @@ this could not be proved. end end + context 'against a belongs_to association' do + if active_record_supports_optional_for_associations? + context 'declared with optional: true' do + context 'and an explicit presence validation is on the association' do + it 'matches' do + record = record_belonging_to( + :parent, + optional: true, + validate_presence: true, + ) + + expect { validate_presence_of(:parent) }.to match_against(record) + end + end + + context 'and an explicit presence validation is not on the association' do + it 'does not match' do + record = record_belonging_to( + :parent, + optional: true, + validate_presence: false, + model_name: 'Child', + parent_model_name: 'Parent', + ) + + expect { validate_presence_of(:parent) }. + not_to match_against(record). + and_fail_with(<<-MESSAGE) +Expected Child to validate that :parent cannot be empty/falsy, but this +could not be proved. + After setting :parent to ‹nil›, the matcher expected the Child to be + invalid, but it was valid instead. + MESSAGE + end + end + end + + context 'declared with optional: false' do + context 'and an explicit presence validation is on the association' do + it 'matches' do + record = record_belonging_to( + :parent, + optional: false, + validate_presence: true, + ) + + expect { validate_presence_of(:parent) }.to match_against(record) + end + end + + context 'and an explicit presence validation is not on the association' do + it 'does not match, instructing the user to use belong_to instead' do + record = record_belonging_to( + :parent, + optional: false, + validate_presence: false, + model_name: 'Child', + parent_model_name: 'Parent', + ) + + expect { validate_presence_of(:parent) }. + not_to match_against(record). + and_fail_with(<<-MESSAGE) +Expected Child to validate that :parent cannot be empty/falsy, but this +could not be proved. + After setting :parent to ‹nil›, the matcher expected the Child to be + invalid and to produce the validation error "can't be blank" on + :parent. The record was indeed invalid, but it produced these + validation errors instead: + + * parent: ["must exist"] + + You're getting this error because you've instructed your `belongs_to` + association to add a presence validation to the attribute. *This* + presence validation doesn't use "can't be blank", the usual validation + message, but "must exist" instead. + + With that said, did you know that the `belong_to` matcher can test + this validation for you? Instead of using `validate_presence_of`, try + the following instead: + + it { should belong_to(:parent).optional(false) } + MESSAGE + end + end + end + + context 'not declared with an optional or required option' do + context 'when belongs_to is configured to be required by default' do + context 'and an explicit presence validation is on the association' do + it 'matches' do + with_belongs_to_as_required_by_default do + record = record_belonging_to( + :parent, + validate_presence: true, + ) + + expect { validate_presence_of(:parent) }. + to match_against(record) + end + end + end + + context 'and an explicit presence validation is not on the association' do + it 'does not match, instructing the user to use belong_to instead' do + with_belongs_to_as_required_by_default do + record = record_belonging_to( + :parent, + validate_presence: false, + model_name: 'Child', + parent_model_name: 'Parent', + ) + + expect { validate_presence_of(:parent) }. + not_to match_against(record). + and_fail_with(<<-MESSAGE) +Expected Child to validate that :parent cannot be empty/falsy, but this +could not be proved. + After setting :parent to ‹nil›, the matcher expected the Child to be + invalid and to produce the validation error "can't be blank" on + :parent. The record was indeed invalid, but it produced these + validation errors instead: + + * parent: ["must exist"] + + You're getting this error because ActiveRecord is configured to add a + presence validation to all `belongs_to` associations, and this + includes yours. *This* presence validation doesn't use "can't be + blank", the usual validation message, but "must exist" instead. + + With that said, did you know that the `belong_to` matcher can test + this validation for you? Instead of using `validate_presence_of`, try + the following instead: + + it { should belong_to(:parent) } + MESSAGE + end + end + end + end + + context 'when belongs_to is configured to be optional by default' do + context 'and an explicit presence validation is on the association' do + it 'matches' do + with_belongs_to_as_optional_by_default do + record = record_belonging_to( + :parent, + validate_presence: true, + ) + + expect { validate_presence_of(:parent) }. + to match_against(record) + end + end + end + + context 'and an explicit presence validation is not on the association' do + it 'does not match' do + with_belongs_to_as_optional_by_default do + record = record_belonging_to( + :parent, + validate_presence: false, + model_name: 'Child', + parent_model_name: 'Parent', + ) + + expect { validate_presence_of(:parent) }. + not_to match_against(record). + and_fail_with(<<-MESSAGE) +Expected Child to validate that :parent cannot be empty/falsy, but this +could not be proved. + After setting :parent to ‹nil›, the matcher expected the Child to be + invalid, but it was valid instead. + MESSAGE + end + end + end + end + end + else + context 'declared with required: true' do + context 'and an explicit presence validation is on the association' do + it 'matches' do + record = record_belonging_to( + :parent, + required: true, + validate_presence: true, + ) + + expect { validate_presence_of(:parent) }.to match_against(record) + end + end + + context 'and an explicit presence validation is not on the association' do + it 'still matches' do + record = record_belonging_to( + :parent, + required: true, + validate_presence: false, + ) + + expect { validate_presence_of(:parent) }.to match_against(record) + end + end + end + + context 'declared with required: false' do + context 'and an explicit presence validation is on the association' do + it 'matches' do + record = record_belonging_to( + :parent, + required: false, + validate_presence: true, + ) + + expect { validate_presence_of(:parent) }.to match_against(record) + end + end + + context 'and an explicit presence validation is not on the association' do + it 'does not match' do + record = record_belonging_to( + :parent, + required: false, + validate_presence: false, + model_name: 'Child', + parent_model_name: 'Parent', + ) + + expect { validate_presence_of(:parent) }. + not_to match_against(record). + and_fail_with(<<-MESSAGE) +Expected Child to validate that :parent cannot be empty/falsy, but this +could not be proved. + After setting :parent to ‹nil›, the matcher expected the Child to be + invalid, but it was valid instead. + MESSAGE + end + end + end + + context 'not declared with a required option' do + context 'and an explicit presence validation is on the association' do + it 'matches' do + record = record_belonging_to(:parent, validate_presence: true) + + expect { validate_presence_of(:parent) }.to match_against(record) + end + end + + context 'and an explicit presence validation is not on the association' do + it 'does not match' do + record = record_belonging_to(:parent, validate_presence: false) + + expect { validate_presence_of(:parent) }. + not_to match_against(record). + and_fail_with(<<-MESSAGE) +Expected Child to validate that :parent cannot be empty/falsy, but this +could not be proved. + After setting :parent to ‹nil›, the matcher expected the Child to be + invalid, but it was valid instead. + MESSAGE + end + end + end + end + + def record_belonging_to( + attribute_name, + model_name: 'Child', + parent_model_name: 'Parent', + column_name: "#{attribute_name}_id", + validate_presence: false, + **association_options, + &block + ) + define_model(parent_model_name) + + child_model = define_model(model_name, column_name => :integer) do + belongs_to(attribute_name, **association_options) + + if validate_presence + validates_presence_of(attribute_name) + end + + if block + instance_eval(&block) + end + end + + child_model.new + end + end + context "an i18n translation containing %{attribute} and %{model}" do before do stub_translation( 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 f13368c8..3ddf2fca 100644 --- a/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb @@ -1,6 +1,8 @@ require 'unit_spec_helper' describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do + include UnitTests::ApplicationConfigurationHelpers + context 'belong_to' do it 'accepts a good association with the default foreign key' do expect(belonging_to_parent).to belong_to(:parent) @@ -284,7 +286,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do if active_record_supports_optional_for_associations? context 'when belongs_to is configured to be required by default' do it 'passes' do - configuring_default_belongs_to_requiredness(true) do + with_belongs_to_as_required_by_default do expect(belonging_to_parent).to belong_to(:parent).required(true) end end @@ -292,7 +294,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do context 'when belongs_to is not configured to be required by default' do it 'fails with an appropriate message' do - configuring_default_belongs_to_requiredness(false) do + with_belongs_to_as_optional_by_default do assertion = lambda do expect(belonging_to_parent). to belong_to(:parent).required(true) @@ -333,7 +335,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do if active_record_supports_optional_for_associations? context 'when belongs_to is configured to be required by default' do it 'fails with an appropriate message' do - configuring_default_belongs_to_requiredness(true) do + with_belongs_to_as_required_by_default do assertion = lambda do expect(belonging_to_parent). to belong_to(:parent).required(false) @@ -354,7 +356,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do context 'when belongs_to is not configured to be required by default' do it 'passes' do - configuring_default_belongs_to_requiredness(false) do + with_belongs_to_as_optional_by_default do expect(belonging_to_parent).to belong_to(:parent).required(false) end end @@ -370,7 +372,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do if active_record_supports_optional_for_associations? context 'when belongs_to is configured to be required by default' do it 'fails with an appropriate message' do - configuring_default_belongs_to_requiredness(true) do + with_belongs_to_as_required_by_default do assertion = lambda do expect(belonging_to_parent). to belong_to(:parent).optional @@ -391,7 +393,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do context 'when belongs_to is not configured to be required by default' do it 'passes' do - configuring_default_belongs_to_requiredness(false) do + with_belongs_to_as_optional_by_default do expect(belonging_to_parent).to belong_to(:parent).optional end end @@ -407,7 +409,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do if active_record_supports_optional_for_associations? context 'when belongs_to is configured to be required by default' do it 'passes' do - configuring_default_belongs_to_requiredness(true) do + with_belongs_to_as_required_by_default do expect(belonging_to_parent).to belong_to(:parent) end end @@ -415,14 +417,14 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do context 'when belongs_to is not configured to be required by default' do it 'passes' do - configuring_default_belongs_to_requiredness(false) do + with_belongs_to_as_optional_by_default do expect(belonging_to_parent).to belong_to(:parent) end end context 'and a presence validation is on the attribute instead of using required: true' do it 'passes' do - configuring_default_belongs_to_requiredness(false) do + with_belongs_to_as_optional_by_default do record = belonging_to_parent do validates_presence_of :parent end @@ -435,7 +437,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do context 'and a presence validation is on the attribute with a condition' do context 'and the condition is true' do it 'passes' do - configuring_default_belongs_to_requiredness(false) do + with_belongs_to_as_optional_by_default do child_model = create_child_model_belonging_to_parent do attr_accessor :condition validates_presence_of :parent, if: :condition @@ -450,7 +452,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do context 'and the condition is false' do it 'passes' do - configuring_default_belongs_to_requiredness(false) do + with_belongs_to_as_optional_by_default do child_model = create_child_model_belonging_to_parent do attr_accessor :condition validates_presence_of :parent, if: :condition @@ -630,7 +632,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do end assertion = lambda do - configuring_default_belongs_to_requiredness(true) do + with_belongs_to_as_required_by_default do expect(model.new).to belong_to(:parent) end end @@ -656,7 +658,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do end end - configuring_default_belongs_to_requiredness(true) do + with_belongs_to_as_required_by_default do expect(model.new). to belong_to(:parent). without_validating_presence @@ -677,7 +679,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do end assertion = lambda do - configuring_default_belongs_to_requiredness(true) do + with_belongs_to_as_required_by_default do expect(model.new).to belong_to(:parent).required end end @@ -703,7 +705,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do end end - configuring_default_belongs_to_requiredness(true) do + with_belongs_to_as_required_by_default do expect(model.new). to belong_to(:parent). required. @@ -1765,21 +1767,4 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do [:destroy, :delete, :nullify, :restrict] end end - - def configuring_default_belongs_to_requiredness(value, &block) - configuring_application( - ActiveRecord::Base, - :belongs_to_required_by_default, - value, - &block - ) - end - - def configuring_application(config, name, value) - previous_value = config.send(name) - config.send("#{name}=", value) - yield - ensure - config.send("#{name}=", previous_value) - end end