2010-12-15 22:34:19 +00:00
|
|
|
module Shoulda # :nodoc:
|
|
|
|
module Matchers
|
|
|
|
module ActiveRecord # :nodoc:
|
|
|
|
# Ensure that the belongs_to relationship exists.
|
|
|
|
#
|
2012-04-11 16:45:52 +00:00
|
|
|
# Options:
|
2012-12-21 06:21:02 +00:00
|
|
|
# * <tt>:class_name</tt> - tests that the association resolves to class_name.
|
2012-04-11 16:45:52 +00:00
|
|
|
# * <tt>:validate</tt> - tests that the association makes use of the validate
|
|
|
|
# option.
|
|
|
|
#
|
|
|
|
# Example:
|
2010-12-15 22:34:19 +00:00
|
|
|
# it { should belong_to(:parent) }
|
|
|
|
#
|
|
|
|
def belong_to(name)
|
|
|
|
AssociationMatcher.new(:belongs_to, name)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Ensures that the has_many relationship exists. Will also test that the
|
|
|
|
# associated table has the required columns. Works with polymorphic
|
|
|
|
# associations.
|
|
|
|
#
|
|
|
|
# Options:
|
|
|
|
# * <tt>through</tt> - association name for <tt>has_many :through</tt>
|
|
|
|
# * <tt>dependent</tt> - tests that the association makes use of the
|
|
|
|
# dependent option.
|
2012-12-21 06:21:02 +00:00
|
|
|
# * <tt>:class_name</tt> - tests that the association resoves to class_name.
|
2012-04-11 16:45:52 +00:00
|
|
|
# * <tt>:validate</tt> - tests that the association makes use of the validate
|
|
|
|
# option.
|
2010-12-15 22:34:19 +00:00
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# it { should have_many(:friends) }
|
|
|
|
# it { should have_many(:enemies).through(:friends) }
|
|
|
|
# it { should have_many(:enemies).dependent(:destroy) }
|
|
|
|
#
|
|
|
|
def have_many(name)
|
|
|
|
AssociationMatcher.new(:has_many, name)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Ensure that the has_one relationship exists. Will also test that the
|
|
|
|
# associated table has the required columns. Works with polymorphic
|
|
|
|
# associations.
|
|
|
|
#
|
|
|
|
# Options:
|
|
|
|
# * <tt>:dependent</tt> - tests that the association makes use of the
|
|
|
|
# dependent option.
|
2012-12-21 06:21:02 +00:00
|
|
|
# * <tt>:class_name</tt> - tests that the association resolves to class_name.
|
2012-04-11 16:45:52 +00:00
|
|
|
# * <tt>:validate</tt> - tests that the association makes use of the validate
|
|
|
|
# option.
|
2010-12-15 22:34:19 +00:00
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# it { should have_one(:god) } # unless hindu
|
|
|
|
#
|
|
|
|
def have_one(name)
|
|
|
|
AssociationMatcher.new(:has_one, name)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Ensures that the has_and_belongs_to_many relationship exists, and that
|
|
|
|
# the join table is in place.
|
|
|
|
#
|
2012-04-11 16:45:52 +00:00
|
|
|
# Options:
|
2012-12-21 06:21:02 +00:00
|
|
|
# * <tt>:class_name</tt> - tests that the association resolves to class_name.
|
2012-04-11 16:45:52 +00:00
|
|
|
# * <tt>:validate</tt> - tests that the association makes use of the validate
|
|
|
|
# option.
|
|
|
|
#
|
|
|
|
# Example:
|
2010-12-15 22:34:19 +00:00
|
|
|
# it { should have_and_belong_to_many(:posts) }
|
|
|
|
#
|
|
|
|
def have_and_belong_to_many(name)
|
|
|
|
AssociationMatcher.new(:has_and_belongs_to_many, name)
|
|
|
|
end
|
|
|
|
|
|
|
|
class AssociationMatcher # :nodoc:
|
|
|
|
def initialize(macro, name)
|
|
|
|
@macro = macro
|
2012-04-23 21:37:40 +00:00
|
|
|
@name = name
|
|
|
|
@options = {}
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def through(through)
|
2012-04-23 21:37:40 +00:00
|
|
|
@options[:through] = through
|
2010-12-15 22:34:19 +00:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def dependent(dependent)
|
2012-04-23 21:37:40 +00:00
|
|
|
@options[:dependent] = dependent
|
2010-12-15 22:34:19 +00:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2011-06-16 14:21:57 +00:00
|
|
|
def order(order)
|
2012-04-23 21:37:40 +00:00
|
|
|
@options[:order] = order
|
2011-06-16 14:21:57 +00:00
|
|
|
self
|
2011-09-12 10:25:05 +00:00
|
|
|
end
|
2011-10-16 12:06:24 +00:00
|
|
|
|
2011-09-23 11:19:17 +00:00
|
|
|
def conditions(conditions)
|
2012-04-23 21:37:40 +00:00
|
|
|
@options[:conditions] = conditions
|
2011-09-23 11:19:17 +00:00
|
|
|
self
|
|
|
|
end
|
2011-06-16 14:21:57 +00:00
|
|
|
|
2011-10-16 12:06:24 +00:00
|
|
|
def class_name(class_name)
|
2012-04-23 21:37:40 +00:00
|
|
|
@options[:class_name] = class_name
|
2011-10-16 12:06:24 +00:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2012-08-24 05:48:17 +00:00
|
|
|
def with_foreign_key(foreign_key)
|
|
|
|
@options[:foreign_key] = foreign_key
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2012-04-11 16:45:52 +00:00
|
|
|
def validate(validate = true)
|
|
|
|
@validate = validate
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
def matches?(subject)
|
|
|
|
@subject = subject
|
|
|
|
association_exists? &&
|
|
|
|
macro_correct? &&
|
|
|
|
foreign_key_exists? &&
|
|
|
|
through_association_valid? &&
|
|
|
|
dependent_correct? &&
|
2011-10-16 12:06:24 +00:00
|
|
|
class_name_correct? &&
|
2011-06-16 14:21:57 +00:00
|
|
|
order_correct? &&
|
2011-09-23 11:19:17 +00:00
|
|
|
conditions_correct? &&
|
2012-04-11 16:45:52 +00:00
|
|
|
join_table_exists? &&
|
|
|
|
validate_correct?
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
2013-01-08 17:29:01 +00:00
|
|
|
def failure_message_for_should
|
2010-12-15 22:34:19 +00:00
|
|
|
"Expected #{expectation} (#{@missing})"
|
|
|
|
end
|
|
|
|
|
2013-01-08 17:29:01 +00:00
|
|
|
def failure_message_for_should_not
|
2010-12-15 22:34:19 +00:00
|
|
|
"Did not expect #{expectation}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def description
|
|
|
|
description = "#{macro_description} #{@name}"
|
2012-04-23 21:37:40 +00:00
|
|
|
description += " through #{@options[:through]}" if @options.key?(:through)
|
|
|
|
description += " dependent => #{@options[:dependent]}" if @options.key?(:dependent)
|
|
|
|
description += " class_name => #{@options[:class_name]}" if @options.key?(:class_name)
|
|
|
|
description += " order => #{@options[:order]}" if @options.key?(:order)
|
2010-12-15 22:34:19 +00:00
|
|
|
description
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
def association_exists?
|
|
|
|
if reflection.nil?
|
|
|
|
@missing = "no association called #{@name}"
|
|
|
|
false
|
|
|
|
else
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def macro_correct?
|
|
|
|
if reflection.macro == @macro
|
|
|
|
true
|
|
|
|
else
|
|
|
|
@missing = "actual association type was #{reflection.macro}"
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def foreign_key_exists?
|
|
|
|
!(belongs_foreign_key_missing? || has_foreign_key_missing?)
|
|
|
|
end
|
|
|
|
|
|
|
|
def belongs_foreign_key_missing?
|
|
|
|
@macro == :belongs_to && !class_has_foreign_key?(model_class)
|
|
|
|
end
|
|
|
|
|
|
|
|
def has_foreign_key_missing?
|
|
|
|
[:has_many, :has_one].include?(@macro) &&
|
|
|
|
!through? &&
|
|
|
|
!class_has_foreign_key?(associated_class)
|
|
|
|
end
|
|
|
|
|
|
|
|
def through_association_valid?
|
2012-04-23 21:37:40 +00:00
|
|
|
@options[:through].nil? || (through_association_exists? && through_association_correct?)
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def through_association_exists?
|
|
|
|
if through_reflection.nil?
|
2012-04-23 21:37:40 +00:00
|
|
|
@missing = "#{model_class.name} does not have any relationship to #{@options[:through]}"
|
2010-12-15 22:34:19 +00:00
|
|
|
false
|
|
|
|
else
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def through_association_correct?
|
2012-04-23 21:37:40 +00:00
|
|
|
if @options[:through] == reflection.options[:through]
|
2010-12-15 22:34:19 +00:00
|
|
|
true
|
|
|
|
else
|
2012-04-23 21:37:40 +00:00
|
|
|
@missing = "Expected #{model_class.name} to have #{@name} through #{@options[:through]}, " +
|
2010-12-15 22:34:19 +00:00
|
|
|
"but got it through #{reflection.options[:through]}"
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def dependent_correct?
|
2012-04-23 21:37:40 +00:00
|
|
|
if @options[:dependent].nil? || @options[:dependent].to_s == reflection.options[:dependent].to_s
|
2010-12-15 22:34:19 +00:00
|
|
|
true
|
|
|
|
else
|
2012-04-23 21:37:40 +00:00
|
|
|
@missing = "#{@name} should have #{@options[:dependent]} dependency"
|
2010-12-15 22:34:19 +00:00
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-10-16 12:06:24 +00:00
|
|
|
def class_name_correct?
|
2012-04-23 21:37:40 +00:00
|
|
|
if @options.key?(:class_name)
|
2012-12-21 06:21:02 +00:00
|
|
|
if @options[:class_name].to_s == reflection.klass.to_s
|
2012-04-23 21:37:40 +00:00
|
|
|
true
|
|
|
|
else
|
2012-12-21 06:21:02 +00:00
|
|
|
@missing = "#{@name} should resolve to #{@options[:class_name]} for class_name"
|
2012-04-23 21:37:40 +00:00
|
|
|
false
|
|
|
|
end
|
2011-10-16 12:06:24 +00:00
|
|
|
else
|
2012-04-23 21:37:40 +00:00
|
|
|
true
|
2011-10-16 12:06:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-06-16 14:21:57 +00:00
|
|
|
def order_correct?
|
2012-04-23 21:37:40 +00:00
|
|
|
if @options.key?(:order)
|
|
|
|
if @options[:order].to_s == reflection.options[:order].to_s
|
|
|
|
true
|
|
|
|
else
|
|
|
|
@missing = "#{@name} should be ordered by #{@options[:order]}"
|
|
|
|
false
|
|
|
|
end
|
2011-06-16 14:21:57 +00:00
|
|
|
else
|
2012-04-23 21:37:40 +00:00
|
|
|
true
|
2011-06-16 14:21:57 +00:00
|
|
|
end
|
2011-09-12 10:25:05 +00:00
|
|
|
end
|
2011-06-16 14:21:57 +00:00
|
|
|
|
2011-09-23 11:19:17 +00:00
|
|
|
def conditions_correct?
|
2012-04-23 21:37:40 +00:00
|
|
|
if @options.key?(:conditions)
|
|
|
|
if @options[:conditions].to_s == reflection.options[:conditions].to_s
|
|
|
|
true
|
|
|
|
else
|
|
|
|
@missing = "#{@name} should have the following conditions: #{@options[:conditions]}"
|
|
|
|
false
|
|
|
|
end
|
2011-09-23 11:19:17 +00:00
|
|
|
else
|
2012-04-23 21:37:40 +00:00
|
|
|
true
|
2011-09-23 11:19:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
def join_table_exists?
|
|
|
|
if @macro != :has_and_belongs_to_many ||
|
2012-09-18 19:31:17 +00:00
|
|
|
model_class.connection.tables.include?(join_table)
|
2010-12-15 22:34:19 +00:00
|
|
|
true
|
|
|
|
else
|
|
|
|
@missing = "join table #{join_table} doesn't exist"
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-04-11 16:45:52 +00:00
|
|
|
def validate_correct?
|
|
|
|
if !@validate && !reflection.options[:validate] || @validate == reflection.options[:validate]
|
|
|
|
true
|
|
|
|
else
|
2012-12-20 05:04:27 +00:00
|
|
|
@missing = "#{@name} should have :validate => #{@validate}"
|
2012-04-11 16:45:52 +00:00
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
def class_has_foreign_key?(klass)
|
2012-08-24 05:48:17 +00:00
|
|
|
if @options.key?(:foreign_key)
|
|
|
|
reflection.options[:foreign_key] == @options[:foreign_key]
|
2010-12-15 22:34:19 +00:00
|
|
|
else
|
2012-08-24 05:48:17 +00:00
|
|
|
if klass.column_names.include?(foreign_key)
|
|
|
|
true
|
|
|
|
else
|
|
|
|
@missing = "#{klass} does not have a #{foreign_key} foreign key."
|
|
|
|
false
|
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def model_class
|
|
|
|
@subject.class
|
|
|
|
end
|
|
|
|
|
|
|
|
def join_table
|
2012-03-23 23:50:08 +00:00
|
|
|
reflection.options[:join_table].to_s
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def associated_class
|
|
|
|
reflection.klass
|
|
|
|
end
|
|
|
|
|
|
|
|
def foreign_key
|
2012-03-23 23:50:08 +00:00
|
|
|
if foreign_key_reflection
|
|
|
|
if foreign_key_reflection.respond_to?(:foreign_key)
|
|
|
|
foreign_key_reflection.foreign_key.to_s
|
|
|
|
else
|
|
|
|
foreign_key_reflection.primary_key_name.to_s
|
|
|
|
end
|
2012-03-11 18:49:27 +00:00
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def through?
|
|
|
|
reflection.options[:through]
|
|
|
|
end
|
|
|
|
|
|
|
|
def reflection
|
|
|
|
@reflection ||= model_class.reflect_on_association(@name)
|
|
|
|
end
|
|
|
|
|
2012-03-23 23:50:08 +00:00
|
|
|
def foreign_key_reflection
|
|
|
|
if [:has_one, :has_many].include?(@macro) && reflection.options.include?(:inverse_of)
|
|
|
|
associated_class.reflect_on_association(reflection.options[:inverse_of])
|
|
|
|
else
|
|
|
|
reflection
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
def through_reflection
|
2012-04-23 21:37:40 +00:00
|
|
|
@through_reflection ||= model_class.reflect_on_association(@options[:through])
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def expectation
|
|
|
|
"#{model_class.name} to have a #{@macro} association called #{@name}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def macro_description
|
|
|
|
case @macro.to_s
|
2012-04-20 17:41:08 +00:00
|
|
|
when 'belongs_to'
|
|
|
|
'belong to'
|
|
|
|
when 'has_many'
|
|
|
|
'have many'
|
|
|
|
when 'has_one'
|
|
|
|
'have one'
|
|
|
|
when 'has_and_belongs_to_many'
|
2010-12-15 22:34:19 +00:00
|
|
|
'have and belong to many'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|