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
What you need is 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 (a query and a subquery), 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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.