When viewing the show page for a given botanical, I would like to display a list of gins that also contain that specific botanical.
Simple. Just do:
@botanical = Botanical.includes(:gins).find(params[:id])
@gins = @botanical.gins
If you have an id already there is no need for a LIKE
query. And since you have setup an indirect association you can just use it to fetch the gins.
If what you really want is to get other gins which have botanicals in common to a given gin you would do it something like this:
class Gin < ApplicationRecord
has_many :gin_botanicals
has_many :botanicals, through: :gin_botanicals
def similiar_gins
Gin.joins(:botanicals)
.where(botanicals: { id: self.botanical_ids })
.where.not(id: self.id)
end
end
.joins
creates a left inner join - so any rows that do not have matches in the join table are discarded.
.where(botanicals: { id: self.botanical_ids })
creates a WHERE IN
query that requires the joined records to have a least one botanical in common.
You can also set a required level of simularity by using GROUP BY and HAVING:
class Gin < ApplicationRecord
has_many :gin_botanicals
has_many :botanicals, through: :gin_botanicals
def similiar_gins(common_ingredients: 1)
Gin.joins(:botanicals)
.where(botanicals: { id: self.botanical_ids })
.where.not(id: self.id)
.group("gins.id")
.having("COUNT(distinct botanicals.id) >= ?", common_ingredients)
end
end
Given:
irb(main):039:0> Gin.all.pluck(:id, :name)
(1.1ms) SELECT "gins"."id", "gins"."name" FROM "gins"
=> [[1, "Beefeater Gin"], [2, "Bombay Sapphire"], [3, "Mockinghamshire"]]
irb(main):040:0> Botanical.all.pluck(:id, :name)
(1.1ms) SELECT "botanicals"."id", "botanicals"."name" FROM "botanicals"
=> [[1, "Almond"], [2, "liquorice"], [3, "Foo"]]
irb(main):041:0> GinBotanical.all.pluck(:gin_id, :botanical_id)
(0.5ms) SELECT "gin_botanicals"."gin_id", "gin_botanicals"."botanical_id" FROM "gin_botanicals"
=> [[1, 1], [2, 1], [1, 3], [1, 2], [2, 2]]
With 2 common ingredients:
irb(main):036:0> Gin.first.similiar_gins(common_ingredients: 2)
Gin Load (1.2ms) SELECT "gins".* FROM "gins" ORDER BY "gins"."id" ASC LIMIT $1 [["LIMIT", 1]]
(4.0ms) SELECT "botanicals".id FROM "botanicals" INNER JOIN "gin_botanicals" ON "botanicals"."id" = "gin_botanicals"."botanical_id" WHERE "gin_botanicals"."gin_id" = $1 [["gin_id", 1]]
Gin Load (4.3ms) SELECT "gins".* FROM "gins" INNER JOIN "gin_botanicals" ON "gin_botanicals"."gin_id" = "gins"."id" INNER JOIN "botanicals" ON "botanicals"."id" = "gin_botanicals"."botanical_id" WHERE "botanicals"."id" IN (1, 2, 3) AND ("gins"."id" != $1) GROUP BY gins.id HAVING (COUNT(distinct botanicals.id) >= 2) [["id", 1]]
=> #<ActiveRecord::Relation [#<Gin id: 2, name: "Bombay Sapphire", created_at: "2018-03-07 23:44:43", updated_at: "2018-03-07 23:44:43">]>
But if we set it to 3 we get an empty set:
irb(main):037:0> Gin.first.similiar_gins(common_ingredients: 3)
Gin Load (0.7ms) SELECT "gins".* FROM "gins" ORDER BY "gins"."id" ASC LIMIT $1 [["LIMIT", 1]]
(1.8ms) SELECT "botanicals".id FROM "botanicals" INNER JOIN "gin_botanicals" ON "botanicals"."id" = "gin_botanicals"."botanical_id" WHERE "gin_botanicals"."gin_id" = $1 [["gin_id", 1]]
Gin Load (5.0ms) SELECT "gins".* FROM "gins" INNER JOIN "gin_botanicals" ON "gin_botanicals"."gin_id" = "gins"."id" INNER JOIN "botanicals" ON "botanicals"."id" = "gin_botanicals"."botanical_id" WHERE "botanicals"."id" IN (1, 2, 3) AND ("gins"."id" != $1) GROUP BY gins.id HAVING (COUNT(distinct botanicals.id) >= 3) [["id", 1]]
=> #<ActiveRecord::Relation []>