6

I am attempting to paginate a shuffled ActiveRecord query. The syntax for doing this using the Kaminari gem is:

@users = Kaminari.paginate_array(User.all.shuffle).page(params[:page]).per(20)

The issue with this is that User.all is re-shuffled on each pagination request, causing duplicate records to be called. Is there any way to prevent this kind of duplication?

Michael Berkowski
  • 267,341
  • 46
  • 444
  • 390
neon
  • 2,811
  • 6
  • 30
  • 44
  • 1
    Calling `User.all` can cause your server to slow down significantly. Even if you have only 100's of users you will pay the unnecessary cost of bringing all the users to the ruby memory space in every request. – Harish Shetty Apr 09 '12 at 02:40

3 Answers3

5

You need to pass seed for rand between queries

params[:seed] ||= Random.new_seed
srand params[:seed].to_i
@users = Kaminari.paginate_array(User.all.shuffle).page(params[:page]).per(20)

And in view add params[:seed] to all kaminari links to pages

mikdiet
  • 9,859
  • 8
  • 59
  • 68
  • thanks for the lead! if possible, could you explain how this works? I'm not following completely, still unsure how to implement this answer. – neon Apr 02 '12 at 04:41
  • I figured it out, but now the array is appearing in the same shuffled order consistently - meaning even after the user logs out and back in, the array remains in the same, initially shuffled order rather than being re-shuffled. Strange. – neon Apr 02 '12 at 16:27
  • I believe the issue is that the first call to [srand](http://www.ruby-doc.org/core-1.9.3/Kernel.html#method-i-srand) will always return 0 (as that function returns the old seed, which defaults to 0). As a result, the seed is always going to be the same. Try changing the first line to read `params[:seed] ||= rand` and the second line to `srand params[:seed].to_f` to see if that makes a difference. – Matt Huggins Apr 11 '12 at 14:41
  • @Matt Huggins, you are not right about `srand` behavior. Read API http://apidock.com/ruby/Kernel/srand – mikdiet Apr 11 '12 at 16:49
  • @neon, try to use `session` hash instead `params` – mikdiet Apr 11 '12 at 16:50
  • 2
    @Mik_Die - that states the same thing I linked to: "The previous seed value is returned." When I open up irb and call `srand`, the first call returns 0 since that is the previous seed value (i.e.: undefined). – Matt Huggins Apr 11 '12 at 17:11
  • 2
    @Matt Huggins, now I see what you talk about, sorry.. I correct my answer – mikdiet Apr 12 '12 at 11:44
  • Thanks @MikDiet for your answer it worked for me. However i want to know how `params[:seed] ||= Random.new_seed` `srand params[:seed].to_i` this worked for shuffling data only once even though call made twice for action for pagination. – Jay_Pandya Sep 23 '16 at 08:51
3

As KandadaBoggu points out above, retrieving all of the User records from the database is inefficient when you only need 20. I would suggest using MySQL's RAND() function to perform the randomization before you return from the database. You can still pass a seed value to RAND() to make sure the shuffling only happens once per session.

For example:

class User < ActiveRecord::Base
  def self.randomized(seed = nil)
    seed = seed.to_i rescue 0
    order("RAND(#{seed})")
  end
end

class UsersController < ApplicationController
  before_filter :set_random_seed

  def index
    @users = User.randomized(session[:seed]).page(params[:page]).per(20)
  end

private

  def set_random_seed
    session[:seed] ||= Random.new_seed
  end
end

I don't have a MySQL installation to test against, but this should perform better than your original code.

Brandan
  • 14,735
  • 3
  • 56
  • 71
0

You can also do this:

class UsersController < ApplicationController
  USERS_SEED = 1000 # Or any another not-so-big number

  def set_random_seed
    session[:seed] ||= Random.rand(USERS_SEED)
  end
end   

Because Random.new_seed will generate most likely the same result if your data isn't that big.