简体   繁体   中英

ActiveRecord AND query for array of argumens

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM