mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
Use relations to define conditions and order on associations
In Rails 4, the following construct: has_many :children, conditions: { adopted: true } changes to: has_many :children, lambda { where(adopted: true) } As a result, the way we check the conditions attached to a has_many changes too: instead of accessing `reflection.options`, we have to use `reflection.scope` -- this which refers to the lambda above, so we have to evaluate it and then grab the `where` from the Relation that the lambda returns.
This commit is contained in:
parent
55f45d9549
commit
2b68f3859b
6 changed files with 142 additions and 25 deletions
|
@ -251,7 +251,7 @@ module Shoulda # :nodoc:
|
|||
|
||||
def conditions_correct?
|
||||
if options.key?(:conditions)
|
||||
if option_verifier.correct_for_string?(:conditions, options[:conditions])
|
||||
if option_verifier.correct_for_relation_clause?(:conditions, options[:conditions])
|
||||
true
|
||||
else
|
||||
@missing = "#{name} should have the following conditions: #{options[:conditions]}"
|
||||
|
@ -292,7 +292,7 @@ module Shoulda # :nodoc:
|
|||
|
||||
def class_has_foreign_key?(klass)
|
||||
if options.key?(:foreign_key)
|
||||
option_verifier.correct_for?(:foreign_key, options[:foreign_key])
|
||||
option_verifier.correct_for_string?(:foreign_key, options[:foreign_key])
|
||||
else
|
||||
if klass.column_names.include?(foreign_key)
|
||||
true
|
||||
|
|
|
@ -36,8 +36,61 @@ module Shoulda # :nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def association_relation
|
||||
if reflection.respond_to?(:scope) && reflection.scope
|
||||
relation_from_scope(reflection.scope)
|
||||
else
|
||||
options = reflection.options
|
||||
relation = RailsShim.clean_scope(reflection.klass)
|
||||
if options[:conditions]
|
||||
relation = relation.where(options[:conditions])
|
||||
end
|
||||
if options[:include]
|
||||
relation = relation.include(options[:include])
|
||||
end
|
||||
if options[:order]
|
||||
relation = relation.order(options[:order])
|
||||
end
|
||||
if options[:group]
|
||||
relation = relation.group(options[:group])
|
||||
end
|
||||
if options[:having]
|
||||
relation = relation.having(options[:having])
|
||||
end
|
||||
if options[:limit]
|
||||
relation = relation.limit(options[:limit])
|
||||
end
|
||||
relation
|
||||
end
|
||||
end
|
||||
|
||||
def build_relation_with_clause(name, value)
|
||||
case name
|
||||
when :conditions then associated_class.where(value)
|
||||
when :order then associated_class.order(value)
|
||||
else raise ArgumentError, "Unknown clause '#{name}'"
|
||||
end
|
||||
end
|
||||
|
||||
def extract_relation_clause_from(relation, name)
|
||||
case name
|
||||
when :conditions then relation.where_values_hash
|
||||
when :order then relation.order_values.join(', ')
|
||||
else raise ArgumentError, "Unknown clause '#{name}'"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def relation_from_scope(scope)
|
||||
# Source: AR::Associations::AssociationScope#eval_scope
|
||||
if scope.is_a?(::Proc)
|
||||
associated_class.all.instance_exec(subject, &scope)
|
||||
else
|
||||
scope
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :subject, :name
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,8 @@ module Shoulda # :nodoc:
|
|||
|
||||
attr_reader :reflector
|
||||
|
||||
RELATION_OPTIONS = [:conditions, :order]
|
||||
|
||||
def initialize(reflector)
|
||||
@reflector = reflector
|
||||
end
|
||||
|
@ -23,12 +25,20 @@ module Shoulda # :nodoc:
|
|||
correct_for?(:hash, name, expected_value)
|
||||
end
|
||||
|
||||
def correct_for_relation_clause?(name, expected_value)
|
||||
correct_for?(:relation_clause, name, expected_value)
|
||||
end
|
||||
|
||||
def actual_value_for(name)
|
||||
method_name = "actual_value_for_#{name}"
|
||||
if respond_to?(method_name, true)
|
||||
__send__(method_name)
|
||||
if RELATION_OPTIONS.include?(name)
|
||||
actual_value_for_relation_clause(name)
|
||||
else
|
||||
reflection.options[name]
|
||||
method_name = "actual_value_for_#{name}"
|
||||
if respond_to?(method_name, true)
|
||||
__send__(method_name)
|
||||
else
|
||||
reflection.options[name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -49,15 +59,28 @@ module Shoulda # :nodoc:
|
|||
|
||||
def type_cast(type, value)
|
||||
case type
|
||||
when :string then value.to_s
|
||||
when :boolean then !!value
|
||||
when :hash then Hash(value).stringify_keys
|
||||
else value
|
||||
when :string, :relation_clause then value.to_s
|
||||
when :boolean then !!value
|
||||
when :hash then Hash(value).stringify_keys
|
||||
else value
|
||||
end
|
||||
end
|
||||
|
||||
def expected_value_for(name, value)
|
||||
value
|
||||
if RELATION_OPTIONS.include?(name)
|
||||
expected_value_for_relation_clause(name, value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def expected_value_for_relation_clause(name, value)
|
||||
relation = reflector.build_relation_with_clause(name, value)
|
||||
reflector.extract_relation_clause_from(relation, name)
|
||||
end
|
||||
|
||||
def actual_value_for_relation_clause(name)
|
||||
reflector.extract_relation_clause_from(reflector.association_relation, name)
|
||||
end
|
||||
|
||||
def actual_value_for_class_name
|
||||
|
|
|
@ -18,7 +18,7 @@ module Shoulda # :nodoc:
|
|||
def matches?(subject)
|
||||
self.subject = ModelReflector.new(subject, name)
|
||||
|
||||
if option_verifier.correct_for_string?(:order, order)
|
||||
if option_verifier.correct_for_relation_clause?(:order, order)
|
||||
true
|
||||
else
|
||||
self.missing_option = "#{name} should be ordered by #{order}"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
class RailsShim # :nodoc:
|
||||
|
||||
def self.layouts_ivar
|
||||
if rails_major_version >= 4
|
||||
'@_layouts'
|
||||
|
@ -18,6 +17,14 @@ module Shoulda # :nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def self.clean_scope(klass)
|
||||
if rails_major_version == 4
|
||||
klass.all
|
||||
else
|
||||
klass.scoped
|
||||
end
|
||||
end
|
||||
|
||||
def self.rails_major_version
|
||||
Rails::VERSION::MAJOR
|
||||
end
|
||||
|
|
|
@ -67,8 +67,8 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|||
|
||||
it 'accepts an association with a valid :conditions option' do
|
||||
define_model :parent, :adopter => :boolean
|
||||
define_model :child, :parent_id => :integer do
|
||||
belongs_to :parent, :conditions => { :adopter => true }
|
||||
define_model(:child, :parent_id => :integer).tap do |model|
|
||||
define_association_with_conditions(model, :belongs_to, :parent, :adopter => true)
|
||||
end
|
||||
|
||||
Child.new.should belong_to(:parent).conditions(:adopter => true)
|
||||
|
@ -297,8 +297,8 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|||
|
||||
it 'accepts an association with a valid :conditions option' do
|
||||
define_model :child, :parent_id => :integer, :adopted => :boolean
|
||||
define_model :parent do
|
||||
has_many :children, :conditions => { :adopted => true }
|
||||
define_model(:parent).tap do |model|
|
||||
define_association_with_conditions(model, :has_many, :children, :adopted => true)
|
||||
end
|
||||
|
||||
Parent.new.should have_many(:children).conditions(:adopted => true)
|
||||
|
@ -374,8 +374,13 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|||
|
||||
def having_many_children(options = {})
|
||||
define_model :child, :parent_id => :integer
|
||||
define_model :parent do
|
||||
has_many :children, options
|
||||
define_model(:parent).tap do |model|
|
||||
if options.key?(:order)
|
||||
order = options.delete(:order)
|
||||
define_association_with_order(model, :has_many, :children, order, options)
|
||||
else
|
||||
model.has_many :children, options
|
||||
end
|
||||
end.new
|
||||
end
|
||||
end
|
||||
|
@ -449,8 +454,8 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|||
|
||||
it 'accepts an association with a valid :conditions option' do
|
||||
define_model :detail, :person_id => :integer, :disabled => :boolean
|
||||
define_model :person do
|
||||
has_one :detail, :conditions => { :disabled => true}
|
||||
define_model(:person).tap do |model|
|
||||
define_association_with_conditions(model, :has_one, :detail, :disabled => true)
|
||||
end
|
||||
|
||||
Person.new.should have_one(:detail).conditions(:disabled => true)
|
||||
|
@ -524,8 +529,13 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|||
|
||||
def having_one_detail(options = {})
|
||||
define_model :detail, :person_id => :integer
|
||||
define_model :person do
|
||||
has_one :detail, options
|
||||
define_model(:person).tap do |model|
|
||||
if options.key?(:order)
|
||||
order = options.delete(:order)
|
||||
define_association_with_order(model, :has_one, :detail, order, options)
|
||||
else
|
||||
model.has_one :detail, options
|
||||
end
|
||||
end.new
|
||||
end
|
||||
end
|
||||
|
@ -565,8 +575,8 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|||
|
||||
it 'accepts an association with a valid :conditions option' do
|
||||
define_model :relative, :adopted => :boolean
|
||||
define_model :person do
|
||||
has_and_belongs_to_many :relatives, :conditions => { :adopted => true }
|
||||
define_model(:person).tap do |model|
|
||||
define_association_with_conditions(model, :has_and_belongs_to_many, :relatives, :adopted => true)
|
||||
end
|
||||
define_model :people_relative, :id => false, :person_id => :integer,
|
||||
:relative_id => :integer
|
||||
|
@ -638,4 +648,28 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|||
end.new
|
||||
end
|
||||
end
|
||||
|
||||
def define_association_with_conditions(model, macro, name, conditions, other_options={})
|
||||
args = []
|
||||
options = {}
|
||||
if Shoulda::Matchers::RailsShim.rails_major_version == 4
|
||||
args << lambda { where(conditions) }
|
||||
else
|
||||
options[:conditions] = conditions
|
||||
end
|
||||
args << options
|
||||
model.__send__(macro, name, *args)
|
||||
end
|
||||
|
||||
def define_association_with_order(model, macro, name, order, other_options={})
|
||||
args = []
|
||||
options = {}
|
||||
if Shoulda::Matchers::RailsShim.rails_major_version == 4
|
||||
args << lambda { order(order) }
|
||||
else
|
||||
options[:order] = order
|
||||
end
|
||||
args << options
|
||||
model.__send__(macro, name, *args)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue