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:
Elliot Winkler 2013-05-13 22:42:12 -04:00
parent 55f45d9549
commit 2b68f3859b
6 changed files with 142 additions and 25 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}"

View File

@ -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

View File

@ -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