2009年9月30日水曜日

[Ruby on Rails]will_paginateで集計関数を使う場合の注意

will_paginateはページングを行うのに非常に便利なruby on railsのプラグインですが、ちょっと意図しない挙動があったのでご報告。

順番に追っていきます。

一番ベーシックな書法

@posts = Post.paginate({
:page => params[:page],
:order => 'created_at DESC'
})


条件をつけてみる
@posts = Post.paginate({
:conditions => ["posts.id = ?", params[:id]],
:page => params[:page],
:order => 'created_at DESC'
})


取得するカラムを指定してみる
@posts = Post.paginate({
:select => "posts.id, posts.title, posts.created_at",
:conditions => ["posts.id = ?", params[:id]],
:page => params[:page],
:order => 'created_at DESC'
})


データベース(MySQL)の集計関数(COUNT)を使ってみる
ここでひっかかった
@posts = Post.paginate({
:select => "COUNT(comments.id) as cnt, posts.id, posts.title, posts.created_at",
:joins => "LEFT JOIN comments ON posts.id = comments.post_id",
:conditions => ["posts.id = ?", params[:id]],
:group => "posts.id, posts.title, posts.created_at",
:page => params[:page],
:order => 'created_at DESC'
})

※ 全体の件数を返すクエリがなんかおかしい
SELECT
count(*) AS count_all,
posts.id,
posts.title,
posts.created_at
FROM
posts
LEFT JOIN comments ON posts.id = comments.post_id
WHERE
posts.id = '9999'
GROUP BY
posts.id,
posts.title,
posts.created_at

どう見ても全体の件数を返すクエリにはなっていません。
どうも、select やら joins やら group やらを指定するとうまく処理してくれない模様です。

それでいて全体件数 (@posts.total_entries) はちゃんとした値が入ってたりするので分かりにくいです。



これは自分で全体の件数を指定するしかないようですが、ちょっと書き直すだけで対応できるようなので一安心。

http://gitrdoc.com/mislav/will_paginate/tree/master/
@entries = WillPaginate::Collection.create(1, 10) do |pager|
result = Post.find(:all, :limit => pager.per_page, :offset => pager.offset)
# inject the result array into the paginated collection:
pager.replace(result)

unless pager.total_entries
# the pager didn't manage to guess the total count, do it manually
pager.total_entries = Post.count
end
end

createメソッドの第3引数には全体の件数(total)を指定できるので、ここに自分で集計した件数を指定してやればいいわけです。


以上をふまえて書き換えたのがこちら

[改良版] データベース(MySQL)の集計関数(COUNT)を使ってみる

total = Post.count({:conditions => ["posts.id = ?", params[:id]]})

@posts = WillPaginate::Collection.create((params[:page] || 1), 10, total) do |pager|
result = Post.find(:all, {
:select => "COUNT(comments.id) as cnt, posts.id, posts.title, posts.created_at",
:joins => "LEFT JOIN comments ON posts.id = comments.post_id",
:conditions => ["posts.id = ?", params[:id]],
:group => "posts.id, posts.title, posts.created_at",
:limit => pager.per_page,
:offset => pager.offset
})
pager.replace(result)
end




[関連記事]
ActiveRecord以外でwill_paginateを使う方法
will_paginateの便利なViewヘルパー

0 件のコメント: