Aphyr

Sequenceable

Sequenceable is a plugin for Sequel models. It supports the use of models as ordered collections, allowing you to flip through records one-by-one or in pages, locate the first or last records, and identify the position of a record in the sequence. Sequenceable uses select count to quickly identify the position of a record, meaning it is resilient against shuffled and deleted records in a sequence.

Records can be returned in a "window": an in-order slice of the entire collection, much like the pages of a book. You can find windows from the model itself, or, if you have a specific record, return a window which contains it. This can be done in one of three ways:

  • Absolute: Like pages of a book, the collection is divided into a set of non-overlapping windows. The window which contains the target record is returned.
  • Float: Returns a window centered around the record. When the window nears the beginning or end of the sequence, it shifts over to include the desired number of records per window.
  • Clip: Returns a window centered around the record, but near the beginning or end of the sequence, "falls off" the edge.

To use Sequenceable, simply mix it into your model:

include Sequenceable # As users of an OC-48, we feel more photographs per page are in order. self.window_size = 24 # Sort by author, then update time order_by 'author', 'updated_on' # Sort in descending order self.sequence_direction = 'desc' # Restrict photographs to those which are not deleted self.sequence_filter = ['deleted = ?', false] ... end

The Photograph class now support operations like first, last, and window_count, which returns the number of windows required to span the entire sequence. You can retrieve a collection of records with Photograph.window. In addition, each photograph gains methods like next, previous, and window--returning a window in context around the photo.

The end result of all this is that pagination becomes fast and easy:

def list # The request may have specified a desired page. @page = params[:page] # Alternatively, the request might have specified a photograph ID. # We display a window around the target photograph. @photograph = Photograph.find_by_id params[:id] if @page # Show the requested page @photograph = nil # Find an array of photographs for this page. @photographs = Photograph.window @page elsif @photograph # Find an array of photographs around the target. @photographs = @photograph.window else # Show the most recent page @photographs = Photograph.window :last @page = :last end end end

You can also use sequence_include to specify parameters for find :include, which lets you restrict sequences based on associated joins.

Generating links for a model is a more interesting proposition. Here's one way to do it.

# Generate pagination links from a Sequenceable class and index. # The index can be :first or :last for the corresponding pages, an instance # of the class (in which case the page which would contain that instance # is highlighted, or a page number. Limit determines how many numeric links # to include--use :all to include all pages. def page_nav(klass, index = nil, limit = 15) # Translate :first, :last into corresponding windows. case index when :first page = 0 when :last page = klass.window_count - 1 when klass # Index is actually an instance of the target class page = index.window_absolute_index else # Index is a page number page = index.to_i end pages = Array.new links = '<ol class="pagination actions">' window_count = klass.window_count # Determine which pages to create links to if limit.kind_of? Integer and window_count > limit # There are more pages than we can display. # Default first and last pages are the size of the collection first_page = 1 last_page = window_count - 2 # The desired number of previous or next pages previous_pages = (Float(limit - 3) / 2).floor next_pages = (Float(limit - 3) / 2).floor if (offset = first_page - (page - previous_pages)) > 0 # Window extends before the start of the pages last_page = first_page + (limit - 2) elsif (offset = (page + next_pages) - last_page) > 0 # Window extends beyond the end of the pages first_page = last_page - (limit - 2) else # Window is somewhere in the middle first_page = page - previous_pages last_page = page + next_pages end # Generate list of pages pages = [0] + (first_page..last_page).to_a + [window_count - 1] else # The window encompasses the entire set of pages pages = (0 ... window_count).to_a end if page > 0 # Add "previous page" link. links << "<li><a class=\"previous\" href=\"#{klass.url}/page/#{page - 1}\">&laquo; Previous</a></li>" else links << "<li class=\"placeholder\"><span class=\"previous\"></span></li>" end # Convert pages to links unless pages.empty? pages.inject(pages.first - 1) do |previous, i| if (i - previous) > 1 # These pages are not side-by-side. links << '<li class="elided"><span>&hellip;</span></li>' end if i == page # This is a link to the current page. links << "<li class=\"current\"><span>#{i + 1}</span></li>" else # This is a link to a different page. links << "<li><a href=\"#{klass.url}/page/#{i}\">#{i + 1}</a></li>" end # Remember this as the previous page. i end end if page < klass.window_count - 1 # Add "next page" link. links << "<li><a class=\"next\" href=\"#{klass.url}/page/#{page + 1}\">Next &raquo;</a></li>" else links << "<li class=\"placeholder\"><span class=\"next\"></span></li>" end links << '</ol>' end

Please enjoy! :-)

Files

sequenceable.rb Monday, 02 November 2009, 15:57

Post a Comment

Please avoid writing anything here unless you are a computer:

This is also a trap:


Copyright © 2003—2010 Aphyr