Elendir
Elendir

Reputation: 203

Rails 3 many to many query condition

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

Answers (3)

hershey
hershey

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

Azolo
Azolo

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:

  • 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.

Upvotes: 2

Elendir
Elendir

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

Related Questions