From 38f79d5e97b12723aedd5e9fd125678f9ec0907d Mon Sep 17 00:00:00 2001 From: vaot Date: Wed, 12 Jun 2024 11:22:12 -0700 Subject: [PATCH] feat: Add without_instance_methods qualifier to enum matcher --- .../active_record/define_enum_for_matcher.rb | 52 ++++++++++-- .../define_enum_for_matcher_spec.rb | 80 ++++++++++++++++++- 2 files changed, 123 insertions(+), 9 deletions(-) diff --git a/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb b/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb index 8b583a4e3..a621c7284 100644 --- a/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +++ b/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb @@ -253,8 +253,29 @@ module ActiveRecord # validating(allowing_nil: true) # end # - # @return [DefineEnumForMatcher] + # ##### without_instance_methods + # + # Use `without_instance_methods` to exclude the check for instance methods. + # + # class Issue < ActiveRecord::Base + # enum status: [:open, :closed], instance_methods: false + # end + # + # # RSpec + # RSpec.describe Issue, type: :model do + # it do + # should define_enum_for(:status). + # without_instance_methods + # end + # end # + # # Minitest (Shoulda) + # class ProcessTest < ActiveSupport::TestCase + # should define_enum_for(:status). + # without_instance_methods + # end + # + # @return [DefineEnumForMatcher] def define_enum_for(attribute_name) DefineEnumForMatcher.new(attribute_name) end @@ -263,7 +284,7 @@ def define_enum_for(attribute_name) class DefineEnumForMatcher def initialize(attribute_name) @attribute_name = attribute_name - @options = { expected_enum_values: [], scopes: true } + @options = { expected_enum_values: [], scopes: true, instance_methods: true } end def description @@ -319,6 +340,11 @@ def without_scopes self end + def without_instance_methods + options[:instance_methods] = false + self + end + def with_default(default_value) options[:default] = default_value self @@ -531,16 +557,24 @@ def model end def enum_value_methods_exist? - if instance_methods_exist? - true - else + if options[:instance_methods] + return true if instance_methods_exist? + message = missing_methods_message + message << " (we can't tell which)" if [expected_prefix, expected_suffix].any? - message << " (we can't tell which)" + @failure_message_continuation = message + + false + elsif instance_methods_exist? + message = "#{attribute_name.inspect} does map to these values" + message << ' with instance methods, but expected no instance methods' @failure_message_continuation = message false + else + true end end @@ -589,6 +623,8 @@ def missing_methods_message elsif expected_suffix message << 'configured with either a different suffix or no ' message << 'suffix at all' + elsif expected_intance_methods + message << 'configured with no instance methods' else '' end @@ -664,6 +700,10 @@ def expected_instance_methods end end + def expected_intance_methods + options[:instance_methods] + end + def expected_prefix if options.include?(:prefix) if options[:prefix] == true diff --git a/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb index 06093c244..faf5428b5 100644 --- a/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb @@ -1114,6 +1114,77 @@ def self.statuses end end end + + describe 'qualified with #without_instance_methods' do + context 'if instance methods are set to false on the enum but without_instance_methods is not used' do + it 'rejects with failure message' do + record = build_record_with_array_values( + attribute_name: :attr, + instance_methods: false, + ) + + matcher = lambda do + expect(record). + to define_enum_for(:attr). + with_values(['published', 'unpublished', 'draft']) + end + + message = format_message(<<-MESSAGE) + Expected Example to define :attr as an enum backed by an integer, + mapping ‹"published"› to ‹0›, ‹"unpublished"› to ‹1›, and ‹"draft"› to + ‹2›. :attr does map to these values, but the enum is configured with no + instance methods. + MESSAGE + + expect(&matcher).to fail_with_message(message) + end + end + + context 'if instance methods are set to false on the enum' do + it 'matches' do + record = build_record_with_array_values( + attribute_name: :attr, + instance_methods: false, + ) + + matcher = lambda do + define_enum_for(:attr). + with_values(['published', 'unpublished', 'draft']). + without_instance_methods + end + + expect(&matcher). + to match_against(record). + or_fail_with(<<-MESSAGE) + Expected Example not to define :attr as an enum backed by an integer, + mapping ‹"published"› to ‹0›, ‹"unpublished"› to ‹1›, and ‹"draft"› to + ‹2›, but it did. + MESSAGE + end + end + + context 'if instance methods are not set to false on the enum' do + it 'rejects with failure message' do + record = build_record_with_array_values(attribute_name: :attr) + + matcher = lambda do + expect(record). + to define_enum_for(:attr). + with_values(['published', 'unpublished', 'draft']). + without_instance_methods + end + + message = format_message(<<-MESSAGE) + Expected Example to define :attr as an enum backed by an integer, + mapping ‹"published"› to ‹0›, ‹"unpublished"› to ‹1›, and ‹"draft"› to + ‹2›. :attr does map to these values with instance methods, but expected + no instance methods. + MESSAGE + + expect(&matcher).to fail_with_message(message) + end + end + end end if rails_version =~ '~> 6.0' @@ -1198,7 +1269,8 @@ def build_record_with_array_values( attribute_alias: nil, scopes: true, default: nil, - validate: false + validate: false, + instance_methods: true ) build_record_with_enum_attribute( model_name: model_name, @@ -1211,6 +1283,7 @@ def build_record_with_array_values( scopes: scopes, default: default, validate: validate, + instance_methods: instance_methods, ) end @@ -1244,7 +1317,8 @@ def build_record_with_enum_attribute( prefix: false, suffix: false, default: nil, - validate: false + validate: false, + instance_methods: true ) enum_name = attribute_alias || attribute_name model = define_model( @@ -1262,7 +1336,7 @@ def build_record_with_enum_attribute( } if rails_version >= 7.0 - model.enum(enum_name, values, prefix: prefix, suffix: suffix, validate: validate, default: default) + model.enum(enum_name, values, prefix: prefix, suffix: suffix, validate: validate, default: default, instance_methods: instance_methods) else params.merge!(_scopes: scopes) model.enum(params)