Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add without_instance_methods qualifier to enum matcher #1636

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_intance_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_intance_methods
options[:instance_methods]
end
vaot marked this conversation as resolved.
Show resolved Hide resolved

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
Loading