やりたかったのは人物の検索結果を出すことです。
検索条件として、例えば男性で、東京在住であるというような、その人物の「属性」でand検索するというものになります。
いきさつははしょりますが、簡単に言うとsqliteでの開発段階でgroup having節を使っていた部分が、postgresqlに変更してからエラーがでたということが発端になります。
postgresqlでは
must appear in the GROUP BY clause or be used in an aggregate function
というエラーがでて、目的のことをやるには一旦select文を分割せねばならないようでした。
SQLを直接書けばそれ自体は難しくないのですが、なんとかArelをできるだけ活用してできないものかと試行錯誤しました。
ハードルはどのようにサブクエリをarelで組み立てるのかということ。
この問題に関してはこちらの記事が参考になりました。
まず、クラスにたいしarel_tableメソッドでArel::Tableクラスのインスタンスを取り出します。
※pattributes_peopleテーブルに対しPattributePersonクラスです。察しの通り、これはPersonとPattributeクラスの関連クラス
sub = PattributePerson.arel_table
次に条件のひとつとして使用するArel::Attributes::Attributeクラスのインスタンスを作成します
※pattributes は[‘1′,’2’,…] のような属性のIDが入った配列です。
AND検索なのでinにしたいところですが、ここでは一旦in_anyで該当する行をすべて取り出しておきます。
attr = sub[:pattribute_id].in_any(pattributes)
最後にサブクエリの本体となるArel::SelectManagerクラスのインスタンスを取り出します。
select_manager = sub.project(table[:person_id]).where(attr).group(:person_id).having(“COUNT(person_id) >= #{pattributes.length}”)
→pattribute_idがpattributeのリストに一致するものすべてを取り出しておいて、person_idが重複しないように圧縮します。
そのときperson_idがpattributesの数分だけあるもののみ取り出しています。つまり、pattributeが全部該当した者=ANDと同じ。
これを他のArel::Tableでくっつけてひとつの条件(Arel::Attributes::Attributeクラスのインスタンス)を作成します。
person_table = Person.arel_table
attr2 = person_table[:id].in(select_manager)
あとはこのままpeopleとして取得するなり、他の条件と結合するなりできます。
@result = Person.includes(:user)
@result.where(attr2)
group節がサブクエリになったことでエラーは出なくなりました。