Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Allow equality/inequality conditions on belongs_to

Fixes #265
  • Loading branch information...
commit 2f5b4851b87ec01bbd30472448987a90496db5b1 1 parent 4360bf7
@ernie ernie authored
View
2  CHANGELOG.md
@@ -3,6 +3,8 @@
* Update relation extensions to support new count behavior in Active Record
4.0.1 (see rails/rails@da9b5d4a)
* Support two-argument version of Relation#from in AR4
+* Allow equality/inequality conditions against the name of a belongs_to
+ association with an AR::Base value object. Fixes issue #265.
## 1.1.0 (2013-07-14)
View
11 lib/squeel/visitors/predicate_visitation.rb
@@ -1,6 +1,7 @@
module Squeel
module Visitors
module PredicateVisitation
+ EXPAND_BELONGS_TO_METHODS = [:eq, :not_eq]
private
@@ -28,6 +29,16 @@ def visit_Squeel_Nodes_Sifter(o, parent)
def visit_Squeel_Nodes_Predicate(o, parent)
value = o.value
+ # Short-circuit for stuff like `where{ author.eq User.first }`
+ # This filthy hack emulates similar behavior in AR PredicateBuilder
+ if ActiveRecord::Base === value &&
+ EXPAND_BELONGS_TO_METHODS.include?(o.method_name) &&
+ association = classify(parent).reflect_on_association(
+ symbolify(o.expr)
+ )
+ return expand_belongs_to(o, parent, association)
+ end
+
case value
when Nodes::KeyPath
value = can_visit?(value.endpoint) ? visit(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_s]
View
32 lib/squeel/visitors/predicate_visitor.rb
@@ -8,6 +8,31 @@ class PredicateVisitor < Visitor
private
+ # Expand a belongs_to association that has an AR::Base value. This allows
+ # for queries like:
+ #
+ # Post.where(:author => User.first)
+ # Post.where{author.eq User.first}
+ #
+ # @param [Squeel::Nodes::Predicate] o A predicate node (eq/not_eq)
+ # @param parent The current parent object in the context
+ # @return [Arel::Nodes::Node] An Arel predicate node
+ def expand_belongs_to(o, parent, association)
+ context = contextualize(parent)
+ ar_base = o.value
+ conditions = [
+ context[association.foreign_key.to_s].send(o.method_name, ar_base.id)
+ ]
+ if association.polymorphic?
+ conditions << [
+ context[association.foreign_type].send(
+ o.method_name, ar_base.class.base_class
+ )
+ ]
+ end
+ conditions.inject(o.method_name == :not_eq ? :or : :and)
+ end
+
# Visit a Hash. This entails iterating through each key and value and
# visiting each value in turn.
#
@@ -47,6 +72,13 @@ def implies_hash_context_shift?(v)
# @param parent The current parent object in the context
# @return An Arel predicate
def visit_without_hash_context_shift(k, v, parent)
+ # Short-circuit for stuff like `where(:author => User.first)`
+ # This filthy hack emulates similar behavior in AR PredicateBuilder
+ if ActiveRecord::Base === v &&
+ association = classify(parent).reflect_on_association(k.to_sym)
+ return expand_belongs_to(Nodes::Predicate.new(k, :eq, v), parent, association)
+ end
+
case v
when Nodes::Stub, Symbol
v = contextualize(parent)[v.to_s]
View
9 lib/squeel/visitors/visitor.rb
@@ -46,6 +46,15 @@ def self.can_visit?(object)
private
+ def symbolify(o)
+ case o
+ when Symbol, String, Nodes::Stub
+ o.to_sym
+ else
+ nil
+ end
+ end
+
# A hash that caches the method name to use for a visitor for a given class
DISPATCH = Hash.new do |hash, klass|
hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
View
36 spec/squeel/adapters/active_record/relation_extensions_spec.rb
@@ -514,6 +514,42 @@ module ActiveRecord
end
end
+ it 'allows equality conditions against a belongs_to with an AR::Base value' do
+ first_person = Person.first
+ relation = Article.where { person.eq first_person }
+ relation.to_sql.should match /"articles"."person_id" = #{first_person.id}/
+ end
+
+ it 'allows equality conditions against a polymorphic belongs_to with an AR::Base value' do
+ first_person = Person.first
+ relation = Note.where { notable.eq first_person }
+ relation.to_sql.should match /"notes"."notable_id" = #{first_person.id} AND "notes"."notable_type" = 'Person'/
+ end
+
+ it 'allows inequality conditions against a belongs_to with an AR::Base value' do
+ first_person = Person.first
+ relation = Article.where { person.not_eq first_person }
+ relation.to_sql.should match /"articles"."person_id" != #{first_person.id}/
+ end
+
+ it 'allows inequality conditions against a polymorphic belongs_to with an AR::Base value' do
+ first_person = Person.first
+ relation = Note.where { notable.not_eq first_person }
+ relation.to_sql.should match /\("notes"."notable_id" != #{first_person.id} OR "notes"."notable_type" != 'Person'\)/
+ end
+
+ it 'allows hash equality conditions against a belongs_to with an AR::Base value' do
+ first_person = Person.first
+ relation = Article.where(:person => first_person)
+ relation.to_sql.should match /"articles"."person_id" = #{first_person.id}/
+ end
+
+ it 'allows hash equality conditions against a polymorphic belongs_to with an AR::Base value' do
+ first_person = Person.first
+ relation = Note.where(:notable => first_person)
+ relation.to_sql.should match /"notes"."notable_id" = #{first_person.id} AND "notes"."notable_type" = 'Person'/
+ end
+
end
describe '#joins' do
View
5 spec/support/models.rb
@@ -3,7 +3,7 @@ class Person < ActiveRecord::Base
has_many :children, :class_name => 'Person', :foreign_key => :parent_id
has_many :articles
has_many :comments
- if ActiveRecord::VERSION::MAJOR == 4
+ if ActiveRecord::VERSION::MAJOR > 3
has_many :articles_with_condition, lambda { where :title => 'Condition' },
:class_name => 'Article'
has_many :article_comments_with_first_post,
@@ -17,9 +17,6 @@ class Person < ActiveRecord::Base
:through => :articles, :source => :comments
end
has_many :condition_article_comments, :through => :articles_with_condition, :source => :comments
- if ActiveRecord::VERSION::MAJOR == 4
- else
- end
has_many :authored_article_comments, :through => :articles,
:source => :comments
has_many :notes, :as => :notable
Please sign in to comment.
Something went wrong with that request. Please try again.