public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Allow conditions on multiple tables to be specified using hash.

Examples:

  User.all :joins => :items, :conditions => { :age => 10, :items => { :color => 
  'black' } }
  Item.first :conditions => { :items => { :color => 'red' } }

Note : Hash key in :conditions is referring to the actual table name or the 
alias defined in query.
lifo (author)
Fri Jun 27 17:27:25 -0700 2008
commit  cd994eff9a343df376bfaec59de5b24a2ab51256
tree    f18d158c5d11cbba33784a136b0276f8237c6a8b
parent  582bff71c465075d01b6e062d64b13ac3df4ad56
...
1
2
 
 
 
 
 
3
4
5
...
1
2
3
4
5
6
7
8
9
10
0
@@ -1,5 +1,10 @@
0
 *Edge*
0
 
0
+* Allow conditions on multiple tables to be specified using hash. [Pratik Naik]. Example:
0
+
0
+  User.all :joins => :items, :conditions => { :age => 10, :items => { :color => 'black' } }
0
+  Item.first :conditions => { :items => { :color => 'red' } }
0
+
0
 * Always treat integer :limit as byte length.  #420 [Tarmo Tänav]
0
 
0
 * Partial updates don't update lock_version if nothing changed.  #426 [Daniel Morrison]
...
1999
2000
2001
 
 
2002
2003
2004
2005
 
2006
2007
2008
2009
 
 
 
 
 
 
 
 
2010
2011
2012
2013
2014
 
2015
2016
 
2017
2018
2019
2020
2021
2022
...
2070
2071
2072
 
 
2073
2074
2075
...
1999
2000
2001
2002
2003
2004
2005
2006
 
2007
2008
2009
2010
 
2011
2012
2013
2014
2015
2016
2017
2018
2019
 
 
 
 
2020
2021
 
2022
2023
 
 
2024
2025
2026
...
2074
2075
2076
2077
2078
2079
2080
2081
0
@@ -1999,24 +1999,28 @@ module ActiveRecord #:nodoc:
0
         #     # => "age BETWEEN 13 AND 18"
0
         #   { 'other_records.id' => 7 }
0
         #     # => "`other_records`.`id` = 7"
0
+        #   { :other_records => { :id => 7 } }
0
+        #     # => "`other_records`.`id` = 7"
0
         # And for value objects on a composed_of relationship:
0
         #   { :address => Address.new("123 abc st.", "chicago") }
0
         #     # => "address_street='123 abc st.' and address_city='chicago'"
0
-        def sanitize_sql_hash_for_conditions(attrs)
0
+        def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
0
           attrs = expand_hash_conditions_for_aggregates(attrs)
0
 
0
           conditions = attrs.map do |attr, value|
0
-            attr = attr.to_s
0
+            unless value.is_a?(Hash)
0
+              attr = attr.to_s
0
+
0
+              # Extract table name from qualified attribute names.
0
+              if attr.include?('.')
0
+                table_name, attr = attr.split('.', 2)
0
+                table_name = connection.quote_table_name(table_name)
0
+              end
0
 
0
-            # Extract table name from qualified attribute names.
0
-            if attr.include?('.')
0
-              table_name, attr = attr.split('.', 2)
0
-              table_name = connection.quote_table_name(table_name)
0
+              "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
0
             else
0
-              table_name = quoted_table_name
0
+              sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
0
             end
0
-
0
-            "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
0
           end.join(' AND ')
0
 
0
           replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
0
@@ -2070,6 +2074,8 @@ module ActiveRecord #:nodoc:
0
           expanded = []
0
 
0
           bind_vars.each do |var|
0
+            next if var.is_a?(Hash)
0
+
0
             if var.is_a?(Range)
0
               expanded << var.first
0
               expanded << var.last
...
200
201
202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
204
205
...
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
0
@@ -200,6 +200,23 @@ class FinderTest < ActiveRecord::TestCase
0
     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
0
   end
0
 
0
+  def test_find_on_hash_conditions_with_hashed_table_name
0
+    assert Topic.find(1, :conditions => {:topics => { :approved => false }})
0
+    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) }
0
+  end
0
+
0
+  def test_find_with_hash_conditions_on_joined_table
0
+    firms = Firm.all :joins => :account, :conditions => {:accounts => { :credit_limit => 50 }}
0
+    assert_equal 1, firms.size
0
+    assert_equal companies(:first_firm), firms.first
0
+  end
0
+
0
+  def test_find_with_hash_conditions_on_joined_table_and_with_range
0
+    firms = DependentFirm.all :joins => :account, :conditions => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
0
+    assert_equal 1, firms.size
0
+    assert_equal companies(:rails_core), firms.first
0
+  end
0
+
0
   def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
0
     david = customers(:david)
0
     assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address })

Comments

wwood Fri Jul 25 21:04:41 -0700 2008

Hi,

Cool. This is very useful because it allows me to not have to worry about the name of the table when I join to a table and then back again, eg. like finding another book on the same bookshelf:

Books.all( :joins => {:bookshelf => :books}, :conditions => {:bookshelves => {:books => {:title => ‘Another Book on the Same Shelf’}}} )

Without having to worry about the name of the second book table in the SQL like I used to. So thanks!

I have 2 suggestions, though.

  • I don’t seem to find that it works with :include, only :joins. Is there any reason for this?
  • To me it makes more intuitive sense that the symbols are the same as the :joins, so it would be :conditions => {:bookshelf => instead of :conditions => :bookshelves as is currently the case.

But overall, very useful. ben

wwood Fri Jul 25 21:31:18 -0700 2008

Actually, sorry to be a pain, but the above seems to actually illustrate a bug. Say I’m trying to get the names of all the books on a particular shelf (which are book1 and book2), and all I have is the name of one of the books (book1).

Using your method, I would


Book.all(
  :joins => {:bookshelf => :books},
  :conditions => {:bookshelves => {:books => {:title => 'book1'}}}
).collect{|b| b.title}

I end up getting <pre> => ["book1", "book1"] </pre>

I suppose this is a bug, but I’m too lazy to file it on lighthouse.

anildigital Mon Aug 04 03:35:15 -0700 2008

Thanks Pratik