require 'will_paginate/per_page' require 'will_paginate/page_number' require 'will_paginate/collection' require 'active_record' begin require 'active_record/deprecated_finders' rescue LoadError # only for Rails 4.1 end module WillPaginate # = Paginating finders for ActiveRecord models # # WillPaginate adds +paginate+, +per_page+ and other methods to # ActiveRecord::Base class methods and associations. # # In short, paginating finders are equivalent to ActiveRecord finders; the # only difference is that we start with "paginate" instead of "find" and # that :page is required parameter: # # @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC' # module ActiveRecord # makes a Relation look like WillPaginate::Collection module RelationMethods include WillPaginate::CollectionMethods attr_accessor :current_page attr_writer :total_entries, :wp_count_options def per_page(value = nil) if value.nil? then limit_value else limit(value) end end # TODO: solve with less relation clones and code dups def limit(num) rel = super if rel.current_page rel.offset rel.current_page.to_offset(rel.limit_value).to_i else rel end end # dirty hack to enable `first` after `limit` behavior above def first(*args) if current_page rel = clone rel.current_page = nil rel.first(*args) else super end end # fix for Rails 3.0 def find_last if !loaded? and offset_value || limit_value @last ||= to_a.last else super end end def offset(value = nil) if value.nil? then offset_value else super(value) end end def total_entries @total_entries ||= begin if loaded? and size < limit_value and (current_page == 1 or size > 0) offset_value + size else @total_entries_queried = true result = count result = result.size if result.respond_to?(:size) and !result.is_a?(Integer) result end end end def count(*args) if limit_value excluded = [:order, :limit, :offset, :reorder] excluded << :includes unless eager_loading? rel = self.except(*excluded) # TODO: hack. decide whether to keep rel = rel.apply_finder_options(@wp_count_options) if defined? @wp_count_options rel.count(*args) else super(*args) end end # workaround for Active Record 3.0 def size if !loaded? and limit_value and group_values.empty? [super, limit_value].min else super end end # overloaded to be pagination-aware def empty? if !loaded? and offset_value result = count result = result.size if result.respond_to?(:size) and !result.is_a?(Integer) result <= offset_value else super end end def clone copy_will_paginate_data super end # workaround for Active Record 3.0 def scoped(options = nil) copy_will_paginate_data super end def to_a if current_page.nil? then super # workaround for Active Record 3.0 else ::WillPaginate::Collection.create(current_page, limit_value) do |col| col.replace super col.total_entries ||= total_entries end end end private def copy_will_paginate_data(other) other.current_page = current_page unless other.current_page other.total_entries = nil if defined? @total_entries_queried other.wp_count_options = @wp_count_options if defined? @wp_count_options other end end module Pagination def paginate(options) options = options.dup pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" } per_page = options.delete(:per_page) || self.per_page total = options.delete(:total_entries) count_options = options.delete(:count) options.delete(:page) rel = limit(per_page.to_i).page(pagenum) rel = rel.apply_finder_options(options) if options.any? rel.wp_count_options = count_options if count_options rel.total_entries = total.to_i unless total.blank? rel end def page(num) rel = if ::ActiveRecord::Relation === self self elsif !defined?(::ActiveRecord::Scoping) or ::ActiveRecord::Scoping::ClassMethods.method_defined? :with_scope # Active Record 3 scoped else # Active Record 4 all end rel = rel.extending(RelationMethods) pagenum = ::WillPaginate::PageNumber(num.nil? ? 1 : num) per_page = rel.limit_value || self.per_page rel = rel.offset(pagenum.to_offset(per_page).to_i) rel = rel.limit(per_page) unless rel.limit_value rel.current_page = pagenum rel end end module BaseMethods # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string # based on the params otherwise used by paginating finds: +page+ and # +per_page+. # # Example: # # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000], # :page => params[:page], :per_page => 3 # # A query for counting rows will automatically be generated if you don't # supply :total_entries. If you experience problems with this # generated SQL, you might want to perform the count manually in your # application. # def paginate_by_sql(sql, options) pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" } || 1 per_page = options[:per_page] || self.per_page total = options[:total_entries] WillPaginate::Collection.create(pagenum, per_page, total) do |pager| query = sanitize_sql(sql.dup) original_query = query.dup oracle = self.connection.adapter_name =~ /^(oracle|oci$)/i # add limit, offset if oracle query = <<-SQL SELECT * FROM ( SELECT rownum rnum, a.* FROM (#{query}) a WHERE rownum <= #{pager.offset + pager.per_page} ) WHERE rnum >= #{pager.offset} SQL else query << " LIMIT #{pager.per_page} OFFSET #{pager.offset}" end # perfom the find pager.replace find_by_sql(query) unless pager.total_entries count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s.]+$/mi, '' count_query = "SELECT COUNT(*) FROM (#{count_query})" count_query << ' AS count_table' unless oracle # perform the count query pager.total_entries = count_by_sql(count_query) end end end end # mix everything into Active Record ::ActiveRecord::Base.extend PerPage ::ActiveRecord::Base.extend Pagination ::ActiveRecord::Base.extend BaseMethods klasses = [::ActiveRecord::Relation] if defined? ::ActiveRecord::Associations::CollectionProxy klasses << ::ActiveRecord::Associations::CollectionProxy else klasses << ::ActiveRecord::Associations::AssociationCollection end # support pagination on associations and scopes klasses.each { |klass| klass.send(:include, Pagination) } end end