class Shoulda::Matchers::ActiveRecord::AssociationMatcher

@private

Constants

MACROS

Attributes

macro[R]
missing[R]
name[R]
options[R]
subject[R]
submatchers[R]

Public Class Methods

new(macro, name) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1398
def initialize(macro, name)
  @macro = macro
  @name = name
  @options = {}
  @submatchers = []
  @missing = ''

  if macro == :belongs_to
    required(belongs_to_required_by_default?)
  end
end

Public Instance Methods

autosave(autosave) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1474
def autosave(autosave)
  @options[:autosave] = autosave
  self
end
class_name(class_name) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1484
def class_name(class_name)
  @options[:class_name] = class_name
  self
end
conditions(conditions) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1464
def conditions(conditions)
  @options[:conditions] = conditions
  self
end
counter_cache(counter_cache = true) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1437
def counter_cache(counter_cache = true)
  add_submatcher(
    AssociationMatchers::CounterCacheMatcher,
    counter_cache,
    name,
  )
  self
end
dependent(dependent) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1419
def dependent(dependent)
  add_submatcher(
    AssociationMatchers::DependentMatcher,
    dependent,
    name,
  )
  self
end
description() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1554
def description
  description = "#{macro_description} #{name}"
  if options.key?(:class_name)
    description += " class_name => #{options[:class_name]}"
  end
  [description, submatchers.map(&:description)].flatten.join(' ')
end
failure_message() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1562
def failure_message
  "Expected #{expectation} (#{missing_options})"
end
failure_message_when_negated() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1566
def failure_message_when_negated
  "Did not expect #{expectation}"
end
index_errors(index_errors) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1479
def index_errors(index_errors)
  @options[:index_errors] = index_errors
  self
end
inverse_of(inverse_of) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1446
def inverse_of(inverse_of)
  add_submatcher(
    AssociationMatchers::InverseOfMatcher,
    inverse_of,
    name,
  )
  self
end
join_table(join_table_name) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1544
def join_table(join_table_name)
  @options[:join_table_name] = join_table_name
  self
end
join_table_name() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1592
def join_table_name
  options[:join_table_name] || reflector.join_table_name
end
matches?(subject) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1570
def matches?(subject)
  @subject = subject
  association_exists? &&
    macro_correct? &&
    validate_inverse_of_through_association &&
    (polymorphic? || class_exists?) &&
    foreign_type_matches? &&
    foreign_key_exists? &&
    primary_key_exists? &&
    query_constraints_exists? &&
    class_name_correct? &&
    join_table_correct? &&
    autosave_correct? &&
    index_errors_correct? &&
    conditions_correct? &&
    validate_correct? &&
    touch_correct? &&
    types_correct? &&
    strict_loading_correct? &&
    submatchers_match?
end
option_verifier() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1596
def option_verifier
  @_option_verifier ||=
    AssociationMatchers::OptionVerifier.new(reflector)
end
optional(optional = true) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1519
def optional(optional = true)
  remove_submatcher(AssociationMatchers::RequiredMatcher)
  add_submatcher(
    AssociationMatchers::OptionalMatcher,
    name,
    optional,
  )
  self
end
order(order) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1428
def order(order)
  add_submatcher(
    AssociationMatchers::OrderMatcher,
    order,
    name,
  )
  self
end
required(required = true) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1509
def required(required = true)
  remove_submatcher(AssociationMatchers::OptionalMatcher)
  add_submatcher(
    AssociationMatchers::RequiredMatcher,
    name,
    required,
  )
  self
end
source(source) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1455
def source(source)
  add_submatcher(
    AssociationMatchers::SourceMatcher,
    source,
    name,
  )
  self
end
strict_loading(strict_loading = true) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1539
def strict_loading(strict_loading = true)
  @options[:strict_loading] = strict_loading
  self
end
through(through) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1410
def through(through)
  add_submatcher(
    AssociationMatchers::ThroughMatcher,
    through,
    name,
  )
  self
end
touch(touch = true) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1534
def touch(touch = true)
  @options[:touch] = touch
  self
end
types(types) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1469
def types(types)
  @options[:types] = types
  self
end
validate(validate = true) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1529
def validate(validate = true)
  @options[:validate] = validate
  self
end
with_foreign_key(foreign_key) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1489
def with_foreign_key(foreign_key)
  @options[:foreign_key] = foreign_key
  self
end
with_foreign_type(foreign_type) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1494
def with_foreign_type(foreign_type)
  @options[:foreign_type] = foreign_type
  self
end
with_primary_key(primary_key) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1499
def with_primary_key(primary_key)
  @options[:primary_key] = primary_key
  self
end
with_query_constraints(query_constraints) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1504
def with_query_constraints(query_constraints)
  @options[:query_constraints] = query_constraints
  self
end
without_validating_presence() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1549
def without_validating_presence
  remove_submatcher(AssociationMatchers::RequiredMatcher)
  self
end

Protected Instance Methods

actual_foreign_key() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1968
def actual_foreign_key
  return unless foreign_key_reflection

  if foreign_key_reflection.options[:foreign_key]
    foreign_key_reflection.options[:foreign_key]
  elsif foreign_key_reflection.respond_to?(:foreign_key)
    foreign_key_reflection.foreign_key
  else
    foreign_key_reflection.primary_key_name
  end
end
add_submatcher(matcher_class, *args) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1609
def add_submatcher(matcher_class, *args)
  remove_submatcher(matcher_class)
  submatchers << matcher_class.new(*args)
end
association_exists?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1654
def association_exists?
  if reflection.nil?
    @missing = "no association called #{name}"
    false
  else
    true
  end
end
autosave_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1783
def autosave_correct?
  if options.key?(:autosave)
    if option_verifier.correct_for_boolean?(
      :autosave,
      options[:autosave],
    )
      true
    else
      @missing = "#{name} should have autosave set to"\
        " #{options[:autosave]}"
      false
    end
  else
    true
  end
end
belongs_foreign_key_missing?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1724
def belongs_foreign_key_missing?
  macro == :belongs_to && !class_has_foreign_key?(model_class)
end
belongs_foreign_type_missing?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1728
def belongs_foreign_type_missing?
  macro == :belongs_to && !class_has_foreign_type?(model_class)
end
belongs_to_required_by_default?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 2011
def belongs_to_required_by_default?
  ::ActiveRecord::Base.belongs_to_required_by_default
end
class_exists?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1775
def class_exists?
  associated_class
  true
rescue NameError
  @missing = "#{reflection.class_name} does not exist"
  false
end
class_has_foreign_key?(klass) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1890
def class_has_foreign_key?(klass)
  @missing = validate_foreign_key(klass)

  @missing.nil?
end
class_has_foreign_type?(klass) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1904
def class_has_foreign_type?(klass)
  if options.key?(:foreign_type) && !foreign_type_correct?
    @missing = foreign_type_failure_message(
      klass,
      options[:foreign_type],
    )

    false
  elsif !has_column?(klass, foreign_type)
    @missing = foreign_type_failure_message(klass, foreign_type)
    false
  else
    true
  end
end
class_name_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1742
def class_name_correct?
  if options.key?(:class_name)
    if option_verifier.correct_for_constant?(
      :class_name,
      options[:class_name],
    )
      true
    else
      @missing = "#{name} should resolve to #{options[:class_name]}"\
        ' for class_name'
      false
    end
  else
    true
  end
end
column_names_for(klass) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 2005
def column_names_for(klass)
  klass.column_names
rescue ::ActiveRecord::StatementInvalid
  []
end
conditions_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1816
def conditions_correct?
  if options.key?(:conditions)
    if option_verifier.correct_for_relation_clause?(
      :conditions,
      options[:conditions],
    )
      true
    else
      @missing = "#{name} should have the following conditions:" +
                 " #{options[:conditions]}"
      false
    end
  else
    true
  end
end
expectation() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1624
def expectation
  expectation =
    "#{model_class.name} to have a #{macro} association called #{name}"

  if through?
    expectation << " through #{reflector.has_and_belongs_to_many_name}"
  end

  expectation
end
failing_submatchers() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1640
def failing_submatchers
  @_failing_submatchers ||= submatchers.reject do |matcher|
    matcher.matches?(subject)
  end
end
foreign_key_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1929
def foreign_key_correct?
  option_verifier.correct_for_string?(
    :foreign_key,
    options[:foreign_key],
  )
end
foreign_key_exists?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1691
def foreign_key_exists?
  !(belongs_foreign_key_missing? || has_foreign_key_missing?)
end
foreign_key_failure_message(klass, foreign_key) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1943
def foreign_key_failure_message(klass, foreign_key)
  "#{klass} does not have a #{foreign_key} foreign key."
end
foreign_key_reflection() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1980
def foreign_key_reflection
  if [:has_one, :has_many].include?(macro) &&
     reflection.options.include?(:inverse_of) &&
     reflection.options[:inverse_of] != false

    associated_class.reflect_on_association(
      reflection.options[:inverse_of],
    )
  else
    reflection
  end
end
foreign_type() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1993
def foreign_type
  if [:has_one, :has_many].include?(macro)
    reflection.type
  else
    reflection.foreign_type
  end
end
foreign_type_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1936
def foreign_type_correct?
  option_verifier.correct_for_string?(
    :foreign_type,
    options[:foreign_type],
  )
end
foreign_type_failure_message(klass, foreign_type) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1947
def foreign_type_failure_message(klass, foreign_type)
  "#{klass} does not have a #{foreign_type} foreign type."
end
foreign_type_matches?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1695
def foreign_type_matches?
  !options.key?(:foreign_type) || (
    !belongs_foreign_type_missing? &&
    !has_foreign_type_missing?
  )
end
has_association_not_through?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1687
def has_association_not_through?
  [:has_many, :has_one].include?(macro) && !through?
end
has_column?(klass, column) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1920
def has_column?(klass, column)
  case column
  when Array
    column.all? { |c| has_column?(klass, c.to_s) }
  else
    column_names_for(klass).include?(column.to_s)
  end
end
has_foreign_key_missing?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1732
def has_foreign_key_missing?
  has_association_not_through? &&
    !class_has_foreign_key?(associated_class)
end
has_foreign_type_missing?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1737
def has_foreign_type_missing?
  has_association_not_through? &&
    !class_has_foreign_type?(associated_class)
end
index_errors_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1800
def index_errors_correct?
  return true unless options.key?(:index_errors)

  if option_verifier.correct_for_boolean?(
    :index_errors,
    options[:index_errors],
  )
    true
  else
    @missing =
      "#{name} should have index_errors set to " +
      options[:index_errors].to_s
    false
  end
end
join_table_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1759
def join_table_correct?
  if macro != :has_and_belongs_to_many || join_table_matcher.matches?(@subject)
    true
  else
    @missing = join_table_matcher.failure_message
    false
  end
end
join_table_matcher() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1768
def join_table_matcher
  @_join_table_matcher ||= AssociationMatchers::JoinTableMatcher.new(
    self,
    reflector,
  )
end
macro_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1663
def macro_correct?
  if reflection.macro == macro
    true
  elsif reflection.macro == :has_many
    macro == :has_and_belongs_to_many &&
      reflection.name == @name
  else
    @missing = "actual association type was #{reflection.macro}"
    false
  end
end
macro_description() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1620
def macro_description
  MACROS[macro.to_s]
end
macro_is_not_through?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1683
def macro_is_not_through?
  macro == :belongs_to || has_association_not_through?
end
missing_options() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1635
def missing_options
  missing_options = [missing, missing_options_for_failing_submatchers]
  missing_options.flatten.select(&:present?).join(', ')
end
missing_options_for_failing_submatchers() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1646
def missing_options_for_failing_submatchers
  if defined?(@_failing_submatchers)
    @_failing_submatchers.map(&:missing_option)
  else
    []
  end
end
primary_key_correct?(klass) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1951
def primary_key_correct?(klass)
  if options.key?(:primary_key)
    if option_verifier.correct_for_string?(
      :primary_key,
      options[:primary_key],
    )
      true
    else
      @missing = "#{klass} does not have a #{options[:primary_key]}"\
        ' primary key'
      false
    end
  else
    true
  end
end
primary_key_exists?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1702
def primary_key_exists?
  !macro_is_not_through? || primary_key_correct?(model_class)
end
query_constraints_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1710
def query_constraints_correct?
  if options.key?(:query_constraints)
    if option_verifier.correct_for_string?(:query_constraints, options[:query_constraints])
      true
    else
      @missing = "#{model_class} should have \:query_constraints"\
      " options set to #{options[:query_constraints]}"
      false
    end
  else
    true
  end
end
query_constraints_exists?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1706
def query_constraints_exists?
  !macro_is_not_through? || query_constraints_correct?
end
reflector() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1605
def reflector
  @_reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
end
remove_submatcher(matcher_class) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1614
def remove_submatcher(matcher_class)
  submatchers.delete_if do |submatcher|
    submatcher.is_a?(matcher_class)
  end
end
strict_loading_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1875
def strict_loading_correct?
  return true unless options.key?(:strict_loading)

  if option_verifier.correct_for_boolean?(:strict_loading, options[:strict_loading])
    return true
  end

  @missing = [
    "#{name} should have strict_loading set to ",
    options[:strict_loading].to_s,
  ].join

  false
end
submatchers_match?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 2001
def submatchers_match?
  failing_submatchers.empty?
end
touch_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1842
def touch_correct?
  if option_verifier.correct_for_boolean?(:touch, options[:touch])
    true
  else
    @missing = "#{name} should have touch: #{options[:touch]}"
    false
  end
end
types_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1851
def types_correct?
  if options.key?(:types)
    types = options[:types]

    correct = types.all? do |type|
      scope_name = type.tableize.tr('/', '_')
      singular   = scope_name.singularize
      query      = "#{singular}?"

      Object.const_defined?(type) && @subject.respond_to?(query) &&
        @subject.respond_to?(singular)
    end

    if correct
      true
    else
      @missing = "#{name} should have types: #{options[:types]}"
      false
    end
  else
    true
  end
end
validate_correct?() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1833
def validate_correct?
  if option_verifier.correct_for_boolean?(:validate, options[:validate])
    true
  else
    @missing = "#{name} should have validate: #{options[:validate]}"
    false
  end
end
validate_foreign_key(klass) click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1896
def validate_foreign_key(klass)
  if options.key?(:foreign_key) && !foreign_key_correct?
    foreign_key_failure_message(klass, options[:foreign_key])
  elsif !has_column?(klass, actual_foreign_key)
    foreign_key_failure_message(klass, actual_foreign_key)
  end
end
validate_inverse_of_through_association() click to toggle source
# File lib/shoulda/matchers/active_record/association_matcher.rb, line 1675
def validate_inverse_of_through_association
  reflector.validate_inverse_of_through_association!
  true
rescue ::ActiveRecord::ActiveRecordError => e
  @missing = e.message
  false
end