Reputation: 203
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.
Upvotes: 2
Views: 2806
Reputation: 475
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.
Upvotes: 0
Reputation: 4383
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:
Then it will group all those different Post results by the joined tags using post_id.
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.
Upvotes: 2
Reputation: 203
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.
Upvotes: 0