-
-
Notifications
You must be signed in to change notification settings - Fork 909
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
Add prefix and suffix to define enum for #1077
Changes from all commits
6bd3fcf
b1e11fb
a05ad14
d8681fa
147ac48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,10 +20,10 @@ module ActiveRecord | |
# | ||
# #### Qualifiers | ||
# | ||
# ##### with | ||
# ##### with_values | ||
# | ||
# Use `with` to test that the attribute has been defined with a certain | ||
# set of known values. | ||
# Use `with_values` to test that the attribute has been defined with a | ||
# certain set of possible values. | ||
# | ||
# class Process < ActiveRecord::Base | ||
# enum status: [:running, :stopped, :suspended] | ||
|
@@ -33,14 +33,14 @@ module ActiveRecord | |
# RSpec.describe Process, type: :model do | ||
# it do | ||
# should define_enum_for(:status). | ||
# with([:running, :stopped, :suspended]) | ||
# with_values([:running, :stopped, :suspended]) | ||
# end | ||
# end | ||
# | ||
# # Minitest (Shoulda) | ||
# class ProcessTest < ActiveSupport::TestCase | ||
# should define_enum_for(:status). | ||
# with([:running, :stopped, :suspended]) | ||
# with_values([:running, :stopped, :suspended]) | ||
# end | ||
# | ||
# ##### backed_by_column_of_type | ||
|
@@ -60,7 +60,7 @@ module ActiveRecord | |
# RSpec.describe LoanApplication, type: :model do | ||
# it do | ||
# should define_enum_for(:status). | ||
# with( | ||
# with_values( | ||
# active: "active", | ||
# pending: "pending", | ||
# rejected: "rejected" | ||
|
@@ -72,14 +72,63 @@ module ActiveRecord | |
# # Minitest (Shoulda) | ||
# class LoanApplicationTest < ActiveSupport::TestCase | ||
# should define_enum_for(:status). | ||
# with( | ||
# with_values( | ||
# active: "active", | ||
# pending: "pending", | ||
# rejected: "rejected" | ||
# ). | ||
# 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] | ||
# | ||
|
@@ -91,76 +140,178 @@ def define_enum_for(attribute_name) | |
class DefineEnumForMatcher | ||
def initialize(attribute_name) | ||
@attribute_name = attribute_name | ||
@options = {} | ||
@options = { expected_enum_values: [] } | ||
end | ||
|
||
def with(expected_enum_values) | ||
def description | ||
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}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer to_s over string interpolation. |
||
end | ||
|
||
if options[:expected_suffix] | ||
if options[:expected_prefix] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the return of the conditional for variable assignment and comparison. |
||
description << ' and' | ||
else | ||
description << ', using' | ||
end | ||
|
||
description << ' a suffix of ' | ||
|
||
description << "#{options[:expected_suffix].inspect}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer to_s over string interpolation. |
||
end | ||
|
||
if presented_expected_enum_values.any? | ||
description << ', with possible values ' | ||
description << Shoulda::Matchers::Util.inspect_value( | ||
presented_expected_enum_values, | ||
) | ||
end | ||
|
||
description | ||
end | ||
|
||
def with_values(expected_enum_values) | ||
options[:expected_enum_values] = expected_enum_values | ||
self | ||
end | ||
|
||
def with(expected_enum_values) | ||
Shoulda::Matchers.warn_about_deprecated_method( | ||
'The `with` qualifier on `define_enum_for`', | ||
'`with_values`', | ||
) | ||
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 | ||
end | ||
|
||
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 | ||
"Expected #{expectation}" | ||
message = "Expected #{model} to #{expectation}" | ||
|
||
if failure_reason | ||
message << ". However, #{failure_reason}" | ||
end | ||
|
||
message << '.' | ||
|
||
Shoulda::Matchers.word_wrap(message) | ||
end | ||
alias :failure_message_for_should :failure_message | ||
|
||
def failure_message_when_negated | ||
"Did not expect #{expectation}" | ||
message = "Expected #{model} not to #{expectation}, but it did." | ||
Shoulda::Matchers.word_wrap(message) | ||
end | ||
alias :failure_message_for_should_not :failure_message_when_negated | ||
|
||
def description | ||
desc = "define :#{attribute_name} as an enum" | ||
|
||
if options[:expected_enum_values] | ||
desc << " with #{options[:expected_enum_values]}" | ||
end | ||
private | ||
|
||
desc << " and store the value in a column of type #{expected_column_type}" | ||
attr_reader :attribute_name, :options, :record, :failure_reason | ||
|
||
desc | ||
def expectation | ||
description | ||
end | ||
|
||
protected | ||
def presented_expected_enum_values | ||
if expected_enum_values.is_a?(Hash) | ||
expected_enum_values.symbolize_keys | ||
else | ||
expected_enum_values | ||
end | ||
end | ||
|
||
attr_reader :record, :attribute_name, :options | ||
def normalized_expected_enum_values | ||
to_hash(expected_enum_values) | ||
end | ||
|
||
def expectation | ||
"#{model.name} to #{description}" | ||
def expected_enum_value_names | ||
to_array(expected_enum_values) | ||
end | ||
|
||
def expected_enum_values | ||
hashify(options[:expected_enum_values]).with_indifferent_access | ||
options[:expected_enum_values] | ||
end | ||
|
||
def presented_actual_enum_values | ||
if expected_enum_values.is_a?(Array) | ||
to_array(actual_enum_values) | ||
else | ||
to_hash(actual_enum_values).symbolize_keys | ||
end | ||
end | ||
|
||
def normalized_actual_enum_values | ||
to_hash(actual_enum_values) | ||
end | ||
|
||
def actual_enum_values | ||
model.send(attribute_name.to_s.pluralize) | ||
end | ||
|
||
def enum_defined? | ||
model.defined_enums.include?(attribute_name.to_s) | ||
if model.defined_enums.include?(attribute_name.to_s) | ||
true | ||
else | ||
@failure_reason = "no such enum exists in #{model}" | ||
false | ||
end | ||
end | ||
|
||
def enum_values_match? | ||
expected_enum_values.empty? || actual_enum_values == expected_enum_values | ||
end | ||
passed = | ||
expected_enum_values.empty? || | ||
normalized_actual_enum_values == normalized_expected_enum_values | ||
|
||
def expected_column_type | ||
options[:expected_column_type] || :integer | ||
if passed | ||
true | ||
else | ||
@failure_reason = | ||
"the actual enum values for #{attribute_name.inspect} are " + | ||
Shoulda::Matchers::Util.inspect_value( | ||
presented_actual_enum_values, | ||
) | ||
false | ||
end | ||
end | ||
|
||
def column_type_matches? | ||
column.type == expected_column_type.to_sym | ||
if column.type == expected_column_type.to_sym | ||
true | ||
else | ||
@failure_reason = | ||
"#{attribute_name.inspect} is " + | ||
Shoulda::Matchers::Util.a_or_an(column.type) + | ||
' column' | ||
false | ||
end | ||
end | ||
|
||
def expected_column_type | ||
options[:expected_column_type] || :integer | ||
end | ||
|
||
def column | ||
|
@@ -171,21 +322,53 @@ def model | |
record.class | ||
end | ||
|
||
def hashify(value) | ||
if value.nil? | ||
return {} | ||
def enum_value_methods_exist? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method has too many lines. [19/10] |
||
passed = expected_singleton_methods.all? do |method| | ||
model.singleton_methods.include?(method) | ||
end | ||
|
||
if value.is_a?(Array) | ||
new_value = {} | ||
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' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use 2 (not 0) spaces for indenting an expression spanning multiple lines. |
||
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 | ||
|
||
value.each_with_index do |v, i| | ||
new_value[v] = i | ||
def to_hash(value) | ||
if value.is_a?(Array) | ||
value.each_with_index.inject({}) do |hash, (item, index)| | ||
hash.merge(item.to_s => index) | ||
end | ||
else | ||
value.stringify_keys | ||
end | ||
end | ||
|
||
new_value | ||
def to_array(value) | ||
if value.is_a?(Array) | ||
value.map(&:to_s) | ||
else | ||
value | ||
value.keys.map(&:to_s) | ||
end | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assignment Branch Condition size for description is too high. [28.3/15]
Method has too many lines. [22/10]