Skip to content

Commit

Permalink
Allow conditions on multiple tables to be specified using hash.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lifo committed Jun 28, 2008
1 parent 582bff7 commit cd994ef
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 9 deletions.
5 changes: 5 additions & 0 deletions activerecord/CHANGELOG
@@ -1,5 +1,10 @@
*Edge*

* Allow conditions on multiple tables to be specified using hash. [Pratik Naik]. Example:

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

* Always treat integer :limit as byte length. #420 [Tarmo Tänav]

* Partial updates don't update lock_version if nothing changed. #426 [Daniel Morrison]
Expand Down
24 changes: 15 additions & 9 deletions activerecord/lib/active_record/base.rb
Expand Up @@ -1999,24 +1999,28 @@ def expand_hash_conditions_for_aggregates(attrs)
# # => "age BETWEEN 13 AND 18"
# { 'other_records.id' => 7 }
# # => "`other_records`.`id` = 7"
# { :other_records => { :id => 7 } }
# # => "`other_records`.`id` = 7"
# And for value objects on a composed_of relationship:
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
def sanitize_sql_hash_for_conditions(attrs)
def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)

conditions = attrs.map do |attr, value|
attr = attr.to_s
unless value.is_a?(Hash)
attr = attr.to_s

# Extract table name from qualified attribute names.
if attr.include?('.')
table_name, attr = attr.split('.', 2)
table_name = connection.quote_table_name(table_name)
end

# Extract table name from qualified attribute names.
if attr.include?('.')
table_name, attr = attr.split('.', 2)
table_name = connection.quote_table_name(table_name)
"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
else
table_name = quoted_table_name
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
end

"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
end.join(' AND ')

replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
Expand Down Expand Up @@ -2070,6 +2074,8 @@ def expand_range_bind_variables(bind_vars) #:nodoc:
expanded = []

bind_vars.each do |var|
next if var.is_a?(Hash)

if var.is_a?(Range)
expanded << var.first
expanded << var.last
Expand Down
17 changes: 17 additions & 0 deletions activerecord/test/cases/finder_test.rb
Expand Up @@ -200,6 +200,23 @@ def test_find_on_hash_conditions_with_explicit_table_name
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
end

def test_find_on_hash_conditions_with_hashed_table_name
assert Topic.find(1, :conditions => {:topics => { :approved => false }})
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) }
end

def test_find_with_hash_conditions_on_joined_table
firms = Firm.all :joins => :account, :conditions => {:accounts => { :credit_limit => 50 }}
assert_equal 1, firms.size
assert_equal companies(:first_firm), firms.first
end

def test_find_with_hash_conditions_on_joined_table_and_with_range
firms = DependentFirm.all :joins => :account, :conditions => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
assert_equal 1, firms.size
assert_equal companies(:rails_core), firms.first
end

def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
david = customers(:david)
assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address })
Expand Down

4 comments on commit cd994ef

@wwood
Copy link

@wwood wwood commented on cd994ef Jul 26, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link

@wwood wwood commented on cd994ef Jul 26, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

=> ["book1", "book1"]

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

@wwood
Copy link

@wwood wwood commented on cd994ef Jul 26, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug report:

http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/710-condition-hashes-through-joins-gives-fails-when-circular-references-are-used

@anildigital
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Pratik

Please sign in to comment.