Skip to content

Commit

Permalink
feat: Add without_instance_methods qualifier to enum matcher (#1636)
Browse files Browse the repository at this point in the history
* feat: Add without_instance_methods qualifier to enum matcher

* Fix type and name collision
  • Loading branch information
vaot authored Jul 26, 2024
1 parent ed98614 commit 121590b
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 9 deletions.
52 changes: 46 additions & 6 deletions lib/shoulda/matchers/active_record/define_enum_for_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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_instance_methods?
message << 'configured with no instance methods'
else
''
end
Expand Down Expand Up @@ -664,6 +700,10 @@ def expected_instance_methods
end
end

def expected_instance_methods?
options[:instance_methods]
end

def expected_prefix
if options.include?(:prefix)
if options[:prefix] == true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Expand All @@ -1211,6 +1283,7 @@ def build_record_with_array_values(
scopes: scopes,
default: default,
validate: validate,
instance_methods: instance_methods,
)
end

Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand Down

0 comments on commit 121590b

Please sign in to comment.