How to get associated items through a middle class in Rails ActiveRecord
I think this may be a pretty simple ActiveRecord problem to solve but if I have a query like:
@store = Store.find params[:id]
@categories = @store.categories.all
where say @categories = @store.categories.all
returns like 3 objects. I then want to do a query where 开发者_Python百科I get all of the associated items
that have that same category_id, I tried:
@categories.items
But it didn't work, any ideas?
I think what you're looking for is, say, if we have category 1, category 2, and category 3, you want all items that belong to any of those categories. Is that right?
This could be solved via iteration or manual conditions on a single query, but it sounds like the job for has_many :through
.
class Store < ActiveRecord::Base
has_many :items, :through => :categories
end
After that, @store.items
should return all items that belong to any of the store's categories. A store has a relationship with certain items through the category object.
If you're wary of creating that relationship (though it's definitely the simplest and least fragile way to go), you could also do this procedurally. The naive approach would be to run the items
method on each category and put the results all in one array, but that would run one query per category: the infamous N+1 problem. If there are 50 categories, then that's 50 queries just to fetch the items. You'd be better off running just one query to find the items you want:
@items = Item.where(:category_id => @categories.map(&:id)).all
(I'm not totally sure on that being the Arel syntax for the classic SQL WHERE…IN
clause, but I think that's it.)
@categories.map(&:id)
returns an array of all the categories' IDs, which is passed as the condition for the item's category_id
. So, in English: "find all items where category_id is in this list of category IDs"
So, there are two one-query solutions that perform about equally well. Go for the one that feels cleaner to you :)
Try this one:
@categories.map(&:items)
It doesn't work because @categories
is an array, not a category object. You can do:
items = []
@categories.each do |category|
items = items.concat(category.items)
end
@categories
is an array of Category
items, but the wireup function doesn't exist on the array itself. You need to iterate the array of categories and collect the related items:
@items = @categories.map(&:items).flatten
map
by itself will return an array of arrays, each corresponding to the output of category.items
, so you need to call flatten
to create a single array of items.
This is a solution to your immediate problem, but @Matchu's post is how you should be writing it.
精彩评论