Add with_prefix and with_suffix to define_enum_for

In Rails 5, the `enum` macro received two new options: `_prefix` and
`_suffix`. These options change the names of the methods that are
generated from the given enum values. This commit adds qualifiers to the
`define_enum_for` matcher so that they can be tested.
This commit is contained in:
Elliot Winkler 2018-01-26 01:12:57 -06:00
parent d8681fa721
commit 147ac482e8
3 changed files with 465 additions and 6 deletions

View File

@ -80,6 +80,55 @@ module Shoulda
# backed_by_column_of_type(:string)
# end
#
## ##### with_prefix
#
# Use `with_prefix` to test that the enum is defined with a `_prefix`
# option (Rails 5 only). Can take either a boolean or a symbol:
#
# class Issue < ActiveRecord::Base
# enum status: [:open, :closed], _prefix: :old
# end
#
# # RSpec
# RSpec.describe Issue, type: :model do
# it do
# should define_enum_for(:status).
# with_values([:open, :closed]).
# with_prefix(:old)
# end
# end
#
# # Minitest (Shoulda)
# class ProcessTest < ActiveSupport::TestCase
# should define_enum_for(:status).
# with_values([:open, :closed]).
# with_prefix(:old)
# end
#
# ##### with_suffix
#
# Use `with_suffix` to test that the enum is defined with a `_suffix`
# option (Rails 5 only). Can take either a boolean or a symbol:
#
# class Issue < ActiveRecord::Base
# enum status: [:open, :closed], _suffix: true
# end
#
# # RSpec
# RSpec.describe Issue, type: :model do
# it do
# should define_enum_for(:status).
# with_values([:open, :closed]).
# with_suffix
# end
# end
#
# # Minitest (Shoulda)
# class ProcessTest < ActiveSupport::TestCase
# should define_enum_for(:status).
# with_values([:open, :closed]).
# with_suffix
# end
#
# @return [DefineEnumForMatcher]
#
@ -98,6 +147,23 @@ module Shoulda
description = "define :#{attribute_name} as an enum, backed by "
description << Shoulda::Matchers::Util.a_or_an(expected_column_type)
if options[:expected_prefix]
description << ', using a prefix of '
description << "#{options[:expected_prefix].inspect}"
end
if options[:expected_suffix]
if options[:expected_prefix]
description << ' and'
else
description << ', using'
end
description << ' a suffix of '
description << "#{options[:expected_suffix].inspect}"
end
if presented_expected_enum_values.any?
description << ', with possible values '
description << Shoulda::Matchers::Util.inspect_value(
@ -121,6 +187,16 @@ module Shoulda
with_values(expected_enum_values)
end
def with_prefix(expected_prefix = attribute_name)
options[:expected_prefix] = expected_prefix
self
end
def with_suffix(expected_suffix = attribute_name)
options[:expected_suffix] = expected_suffix
self
end
def backed_by_column_of_type(expected_column_type)
options[:expected_column_type] = expected_column_type
self
@ -128,7 +204,11 @@ module Shoulda
def matches?(subject)
@record = subject
enum_defined? && enum_values_match? && column_type_matches?
enum_defined? &&
enum_values_match? &&
column_type_matches? &&
enum_value_methods_exist?
end
def failure_message
@ -168,6 +248,10 @@ module Shoulda
to_hash(expected_enum_values)
end
def expected_enum_value_names
to_array(expected_enum_values)
end
def expected_enum_values
options[:expected_enum_values]
end
@ -238,6 +322,38 @@ module Shoulda
record.class
end
def enum_value_methods_exist?
passed = expected_singleton_methods.all? do |method|
model.singleton_methods.include?(method)
end
if passed
true
else
@failure_reason =
if options[:expected_prefix]
if options[:expected_suffix]
'it was defined with either a different prefix, a ' +
'different suffix, or neither one at all'
else
'it was defined with either a different prefix or none at all'
end
elsif options[:expected_suffix]
'it was defined with either a different suffix or none at all'
end
false
end
end
def expected_singleton_methods
expected_enum_value_names.map do |name|
[options[:expected_prefix], name, options[:expected_suffix]].
select(&:present?).
join('_').
to_sym
end
end
def to_hash(value)
if value.is_a?(Array)
value.each_with_index.inject({}) do |hash, (item, index)|

View File

@ -9,6 +9,10 @@ module UnitTests
Tests::Version.new(::ActiveRecord::VERSION::STRING)
end
def active_record_enum_supports_prefix_and_suffix?
active_record_version >= 5
end
def active_record_supports_has_secure_password?
active_record_version >= 3.1
end

View File

@ -304,30 +304,361 @@ describe Shoulda::Matchers::ActiveRecord::DefineEnumForMatcher, type: :model do
end
end
if active_record_enum_supports_prefix_and_suffix?
context 'qualified with #with_prefix' do
context 'when the prefix is explicit' do
context 'if the attribute was not defined with a prefix' do
it 'rejects with an appropriate failure message' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: [:active, :archived],
)
assertion = lambda do
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_prefix(:foo)
end
message = format_message(<<-MESSAGE)
Expected Example to define :attr as an enum, backed by an integer,
using a prefix of :foo, with possible values [:active,
:archived]. However, it was defined with either a different
prefix or none at all.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
context 'if the attribute was defined with a different prefix' do
it 'rejects with an appropriate failure message' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: [:active, :archived],
prefix: :foo,
)
assertion = lambda do
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_prefix(:bar)
end
message = format_message(<<-MESSAGE)
Expected Example to define :attr as an enum, backed by an integer,
using a prefix of :bar, with possible values [:active,
:archived]. However, it was defined with either a different
prefix or none at all.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
context 'if the attribute was defined with the same prefix' do
it 'accepts' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
values: [:active, :archived],
prefix: :foo,
)
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_prefix(:foo)
end
end
end
context 'when the prefix is implicit' do
context 'if the attribute was not defined with a prefix' do
it 'rejects with an appropriate failure message' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: [:active, :archived],
)
assertion = lambda do
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_prefix
end
message = format_message(<<-MESSAGE)
Expected Example to define :attr as an enum, backed by an integer,
using a prefix of :attr, with possible values [:active,
:archived]. However, it was defined with either a different
prefix or none at all.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
context 'if the attribute was defined with a prefix' do
it 'accepts' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
values: [:active, :archived],
prefix: true,
)
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_prefix
end
end
end
end
context 'qualified with #with_suffix' do
context 'when the suffix is explicit' do
context 'if the attribute was not defined with a suffix' do
it 'rejects with an appropriate failure message' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: [:active, :archived],
)
assertion = lambda do
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_suffix(:foo)
end
message = format_message(<<-MESSAGE)
Expected Example to define :attr as an enum, backed by an integer,
using a suffix of :foo, with possible values [:active,
:archived]. However, it was defined with either a different
suffix or none at all.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
context 'if the attribute was defined with a different suffix' do
it 'rejects with an appropriate failure message' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: [:active, :archived],
suffix: :foo,
)
assertion = lambda do
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_suffix(:bar)
end
message = format_message(<<-MESSAGE)
Expected Example to define :attr as an enum, backed by an integer,
using a suffix of :bar, with possible values [:active,
:archived]. However, it was defined with either a different
suffix or none at all.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
context 'if the attribute was defined with the same suffix' do
it 'accepts' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
values: [:active, :archived],
suffix: :foo,
)
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_suffix(:foo)
end
end
end
context 'when the suffix is implicit' do
context 'if the attribute was not defined with a suffix' do
it 'rejects with an appropriate failure message' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: [:active, :archived],
)
assertion = lambda do
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_suffix
end
message = format_message(<<-MESSAGE)
Expected Example to define :attr as an enum, backed by an integer,
using a suffix of :attr, with possible values [:active,
:archived]. However, it was defined with either a different
suffix or none at all.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
context 'if the attribute was defined with a suffix' do
it 'accepts' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
values: [:active, :archived],
suffix: true,
)
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_suffix
end
end
end
end
context 'qualified with both #with_prefix and #with_suffix' do
context 'if the attribute was not defined with a different prefix' do
it 'rejects with an appropriate failure message' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: [:active, :archived],
prefix: :foo,
suffix: :bar,
)
assertion = lambda do
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_prefix(:whatever).
with_suffix(:bar)
end
message = format_message(<<-MESSAGE)
Expected Example to define :attr as an enum, backed by an integer,
using a prefix of :whatever and a suffix of :bar, with possible
values [:active, :archived]. However, it was defined with either
a different prefix, a different suffix, or neither one at all.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
context 'if the attribute was defined with a different suffix' do
it 'rejects with an appropriate failure message' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: [:active, :archived],
prefix: :foo,
suffix: :bar,
)
assertion = lambda do
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_prefix(:foo).
with_suffix(:whatever)
end
message = format_message(<<-MESSAGE)
Expected Example to define :attr as an enum, backed by an integer,
using a prefix of :foo and a suffix of :whatever, with possible
values [:active, :archived]. However, it was defined with
either a different prefix, a different suffix, or neither one at
all.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
context 'if the attribute was defined with the same prefix and suffix' do
it 'accepts' do
record = build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
values: [:active, :archived],
prefix: :foo,
suffix: :bar,
)
expect(record).
to define_enum_for(:attr).
with_values([:active, :archived]).
with_prefix(:foo).
with_suffix(:bar)
end
end
end
end
end
def build_record_with_array_values(
model_name: 'Example',
attribute_name: :attr,
column_type: :integer,
values: ['published', 'unpublished', 'draft']
values: ['published', 'unpublished', 'draft'],
prefix: false,
suffix: false
)
build_record_with_enum_attribute(
model_name: model_name,
attribute_name: attribute_name,
column_type: column_type,
values: values,
prefix: prefix,
suffix: suffix,
)
end
def build_record_with_hash_values(
model_name: 'Example',
attribute_name: :attr,
values: { active: 0, archived: 1 }
values: { active: 0, archived: 1 },
prefix: false,
suffix: false
)
build_record_with_enum_attribute(
model_name: model_name,
attribute_name: attribute_name,
column_type: :integer,
values: values,
prefix: prefix,
suffix: suffix,
)
end
@ -335,13 +666,21 @@ describe Shoulda::Matchers::ActiveRecord::DefineEnumForMatcher, type: :model do
model_name:,
attribute_name:,
column_type:,
values:
values:,
prefix: false,
suffix: false
)
model = define_model(
model_name,
attribute_name => column_type,
attribute_name => { type: column_type },
)
model.enum(attribute_name => values)
if active_record_enum_supports_prefix_and_suffix?
model.enum(attribute_name => values, _prefix: prefix, _suffix: suffix)
else
model.enum(attribute_name => values)
end
model.new
end