月別アーカイブ: 2012年12月

Ruby on Rails 3.2 Arel + Postgresql のgroup – having節でエラー

やりたかったのは人物の検索結果を出すことです。
検索条件として、例えば男性で、東京在住であるというような、その人物の「属性」で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節がサブクエリになったことでエラーは出なくなりました。

jquery UI jQuery UI – v1.8.23 Dialogの画面スクロールに固定させる

jquery UI の Dialogは基本的にDialogの箱を position: absolute にしている。

スクロールするとダイアログはそのまま置いていかれ、画面の外にいってしまう。

それが困るので、画面に固定させる方法を色々試してみました。

最も簡単な方法が position: fixedにする方法です。

[javascript]

box = dialog.parent() //dialogはdialog()メソッドを呼んだ後のjqueryオブジェクト
box.css({position: ‘fixed’})
[/javascript]

しかし、これだとリサイズした際にjquery UIがpositionをabsoluteに戻してしまうらしく、バグが起きました。
というわけでリサイズした後に再度position: fixedに戻してやることで対応しました。

[javascript]
function fix(){
box = dialog.parent()
if(box.css(‘position’) == ‘absolute’){
top = parseInt(box.css(‘top’)) – $(window).scrollTop()
left = parseInt(box.css(‘left’)) – $(window).scrollLeft()
box.css({position: ‘fixed’, top: top + ‘px’, left: left + ‘px’})
}
}
fix()
dialog.on(‘dialogresizestop’) = function(e){
fix()
}
[/javascript]

ポイントはposition: absoluteと position: fixedではleftおよびtopが
相対値と絶対値という違いがあるのでwindowのスクロールポジションから補正してあげることです。

この記事は jquery UI -v 1.8.23にて検証したものです。
それ以外のバージョンでは状況が違う可能性があります。