Railsのpaginateでdistinctを扱う
ActiveRecordでdistinctを伴うpaginateをやろうとするとなかなかうまく行きません。
いろいろいじくってやり方を編み出したのでメモ。
まず、ActiveRecordでfindのdistinctを扱うには下記のようにします。
>> RubricksUser.find(:all, :select => 'distinct *') SELECT distinct * FROM rubricks_users
また、countのdistinctを扱うには下記のようにします。
>> RubricksUser.count(:distinct => true, :select => 'id') SELECT count(DISTINCT id) AS count_id FROM rubricks_users
さて、paginateでdistinctを使うにはどうすればいいか。
今回やった方法は下記のような感じです。
@pages, @user_list = paginate(:rubricks_user, :per_page => 5, :select => "distinct *", :distinct => true, :count => 'id' )
paginateでは検索結果を得るためのfindとページ番号を算出するためのcountが両方発行されます。そのため、双方のオプションを重ね合わせるようにパラメータを渡す必要があります。
つまり、:selectはAR.findへ、:distinctはAR.countへ渡されます。なお、:countオプションというのはAR.countの:selectオプションに渡されるものです。
ただし、この方法はそのままでは通らず、エラーが出ます。
paginateの中で
(1):distinctオプションがなぜか不正としてはじかれてしまう
#pagination.rb (distinctがない) DEFAULT_OPTIONS = { :class_name => nil, :singular_name => nil, :per_page => 10, :conditions => nil, :order_by => nil, :order => nil, :join => nil, :joins => nil, :count => nil, :include => nil, :select => nil, :parameter => 'page' } ... def self.validate_options!(collection_id, options, in_action) #:nodoc: options.merge!(DEFAULT_OPTIONS) {|key, old, new| old} valid_options = DEFAULT_OPTIONS.keys valid_options << :actions unless in_action unknown_option_keys = options.keys - valid_options raise ActionController::ActionControllerError, "Unknown options: #{unknown_option_keys.join(', ')}" unless unknown_option_keys.empty? options[:singular_name] ||= Inflector.singularize(collection_id.to_s) options[:class_name] ||= Inflector.camelize(options[:singular_name]) end
(2)paginateからAR.countに:distinctが渡されない
#pagination.rb def count_collection_for_pagination(model, options) model.count(:conditions => options[:conditions], :joins => options[:join] || options[:joins], :include => options[:include], :select => options[:count]) end
という実装になっているためです。
そこで、下記のように強引にpaginate周りをいじる必要があります。
#pluginなどでクラスを再オープンして定義 module ActionController module Pagination DEFAULT_OPTIONS[:distinct] = false def count_collection_for_pagination(model, options) model.count(:conditions => options[:conditions], :joins => options[:join] || options[:joins], :include => options[:include], :select => options[:count], :distinct => options[:distinct]) end end end
クラス再オープンしている時点で終わってる気がします。もっとスマートなやり方知ってる人は教えてください。
なければRailsにパッチ送ろうかなぁ。
(Shouta)