I have following models:
class Fruit < ApplicationRecord
has_many :taggings, as: :tagable, dependent: :destroy
has_many :tags, through: :taggings
class Tagging < ApplicationRecord
belongs_to :tag
belongs_to :tagable, polymorphic: true
class Tag < ApplicationRecord
has_many :taggings, dependent: :destroy
has_many :fruits,
through: :taggings,
source: :tagable,
source_type: 'Fruit'
Imagine I have Fruit 'banana' with tags 'yellow' (id: 182) AND 'long' (id: 168). And I need a query that would give me Fruit with both 'yellow' AND 'long' tags.
Banana indeed has two tags.
Fruit.find(97).tags
Fruit Load (4.1ms) SELECT "fruits".* FROM "fruits" WHERE "fruits"."id" = $1 ORDER BY name LIMIT $2 [["id", 97], ["LIMIT", 1]]
Tag Load (7.4ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."tagable_id" = $1 AND "taggings"."tagable_type" = $2 [["tagable_id", 97], ["tagable_type", "Fruit"]]
=> [#<Tag:0x000000010d50e9d0 id: 168, name: "long", created_at: Wed, 22 Dec 2021 16:54:32.290923000 UTC 00:00, updated_at: Wed, 22 Dec 2021 16:54:32.290923000 UTC 00:00>, #<Tag:0x000000010d50e868 id: 182, name: "yellow", created_at: Mon, 18 Apr 2022 16:59:30.244851000 UTC 00:00, updated_at: Mon, 18 Apr 2022 16:59:30.244851000 UTC 00:00>]
Asking like this:
irb(main):063:0> Fruit.joins(:tags).where(tags: { id: [168,182]}).to_sql
will get me
"SELECT \"fruits\".* FROM \"fruits\" INNER JOIN \"taggings\" ON \"taggings\".\"tagable_type\" = 'Fruit' AND \"taggings\".\"tagable_id\" = \"fruits\".\"id\" INNER JOIN \"tags\" ON \"tags\".\"id\" = \"taggings\".\"tag_id\" WHERE \"tags\".\"id\" IN (168, 182) ORDER BY name"
which is all the fruits with either tag.id 168 OR 182. So no go.
I've tried
Fruit.joins(:tags).where(tags: { id: 168 }).and(where(tags: { id: 182 }))
which will get me empty result.
Fruit Load (2.0ms) SELECT "fruits".* FROM "fruits" INNER JOIN "taggings" ON "taggings"."tagable_type" = $1 AND "taggings"."tagable_id" = "fruits"."id" INNER JOIN "tags" ON "tags"."id" = "taggings"."tag_id" WHERE "tags"."id" = $2 AND "tags"."id" = $3 ORDER BY name [["tagable_type", "Fruit"], ["id", 168], ["id", 182]]
=> []
Empty.
Also I've tried
f = Fruit.joins(:tags)
tags = [168,182]
tags.each do |tag|
f = f.where(tags: {id: tag})
end
Fruit Load (22.3ms) SELECT "fruits".* FROM "fruits" INNER JOIN "taggings" ON "taggings"."tagable_type" = $1 AND "taggings"."tagable_id" = "fruits"."id" INNER JOIN "tags" ON "tags"."id" = "taggings"."tag_id" WHERE "tags"."id" = $2 AND "tags"."id" = $3 ORDER BY name [["tagable_type", "Fruit"], ["id", 168], ["id", 182]]
Again, sad story, empty result.
Asking for one tag.id works, for one or another works as well, but I can't find a way to ask for both tags at the same time and get result.
Thank you
CodePudding user response:
What you need as a SQL GROUP BY, basically select all fruit that have either of the tags and then group them by fruit id and filter all that appear more than once. AFAIK this can't be done with one SQL query but rather with two, which could get tedious with ActiveRecord.
How about doing most of the hard work in Ruby instead?
fruit = Fruit.joins(:tags).where(tags: { id: [168,182]}).to_a # returns the fruit that have both 2 tags
both_tags_ids = fruit.group_by { |f| f.id }.select { |fruit_id, arr|
arr.count > 1
}.values.flatten