RailsでItem has many Tagの時にTagに対する条件のANDをしたい
やりたいこと
# Item has_many :item_tag_relations, dependent: :destroy has_many :tags, through: :item_tag_relations, dependent: :destroy
# ItemTagRelation belongs_to :item belongs_to :tag
- Tag name: string
という状況で「"red"と"blue"と"yellow"全てのnameのTagを持ったItem」が欲しいという状況がある。 単純に考えて
names = ['red', 'blue', 'yellow'] tag_ids = Tag.where(name: names).select(:id) item_ids = ItemTagRelation.where(tag_id: tag_ids).select(:item_id) @items = Item.where(id: item_ids)
としても「"red"と"blue"と"yellow"のいずれかのnameのTagを持ったA」になってしまい、これは条件のORである。 これを「"red"と"blue"と"yellow"全てのnameのTagを持ったItem」という形で条件のANDにしたかった。
やりかた
names = ['red', 'blue', 'yellow'] item_ids = Item.joins(:tags) .where(tags: { name: names }) .group('items.id') .having('count(distinct tags.name) = ?', names.uniq.count) @items = Item.where(id: item_ids)
やってることは単純で、joinした後に条件をクリアしたTagのnameによる重複を排除した数と条件の数が等しいならそれは全ての条件をクリアしているということなのでそれを利用している。