module ActiveCollection module Pagination PER_PAGE = 30 def self.included(mod) mod.extend ClassMethods mod.class_eval do alias_method_chain :total_entries, :pagination alias_method_chain :size, :pagination find_scope :pagination_options end end module ClassMethods def per_page PER_PAGE end end def current_page @current_page ||= params.has_key?(:page) ? (params[:page] || 1).to_i : nil end # Defaults to the model class' per_page. def per_page @per_page ||= params[:per_page] || (model_class.respond_to?(:per_page) && model_class.per_page) || self.class.per_page end attr_writer :per_page # Loads total entries and calculates the size from that. def size_with_pagination if paginated? last_page?? size_without_pagination % per_page : per_page else size_without_pagination end end # Create a new collection for the page specified # # Optionally accepts a per page parameter which will override the default # per_page for the new collection (without changing the current collection). def page(pg, per = self.per_page) new_collection = self.class.new(params.merge(:page => pg)) new_collection.per_page = per new_collection end # Force this collection to a page specified # # Optionally accepts a per page parameter which will override the per_page # for this collection. def page!(pg, per = self.per_page) raise_if_loaded @per_page = per @current_page = pg end # Helper method that is true when someone tries to fetch a page with a # larger number than the last page. Can be used in combination with flashes # and redirecting. # # loads total_entries if not already loaded. def out_of_bounds? current_page > total_pages end # Current offset of the paginated collection. If we're on the first page, # it is always 0. If we're on the 2nd page and there are 30 entries per page, # the offset is 30. This property is useful if you want to render ordinals # side by side with records in the view: simply start with offset + 1. # # loads total_entries if not already loaded. def offset (current_page - 1) * per_page end # current_page - 1 or nil if there is no previous page. def previous_page current_page > 1 ? (current_page - 1) : nil end # current_page + 1 or nil if there is no next page. # # loads total_entries if not already loaded. def next_page current_page < total_pages ? (current_page + 1) : nil end # true if the collection is the last page. # # may load total_entries if not already loaded. def last_page? next_page.nil? end # New Collection for current_page - 1 or nil. def previous_page_collection previous_page ? page(previous_page, per_page) : nil end # New Collection for current_page + 1 or nil # # loads total_entries if not already loaded. def next_page_collection next_page ? page(next_page, per_page) : nil end # Always returns the total count regardless of pagination. # # Attempts to save a count query if collection is loaded and is the last page. def total_entries_with_pagination @total_entries ||= if paginated? if loaded? and length < per_page and (current_page == 1 or length > 0) offset + length else total_entries_without_pagination end else total_entries_without_pagination end end # Total number of pages. def total_pages @total_pages ||= (total_entries / per_page.to_f).ceil end # return a paginated collection if it isn't already paginated. # returns self if already paginated. def paginate paginated?? self : page(current_page || 1) end # forces pagination of self, raising if already loaded. # returns current_page if the collection is now paginated # returns nil if already paginated def paginate! raise_if_loaded current_page ? nil : @current_page = 1 end # if the collection has a page parameter def paginated? current_page && current_page > 0 end def as_data_hash data_hash = { "collection" => collection.as_json } if paginated? data_hash["total_entries"] = total_entries data_hash["page"] = current_page data_hash["per_page"] = per_page data_hash["total_pages"] = total_pages end data_hash end def to_xml(options = {}) collect options[:indent] ||= 2 xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) xml.instruct! unless options[:skip_instruct] xml.tag!(table_name) do if paginated? xml.total_entries(total_entries, :type => "integer") xml.page(current_page, :type => "integer") xml.per_page(per_page, :type => "integer") xml.total_pages(total_pages, :type => "integer") end xml.collection(:type => "array") do collection.each do |item| item.to_xml(:indent => options[:indent], :builder => xml, :skip_instruct => true) end end end end protected # Find options for pagination. def pagination_options { :offset => offset, :limit => per_page } if paginated? end end end