Isaac Byrne
Isaac Byrne

Reputation: 613

.order("RANDOM()") with will_paginate gem

I am wondering if there is any way to still use the .order("RANDOM()") with will_paginate so that when a page loads and it orders the pages, all the post will stay the same on each page until the home page is reloaded.

so to have all the posts on localhost:3000/posts?page=1 stay the same until localhost:3000(root_path) is visited again.

Problem is it will paginate posts but it current re orders them for each page selected so you will often see posts on page 1 also on page 2.

Upvotes: 3

Views: 396

Answers (1)

Robert Nubel
Robert Nubel

Reputation: 7532

One way to do this is to set the random seed which your database is ordering by, such that it returns the same sequence of random numbers each time. You can store this seed in your users' session, and reset it only when you want to. However, there's a complication -- even though setting the random seed produces the same ordering of random numbers each time, there's no guarantee your database will execute it on your rows in the same order each time, unless you force it to do so like so:

SELECT items.*
FROM (SELECT setseed(0.2)) t
   , (SELECT name, rank() OVER (ORDER BY name DESC)
      FROM foos ORDER BY name DESC) items
   JOIN generate_series(1, (SELECT COUNT(*) FROM foos))
     ON items.rank = generate_series
ORDER BY RANDOM()
LIMIT 10;

As you can tell, that's quite complicated, and it forces your database to materialize your entire table into memory. It'd work for smaller data sets, but if you've got a big data set, it's out of the question!

Instead, I'd suggest you go with a solution more like tadman suggested above: generate a page of results, store the ids into session, and when you need to generate the next page, simply ignore anything you've already shown the user. The code would look like:

class ThingsController < ApplicationController
  def index
    @page = params[:page].to_i
    session[:pages] ||= {}

    if ids = session[:pages][@page]
      # Grab the items we already showed, and ensure they show up in the same order.
      @things = Things.where(id: ids).sort_by { |thing| ids.index(thing.id) }
    else 
      # Generate a new page of things, filtering anything we've already shown.
      @things = Things.where(["id NOT IN (?)", shown_thing_ids])
                      .order("RANDOM()")
                      .limit(30) # your page size
      # Save the IDs into our session so the above case will work.
      session[:pages][@page] = @things.map(&:id)
    end
  end

  private
  def shown_thing_ids
    session[:pages].values.flatten
  end
end

This method uses the session to store which IDs were shown on each page, so you can guarantee the same set of items and ordering will be shown if the user goes back. For a new page, it will exclude any items already displayed. You can reset the cache whenever you want with:

session.delete(:pages)

Hope that helps! You could also use Redis or Memcache to store your page data, but the session is a good choice if you want the ordering to be random per-user.

Upvotes: 1

Related Questions