Home > Net >  ActiveRecord AND query for array of argumens
ActiveRecord AND query for array of argumens

Time:04-26

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