Reputation: 5920
In my ROR app I the have the models Category, Item, Property and PropertyValuation. The idea is that a category contains items, and an item has several properties. PropertyValuation purpose is to store the property value for the specific items. The models are defined as above:
class Category < ActiveRecord::Base
attr_accessible :name, :description, :parent, :children, :items, :parent_id
has_many :children, :class_name => "Category", :foreign_key => "parent_id", :dependent => :nullify
belongs_to :parent, :class_name => "Category"
has_many :categorizations
has_many :items, :through => :categorizations
end
class Item < ActiveRecord::Base
attr_accessible :name, :description, :property_valuations, :barcode
has_many :property_valuations, :dependent => :destroy
has_many :properties, :through => :property_valuations
has_many :categorizations
has_many :categories, :through => :categorizations
end
class Property < ActiveRecord::Base
attr_accessible :name, :description, :value_type, :unit, :unit_id
has_many :property_valuations, :dependent => :destroy
has_many :items, :through => :property_valuations
has_many :property_ranges, :dependent => :destroy
belongs_to :unit
end
class PropertyValuation < ActiveRecord::Base
attr_accessible :property, :item, :value, :categorization
belongs_to :property
belongs_to :item
end
Now my question, I've successfully managed to filter categories items by name by doing this:
@category.items.where("lower(items.name) like ?", "%#{params[:keywords].downcase}%")
But now I also want to filter those items depending on the associated property value. Example: I want the category items whose name contains "foo", and where property "A" has value 1, property B has value 2, and so on. How can I implement such a query?
Upvotes: 2
Views: 11260
Reputation: 93
A gem that exists to do that: activerecord_where_assoc (I'm the author)
With it, you can do what you want this way:
I want the category items whose name contains "foo", and where property "A" has value 1, property B has value 2, and so on.
@category.items.where(name: "foo").where_assoc_exists(:property_valuations) { |pv|
pv.where(value: 1).where_assoc_exists(:property, name: 'A')
}.where_assoc_exists(:property_valuations) { |pv|
pv.where(value: 2).where_assoc_exists(:property, name: 'B')
}
This shows the greatest power of the gem, it's just reusable without conflicts. Doing this with a joins would be a pain. However, when the need is complex, the result also is dense... This can be solved easily with scopes.
# In items:
scope :with_property, lambda {|name, value|
where_assoc_exists(:property_valuations) { |pv|
pv.where(value: value).where_assoc_exists(:property, name: name)
}}
@category.items.where(name: "foo").with_property('A', 1).with_property('B', 2)
If you wanted to make a more powerful scope that can receive an operator, you could do that by just changing the where(value: value)
part to fit your need.
Here are the introduction and examples. Read more details in the documentation.
Upvotes: 0
Reputation: 3766
You should join the tables together, and then limit based on your criteria.
# Category items by name
Category.joins(:items).where(:items => { :name => keywords })
You may find http://guides.rubyonrails.org/active_record_querying.html#joining-tables and calling .to_sql
helpful.
Upvotes: 2
Reputation: 29880
You can chain ActiveRecord scopes, including where
. So you can limit the names first, and then chain an additional where
to limit the results. This example below will limit the results where property "A" has value 1, as mentioned in your question:
keywords = params[:keywords].downcase
@category.items.where("lower(items.name) like ?", "%#{keywords}%").where(:A => 1)
You can also store scopes in a variable. For example if you wanted to limit the same dataset by properties A and B separately, you might do something like this:
keywords = params[:keywords].downcase
matched_by_name = @category.items.where("lower(items.name) like ?", "%#{keywords}%")
foo = matches_by_name.where(:A => 1)
bar = matches_by_name.where(:B => 1)
Upvotes: 0