class Shoulda::Matchers::ActiveRecord::DefineEnumForMatcher

@private

Attributes

attribute_name[R]
failure_message_continuation[R]
options[R]
record[R]

Public Class Methods

new(attribute_name) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 285
def initialize(attribute_name)
  @attribute_name = attribute_name
  @options = { expected_enum_values: [], scopes: true, instance_methods: true }
end

Public Instance Methods

backed_by_column_of_type(expected_column_type) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 333
def backed_by_column_of_type(expected_column_type)
  options[:expected_column_type] = expected_column_type
  self
end
description() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 290
def description
  description = "#{simple_description} backed by "
  description << Shoulda::Matchers::Util.a_or_an(expected_column_type)

  if expected_enum_values.any?
    description << ' with values '
    description << Shoulda::Matchers::Util.inspect_value(
      expected_enum_values,
    )
  end

  if options[:prefix]
    description << ", prefix: #{options[:prefix].inspect}"
  end

  if options[:suffix]
    description << ", suffix: #{options[:suffix].inspect}"
  end

  description
end
failure_message() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 365
def failure_message
  message =
    if enum_defined?
      "Expected #{model} to #{expectation}. "
    else
      "Expected #{model} to #{expectation}, but "
    end

  message << "#{failure_message_continuation}."

  Shoulda::Matchers.word_wrap(message)
end
failure_message_when_negated() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 378
def failure_message_when_negated
  message = "Expected #{model} not to #{expectation}, but it did."
  Shoulda::Matchers.word_wrap(message)
end
matches?(subject) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 353
def matches?(subject)
  @record = subject

  enum_defined? &&
    enum_values_match? &&
    column_type_matches? &&
    enum_value_methods_exist? &&
    scope_presence_matches? &&
    default_value_matches? &&
    validating_matches?
end
validating(value = true, allowing_nil: false) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 312
def validating(value = true, allowing_nil: false)
  options[:validating] = value
  options[:allowing_nil] = allowing_nil
  self
end
with_default(default_value) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 348
def with_default(default_value)
  options[:default] = default_value
  self
end
with_prefix(expected_prefix = true) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 323
def with_prefix(expected_prefix = true)
  options[:prefix] = expected_prefix
  self
end
with_suffix(expected_suffix = true) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 328
def with_suffix(expected_suffix = true)
  options[:suffix] = expected_suffix
  self
end
with_values(expected_enum_values) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 318
def with_values(expected_enum_values)
  options[:expected_enum_values] = expected_enum_values
  self
end
without_instance_methods() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 343
def without_instance_methods
  options[:instance_methods] = false
  self
end
without_scopes() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 338
def without_scopes
  options[:scopes] = false
  self
end

Private Instance Methods

actual_default_value() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 657
def actual_default_value
  attribute_schema = if model.respond_to?(:_default_attributes)
                       model._default_attributes[attribute_name.to_s]
                     else
                       model.attributes_to_define_after_schema_loads[attribute_name.to_s]
                     end

  if Kernel.const_defined?('ActiveModel::Attribute::UserProvidedDefault') &&
     attribute_schema.is_a?(::ActiveModel::Attribute::UserProvidedDefault)
    attribute_schema = attribute_schema.marshal_dump
  end

  value = case attribute_schema
          in [_, { default: default_value } ]
            default_value
          in [_, default_value, *]
            default_value
          in [_, default_value]
            default_value
          end

  value.respond_to?(:call) ? value.call : value
end
actual_enum_values() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 503
def actual_enum_values
  model.send(attribute_name.to_s.pluralize)
end
column() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 548
def column
  key = attribute_name.to_s
  column_name = model.attribute_alias(key) || key

  model.columns_hash[column_name]
end
column_type_matches?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 532
def column_type_matches?
  if column.type == expected_column_type.to_sym
    true
  else
    @failure_message_continuation =
      "However, #{attribute_name.inspect} is "\
      "#{Shoulda::Matchers::Util.a_or_an(column.type)}"\
      ' column'
    false
  end
end
default_value_matches?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 633
def default_value_matches?
  return true if options[:default].blank?

  if actual_default_value.nil?
    @failure_message_continuation = 'However, no default value was set'
    return false
  end

  if actual_default_value == expected_default_value
    true
  else
    String.new.tap do |message|
      message << 'However, the default value is '
      message << Shoulda::Matchers::Util.inspect_value(actual_default_value)
      @failure_message_continuation = message
    end
    false
  end
end
enum_defined?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 507
def enum_defined?
  if model.defined_enums.include?(attribute_name.to_s)
    true
  else
    @failure_message_continuation =
      "no such enum exists on #{model}"
    false
  end
end
enum_value_methods_exist?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 559
def enum_value_methods_exist?
  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?

    @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
enum_values_match?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 517
def enum_values_match?
  passed =
    expected_enum_values.empty? ||
    normalized_actual_enum_values == normalized_expected_enum_values

  if passed
    true
  else
    @failure_message_continuation =
      "However, #{attribute_name.inspect} actually maps " +
      presented_enum_mapping(normalized_actual_enum_values)
    false
  end
end
exclude_scopes?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 754
def exclude_scopes?
  !options[:scopes]
end
expectation() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 412
def expectation # rubocop:disable Metrics/MethodLength
  if enum_defined?
    expectation = "#{simple_description} backed by "
    expectation << Shoulda::Matchers::Util.a_or_an(expected_column_type)

    if expected_enum_values.any?
      expectation << ', mapping '
      expectation << presented_enum_mapping(
        normalized_expected_enum_values,
      )
    end

    if options[:default].present?
      expectation << ', with a default value of '
      expectation << Shoulda::Matchers::Util.inspect_value(expected_default_value)
    end

    if expected_validating?
      expectation << ', and being validated '
      expectation <<
        if expected_allowing_nil?
          'allowing nil values'
        else
          'not allowing nil values'
        end
    end

    if expected_prefix
      expectation <<
        if expected_suffix
          ', '
        else
          ' and '
        end

      expectation << 'prefixing accessor methods with '
      expectation << "#{expected_prefix}_".inspect
    end

    if expected_suffix
      expectation <<
        if expected_prefix
          ', and '
        else
          ' and '
        end

      expectation << 'suffixing accessor methods with '
      expectation << "_#{expected_suffix}".inspect
    end

    if exclude_scopes?
      expectation << ' with no scopes'
    end

    expectation
  else
    simple_description
  end
end
expected_allowing_nil?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 742
def expected_allowing_nil?
  options[:allowing_nil].present?
end
expected_column_type() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 544
def expected_column_type
  options[:expected_column_type] || :integer
end
expected_default_value() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 653
def expected_default_value
  options[:default].respond_to?(:call) ? options[:default].call : options[:default]
end
expected_enum_value_names() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 491
def expected_enum_value_names
  to_array(expected_enum_values)
end
expected_enum_values() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 495
def expected_enum_values
  options[:expected_enum_values]
end
expected_instance_methods() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 702
def expected_instance_methods
  methods = expected_enum_value_names.map do |name|
    [expected_prefix, name, expected_suffix].
      select(&:present?).
      join('_')
  end

  methods.flat_map do |m|
    ["#{m}?".to_sym, "#{m}!".to_sym]
  end
end
expected_instance_methods?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 714
def expected_instance_methods?
  options[:instance_methods]
end
expected_prefix() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 718
def expected_prefix
  if options.include?(:prefix)
    if options[:prefix] == true
      attribute_name
    else
      options[:prefix]
    end
  end
end
expected_singleton_methods() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 693
def expected_singleton_methods
  expected_enum_value_names.map do |name|
    [expected_prefix, name, expected_suffix].
      select(&:present?).
      join('_').
      to_sym
  end
end
expected_suffix() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 728
def expected_suffix
  if options.include?(:suffix)
    if options[:suffix] == true
      attribute_name
    else
      options[:suffix]
    end
  end
end
expected_validating?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 738
def expected_validating?
  options[:validating].present?
end
find_enum_validator() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 746
def find_enum_validator
  record.class.validators.detect do |validator|
    validator.kind == :inclusion &&
      validator.attributes.include?(attribute_name.to_s) &&
      validator.options[:in] == expected_enum_value_names
  end
end
instance_methods_exist?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 687
def instance_methods_exist?
  expected_instance_methods.all? do |method|
    record.methods.include?(method)
  end
end
missing_methods_message() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 611
def missing_methods_message
  message = "#{attribute_name.inspect} does map to these "
  message << 'values, but the enum is '

  if expected_prefix
    if expected_suffix
      message << 'configured with either a different prefix or '
      message << 'suffix, or no prefix or suffix at all'
    else
      message << 'configured with either a different prefix or no '
      message << 'prefix at all'
    end
  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
end
model() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 555
def model
  record.class
end
normalized_actual_enum_values() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 499
def normalized_actual_enum_values
  to_hash(actual_enum_values)
end
normalized_expected_enum_values() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 487
def normalized_expected_enum_values
  to_hash(expected_enum_values)
end
presented_enum_mapping(enum_values) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 477
def presented_enum_mapping(enum_values)
  enum_values.
    map { |output_to_input|
      output_to_input.
        map(&Shoulda::Matchers::Util.method(:inspect_value)).
        join(' to ')
    }.
    to_sentence
end
scope_presence_matches?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 581
def scope_presence_matches?
  if exclude_scopes?
    if singleton_methods_exist?
      message = "#{attribute_name.inspect} does map to these values "
      message << 'but class scope methods were present'

      @failure_message_continuation = message

      false
    else
      true
    end
  elsif singleton_methods_exist?
    true
  else
    if enum_defined?
      message = 'But the class scope methods are not present'
    else
      message = missing_methods_message

      message << 'or the class scope methods are not present'
      message << " (we can't tell which)"
    end

    @failure_message_continuation = message

    false
  end
end
simple_description() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 473
def simple_description
  "define :#{attribute_name} as an enum"
end
singleton_methods_exist?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 681
def singleton_methods_exist?
  expected_singleton_methods.all? do |method|
    model.singleton_methods.include?(method)
  end
end
to_array(value) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 768
def to_array(value)
  if value.is_a?(Array)
    value.map(&:to_s)
  else
    value.keys.map(&:to_s)
  end
end
to_hash(value) click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 758
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
validating_matches?() click to toggle source
# File lib/shoulda/matchers/active_record/define_enum_for_matcher.rb, line 385
def validating_matches?
  return true if options[:validating].nil?

  validator = find_enum_validator

  if expected_validating? == !!validator
    if validator&.options&.dig(:allow_nil).present? == expected_allowing_nil?
      true
    else
      @failure_message_continuation =
        "However, #{attribute_name.inspect} is allowing nil values"
      false
    end
  else
    @failure_message_continuation =
      if expected_validating?
        "However, #{attribute_name.inspect} is not being validated"
      else
        "However, #{attribute_name.inspect} is being validated"
      end
    false
  end
end