5

I'm using Kaminari to paginate some result from a query in which I'm selecting distinct records. Consider the following controller code:

@things = Thing.joins... # create a complex query that produces duplicate results

# I want to select distinct results: this produces the correct results
@things = @things.select("DISTINCT things.*")

# when Kaminari calls count, it will run "SELECT COUNT(*)", instead of 
# "SELECT COUNT(DISTINCT things.*)" we will get the wrong count and extra pages
@things = @things.page(params[:page]).per(10)

The best solution that I can think of is to pass :distinct => true to count, like in this pull request, which was rejected by Kaminari's developer. This SO question discusses the underlying problem. This line of code is the offending call to count.

Are there any workarounds that will provide Kaminari with the correct count that don't involve patching Kaminari? Thanks.

UPDATE:

  • Using a scope called "count" is a great suggestion but doesn't work when called on an ActiveRecord::Relation. It works when called on my model class, but that doesn't help.
Community
  • 1
  • 1
balexand
  • 9,549
  • 7
  • 41
  • 36

2 Answers2

3

please refer to the following url.

https://github.com/amatsuda/kaminari/pull/77

https://github.com/tbeauvais/kaminari/commit/23695cbdc4ff1b9fa58c18d4a3c2f18e21451b8b but ,they faild on Rails 3.1.0.

For Rails 3.1.0, create Rails.root/initializers/kaminari_for_distinct.rb. And use the following code.

module Kaminari
  module ActiveRecordRelationMethods
    extend ActiveSupport::Concern
    module InstanceMethods
      def total_count #:nodoc:
        if distinct_column_name.nil?
          c = except(:offset, :limit).count
        else  
          c = except(:offset, :limit).count(distinct_column_name, :distinct => true)
        end
        # .group returns an OrderdHash that responds to #count
        c.respond_to?(:count) ? c.count : c
      end

      # Get the column name used in distinct query.
      # This could have been set on the Model class, or the ActiveRecord::Relation 
      def distinct_column_name
        @distinct_column || distinct_column
      end
    end
  end
end

module Kaminari
  module ConfigurationMethods
    extend ActiveSupport::Concern
    module ClassMethods

      # Set the name of the column to use during .count()
      # Setting this will cause call to count to use: count(:id, :distinct => true) for all the Models paged queries. 
      # Example:
      #   class User < ActiveRecord::Base
      #     use_distinct :id
      #   end
      def use_distinct(column)
        @distinct_column = column
      end

      # Returns the distinct column name set on the Model, or nil if not using distinct
      def distinct_column
        @distinct_column
      end
    end
  end
end


module Kaminari
  module PageScopeMethods
    extend ActiveSupport::Concern
    module InstanceMethods

      # Set the name of the column to use during .count()
      # Setting this will cause call to count to use: count(:id, :distinct => true)
      # Example: User.page(3).per(5).use_distinct(:id)
      def use_distinct(column)
        @distinct_column = column
        self
      end
    end
  end
end
0

I would suggest a scope on the Model

This might confuse things though so you want to be careful

scope :count, select("DISTINCT things.*")

For more information look here

LeakyBucket
  • 160
  • 1
  • 11
  • 1
    Is this supposed to be more than a comment? – apneadiving Jul 14 '11 at 20:21
  • Yes, if the OP were to define a scope on the Model and name it count then they would be able to control what the library does when it tries to get a count from the model. – LeakyBucket Jul 14 '11 at 20:26
  • continue to give details and this could be ok: answers are supposed to be clear. Otherwise, give hints as comments. – apneadiving Jul 14 '11 at 20:27
  • Do you mean create a scope called "count" that calls the real count method with :distinct => true? I tried that but was not able to get it working (see gist https://gist.github.com/1083358). That would be an excellent solution if I could get it working. – balexand Jul 14 '11 at 20:30
  • Try a straight up scope rather than aliasing. It might make a difference. – LeakyBucket Jul 14 '11 at 20:44
  • Same thing with the straight up scope (scope :count, lambda { ... }). Thing.count calls my block but Thing.scoped.count or Thing.where(...).count calls the original. – balexand Jul 14 '11 at 21:09
  • Is your pagination library making those calls too? – LeakyBucket Jul 14 '11 at 21:14
  • I'm making a call to where and joins, and Kaminari is making calls to except and reorder (https://github.com/amatsuda/kaminari/blob/master/lib/kaminari/models/active_record_relation_methods.rb#L21). Any calls to count on an ActiveRecord::Relation (and not to my model class), go to the original count. – balexand Jul 14 '11 at 21:17
  • Right makes sense, what about Ruby's built in [set functionality](http://www.ruby-doc.org/stdlib/libdoc/set/rdoc/index.html)? Would that let you remove duplicates from your collection before you paginate? It's probably going to be pretty slow if you do it in Ruby rather than letting the database handle it though. – LeakyBucket Jul 14 '11 at 21:28
  • @balexand let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1483/discussion-between-leakybucket-and-balexand) – LeakyBucket Jul 14 '11 at 21:36