I'm trying to do a simple Post/Tags relation in rails 3. Everything working fine except when I want to query the Posts which are related to several tags. Basically I'd like to do this kind of thing :
Post.joins(:tags).where('tags.name ILIKE ?', var)
But instead of having just one var, I'd like to use an array. I tried :
Post.joins(:tags).where('tags.name IN (?)', names_array)
But unfortunately it does a simple LIKE (not ILIKE) and works like a OR condition which sounds perfectly logical.
I also found another solution by using find_by_sql in this post http://snippets.dzone.com/posts/show/32
But it seems a bit ugly to me.
To better understand the problem. I've got 3 posts : PostA PostB PostC
PostA is related to TagA TagB and TagC tags. PostB is related to TagA and TagB tags. PostC is only related to TagA.
If I look for TagA and TagC Posts I'd like to finds PostA because it is related to both Tags. Using a hash condition returns PostA PostB and PostC. What I want is the Posts which are related to "at least" all the specified Tags.
So anyone has a better way to handle this ?
Thanks.
I don't know if it will solve you problem but for complex queries like that I almost always just use Squeel.
Then do something like this:
@posts = Post.joins(:tags)
.where{tags.name.like_any names_array}
.group("post_id")
.having("count(post_id) = #{names_array.size}")
The SQL hopefully looks something like this
SELECT "posts".* FROM "posts"
INNER JOIN "tags" ON "tags"."post_id" = "posts"."id"
WHERE (("tags"."name" LIKE "TagA" OR "tags"."name" LIKE "TagB"))
GROUP BY post_id
HAVING count(post_id) = 2
If I remember squeel is pretty good at using ILIKE instead of LIKE depending on the database used. (atleast better than AR)
Also you could do this using AR without squeel but I REALLY like some of the ideas and helpers that come with squeel like _all
As for an explination...
Assume I searched for TagsA and B.
What that does is finds all the Posts with those tags.
So you'll have something like:
- PostA TagA
- PostA TagB
- PostB TagA
- PostB TagB
- PostC TagA
Then it will group all those different Post results by the joined tags using post_id.
- PostA TagA TagB
- PostB TagA TagB
- PostC TagA
Then it will check the number of Tags the SQL line has by checking how many forgien_ids are present. Since A and B have 2 tags you know it matched all you input.
Ok so I found no way to avoid a find_by_sql. Here is what I've done.
@posts = Post.find_by_sql([ "SELECT * FROM posts p
JOIN (
SELECT pt.post_id FROM posts_tags pt
JOIN posts p ON p.id = pt.post_id
JOIN tags t ON t.id = pt.tag_id
WHERE t.label IN (?)
GROUP BY pt.post_id
HAVING count(pt.post_id) = ?
) ct ON c.id = ct.post_id", names_array, names_array.size])
I personally don't completely understand this query (found on http://www.sergiy.ca/how-to-write-many-to-many-search-queries-in-mysql-and-hibernate/ - #3). Especially the part where it joins a select. So if anyone could explain how really work this query it would be great.
Further more, if anyone knows how to do this in a more "rails" way (than a hard coded query), I'd love it.
Hope this helps some people.
I'm stuck on the same problem. The squeel docuemtnation suggests this is possible. In the Compound Conditions section he lists three ways to do something similar.
Given
names = ['Ernie%', 'Joe%', 'Mary%']
Then you can do
Person.where('name LIKE ? OR name LIKE ? OR name LIKE ?', *names)
or
Person.where((['name LIKE ?'] * names.size).join(' OR '), *names)
or
Person.where{name.like_any names}
The documentation implies we can use AND instead of OR or like_all as opposed to like_any.
However I can't seem to get it to work for a habtm relationship. It keeps giving me undefined method 'call' for an instance of ActiveRecord.
来源:https://stackoverflow.com/questions/8726564/rails-3-many-to-many-query-condition