Paginating a shuffled ActiveRecord query
Asked Answered
C

3

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?

Carvajal answered 1/4, 2012 at 18:36 Comment(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.Latoria
E
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

Erb answered 1/4, 2012 at 19:31 Comment(8)
thanks for the lead! if possible, could you explain how this works? I'm not following completely, still unsure how to implement this answer.Carvajal
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.Carvajal
I believe the issue is that the first call to 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.Chlorate
@Matt Huggins, you are not right about srand behavior. Read API apidock.com/ruby/Kernel/srandErb
@neon, try to use session hash instead paramsErb
@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).Chlorate
@Matt Huggins, now I see what you talk about, sorry.. I correct my answerErb
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.Wilhelminawilhelmine
P
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.

Polygenesis answered 12/4, 2012 at 21:15 Comment(0)
F
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.

Fann answered 28/4, 2015 at 13:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.