pax_global_header00006660000000000000000000000064141051703700014510gustar00rootroot0000000000000052 comment=c0f1f470203e1b311bc559c057d8518da3b2967d will_paginate-3.3.1/000077500000000000000000000000001410517037000143335ustar00rootroot00000000000000will_paginate-3.3.1/.envrc000066400000000000000000000003761410517037000154570ustar00rootroot00000000000000# shellcheck shell=bash export MYSQL_HOST=127.0.0.1 export MYSQL_PORT=3307 export POSTGRES_HOST=localhost export POSTGRES_PORT=5433 export POSTGRES_USER=postgres export POSTGRES_PASSWORD=postgres export MONGODB_HOST=localhost export MONGODB_PORT=27018 will_paginate-3.3.1/.github/000077500000000000000000000000001410517037000156735ustar00rootroot00000000000000will_paginate-3.3.1/.github/workflows/000077500000000000000000000000001410517037000177305ustar00rootroot00000000000000will_paginate-3.3.1/.github/workflows/test.yml000066400000000000000000000057701410517037000214430ustar00rootroot00000000000000--- name: Test Suite 'on': - push - pull_request jobs: test-rails: strategy: fail-fast: false matrix: ruby: - '2.4' - '2.5' - '2.6' - '2.7' - '3.0' gemfile: - Gemfile - environments/Gemfile.rails5.0.rb - environments/Gemfile.rails5.1.rb - environments/Gemfile.rails5.2.rb - environments/Gemfile.rails6.0.rb - environments/Gemfile.rails-edge.rb exclude: - ruby: '2.4' gemfile: Gemfile - ruby: '3.0' gemfile: environments/Gemfile.rails5.0.rb - ruby: '3.0' gemfile: environments/Gemfile.rails5.1.rb - ruby: '3.0' gemfile: environments/Gemfile.rails5.2.rb - ruby: '2.4' gemfile: environments/Gemfile.rails6.0.rb - ruby: '2.4' gemfile: environments/Gemfile.rails-edge.rb - ruby: '2.5' gemfile: environments/Gemfile.rails-edge.rb - ruby: '2.6' gemfile: environments/Gemfile.rails-edge.rb runs-on: ubuntu-latest env: BUNDLE_GEMFILE: "${{ matrix.gemfile }}" services: mysql: image: mysql:5.7 env: MYSQL_DATABASE: will_paginate MYSQL_ALLOW_EMPTY_PASSWORD: true ports: - 3306:3306 postgres: image: postgres:11 env: POSTGRES_DB: will_paginate POSTGRES_PASSWORD: postgres ports: - 5432:5432 steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: "${{ matrix.ruby }}" bundler-cache: true - name: Run tests env: MYSQL_HOST: 127.0.0.1 MYSQL_PORT: 3306 POSTGRES_HOST: localhost POSTGRES_PORT: 5432 POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres run: | docker-wait() { local container container="$(docker ps -q -f ancestor=$1)" timeout 90s bash -c "until docker exec $container $2; do sleep 5; done" } docker-wait postgres:11 "pg_isready" docker-wait mysql:5.7 "mysqladmin ping" bundler binstubs rspec-core script/test_all test-nonrails: strategy: fail-fast: false matrix: ruby: - '2.4' - '2.5' - '2.6' - '2.7' - '3.0' runs-on: ubuntu-latest env: BUNDLE_GEMFILE: environments/Gemfile.non-rails.rb services: mongodb: image: mongo:4.2 ports: - 27017:27017 steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: "${{ matrix.ruby }}" bundler-cache: true - name: Run tests run: | docker-wait() { local container container="$(docker ps -q -f ancestor=$1)" timeout 90s bash -c "until docker exec $container $2; do sleep 5; done" } docker-wait mongo:4.2 "mongo --quiet" bundler binstubs rspec-core script/test_all will_paginate-3.3.1/.gitignore000066400000000000000000000001421410517037000163200ustar00rootroot00000000000000Gemfile*.lock Brewfile.lock.json .bundle doc *.gem coverage /bin vendor/bundle tags .ruby-version will_paginate-3.3.1/.rspec000066400000000000000000000000101410517037000154370ustar00rootroot00000000000000--color will_paginate-3.3.1/Brewfile000066400000000000000000000002241410517037000160130ustar00rootroot00000000000000# brew 'mongodb/brew/mongodb-community@4.0', restart_service: true brew 'mysql@5.7', restart_service: true brew 'postgresql', restart_service: true will_paginate-3.3.1/CONTRIBUTING.md000066400000000000000000000005651410517037000165720ustar00rootroot00000000000000How to set up your environment for running tests: 1. Run `script/bootstrap` **Note:** on systems without Homebrew, you must ensure that MySQL 5.7, PostgreSQL 12, and MongoDB 4.x Community Edition are up and running. 2. Run `script/test_all` This ensures that the Active Record part of the suite is run across `sqlite3`, `mysql`, and `postgres` database adapters. will_paginate-3.3.1/Gemfile000066400000000000000000000005301410517037000156240ustar00rootroot00000000000000source 'https://rubygems.org' # We test against other Rails versions, too. See `environments/` rails_version = '~> 6.1.2' gem 'activerecord', rails_version gem 'actionpack', rails_version gem 'rspec', '~> 2.99' gem 'mocha', '~> 0.9.8' gem 'sqlite3', '~> 1.4.0' gem 'mysql2', '~> 0.5.2', :group => :mysql gem 'pg', '~> 1.2', :group => :pg will_paginate-3.3.1/LICENSE000066400000000000000000000020571410517037000153440ustar00rootroot00000000000000Copyright (c) 2009 Mislav Marohnić Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. will_paginate-3.3.1/README.md000066400000000000000000000030631410517037000156140ustar00rootroot00000000000000# will_paginate will_paginate is a pagination library that integrates with Ruby on Rails, Sinatra, Hanami::View, Merb, DataMapper and Sequel. ``` ruby gem 'will_paginate', '~> 3.1.0' ``` See [installation instructions][install] on the wiki for more info. ℹ️ will_paginate is now in _maintenance mode_ and it will not be receiving new features. [See alternatives](https://www.ruby-toolbox.com/categories/pagination) ## Basic will_paginate use ``` ruby ## perform a paginated query: @posts = Post.paginate(page: params[:page]) # or, use an explicit "per page" limit: Post.paginate(page: params[:page], per_page: 30) ## render page links in the view: <%= will_paginate @posts %> ``` And that's it! You're done. You just need to add some CSS styles to [make those pagination links prettier][css]. You can customize the default "per_page" value: ``` ruby # for the Post model class Post self.per_page = 10 end # set per_page globally WillPaginate.per_page = 10 ``` New in Active Record 3: ``` ruby # paginate in Active Record now returns a Relation Post.where(:published => true).paginate(:page => params[:page]).order('id DESC') # the new, shorter page() method Post.page(params[:page]).order('created_at DESC') ``` See [the wiki][wiki] for more documentation. [Report bugs][issues] on GitHub. Happy paginating. [wiki]: https://github.com/mislav/will_paginate/wiki [install]: https://github.com/mislav/will_paginate/wiki/Installation "will_paginate installation" [issues]: https://github.com/mislav/will_paginate/issues [css]: http://mislav.github.io/will_paginate/ will_paginate-3.3.1/docker-compose.yml000066400000000000000000000005761410517037000200000ustar00rootroot00000000000000--- version: '2.1' services: mysql: image: mysql:5.7 environment: - MYSQL_DATABASE=will_paginate - MYSQL_ALLOW_EMPTY_PASSWORD=true ports: - 3307:3306 postgres: image: postgres:11 environment: - POSTGRES_DB=will_paginate - POSTGRES_PASSWORD=postgres ports: - 5433:5432 mongodb: image: mongo:4.2 ports: - 27018:27017 will_paginate-3.3.1/environments/000077500000000000000000000000001410517037000170625ustar00rootroot00000000000000will_paginate-3.3.1/environments/Gemfile.non-rails.rb000066400000000000000000000003521410517037000226600ustar00rootroot00000000000000source 'https://rubygems.org' gem 'rspec', '~> 2.99' gem 'mocha', '~> 0.9.8' gem 'sqlite3', '~> 1.4.0' gem 'sequel', '~> 5.29' gem 'dm-core' gem 'dm-aggregates' gem 'dm-migrations' gem 'dm-sqlite-adapter' gem 'mongoid', '~> 7.2.0' will_paginate-3.3.1/environments/Gemfile.rails-edge.rb000066400000000000000000000005471410517037000230000ustar00rootroot00000000000000source 'https://rubygems.org' gem 'activerecord', git: 'https://github.com/rails/rails.git', branch: 'main' gem 'actionpack', git: 'https://github.com/rails/rails.git', branch: 'main' gem 'thread_safe' gem 'rspec', '~> 2.99' gem 'mocha', '~> 0.9.8' gem 'sqlite3', '~> 1.4.0' gem 'mysql2', '~> 0.5.2', :group => :mysql gem 'pg', '~> 1.2', :group => :pg will_paginate-3.3.1/environments/Gemfile.rails5.0.rb000066400000000000000000000004621410517037000223150ustar00rootroot00000000000000source 'https://rubygems.org' rails_version = '~> 5.0.7' gem 'activerecord', rails_version gem 'actionpack', rails_version gem 'rails-dom-testing' gem 'rspec', '~> 2.99' gem 'mocha', '~> 0.9.8' gem 'sqlite3', '~> 1.3.6' gem 'mysql2', '~> 0.5.2', :group => :mysql gem 'pg', '~> 1.2.3', :group => :pg will_paginate-3.3.1/environments/Gemfile.rails5.1.rb000066400000000000000000000004321410517037000223130ustar00rootroot00000000000000source 'https://rubygems.org' rails_version = '~> 5.1.7' gem 'activerecord', rails_version gem 'actionpack', rails_version gem 'rspec', '~> 2.99' gem 'mocha', '~> 0.9.8' gem 'sqlite3', '~> 1.3.6' gem 'mysql2', '~> 0.5.2', :group => :mysql gem 'pg', '~> 1.2.3', :group => :pg will_paginate-3.3.1/environments/Gemfile.rails5.2.rb000066400000000000000000000004311410517037000223130ustar00rootroot00000000000000source 'https://rubygems.org' rails_version = '~> 5.2.4' gem 'activerecord', rails_version gem 'actionpack', rails_version gem 'rspec', '~> 2.99' gem 'mocha', '~> 0.9.8' gem 'sqlite3', '~> 1.3.6' gem 'mysql2', '~> 0.5.2', :group => :mysql gem 'pg', '~> 1.2.3', :group => :pg will_paginate-3.3.1/environments/Gemfile.rails6.0.rb000066400000000000000000000004301410517037000223110ustar00rootroot00000000000000source 'https://rubygems.org' rails_version = '~> 6.0.0' gem 'activerecord', rails_version gem 'actionpack', rails_version gem 'rspec', '~> 2.99' gem 'mocha', '~> 0.9.8' gem 'sqlite3', '~> 1.4.0' gem 'mysql2', '~> 0.5.2', :group => :mysql gem 'pg', '~> 1.2', :group => :pg will_paginate-3.3.1/init.rb000066400000000000000000000007271410517037000156310ustar00rootroot00000000000000require 'will_paginate' # This is all duplication of what Railtie does, but is necessary because # the initializer defined by the Railtie won't ever run when loaded as plugin. if defined? ActiveRecord::Base require 'will_paginate/active_record' end if defined? ActionController::Base WillPaginate::Railtie.setup_actioncontroller end if defined? ActionView::Base require 'will_paginate/view_helpers/action_view' end WillPaginate::Railtie.add_locale_path config will_paginate-3.3.1/lib/000077500000000000000000000000001410517037000151015ustar00rootroot00000000000000will_paginate-3.3.1/lib/will_paginate.rb000066400000000000000000000012531410517037000202460ustar00rootroot00000000000000# You will paginate! module WillPaginate end if defined?(Rails::Railtie) require 'will_paginate/railtie' elsif defined?(Rails::Initializer) raise "will_paginate 3.0 is not compatible with Rails 2.3 or older" end if defined?(Merb::AbstractController) require 'will_paginate/view_helpers/merb' Merb::BootLoader.before_app_loads do adapters = { :datamapper => 'data_mapper', :activerecord => 'active_record', :sequel => 'sequel' } # auto-load the right ORM adapter if adapter = adapters[Merb.orm] require "will_paginate/#{adapter}" end end end if defined?(Sinatra) and Sinatra.respond_to? :register require 'will_paginate/view_helpers/sinatra' end will_paginate-3.3.1/lib/will_paginate/000077500000000000000000000000001410517037000177205ustar00rootroot00000000000000will_paginate-3.3.1/lib/will_paginate/active_record.rb000066400000000000000000000167461410517037000230740ustar00rootroot00000000000000require 'will_paginate/per_page' require 'will_paginate/page_number' require 'will_paginate/collection' require 'active_record' 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 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(*args) if !loaded? && args.empty? && (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) column_name = if rel.select_values.present? select = rel.select_values.join(", ") select if select !~ /[,*]/ end || :all rel.count(column_name) 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 total_entries <= 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 end end module Pagination def paginate(options) options = options.dup pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" } options.delete(:page) per_page = options.delete(:per_page) || self.per_page total = options.delete(:total_entries) if options.any? raise ArgumentError, "unsupported parameters: %p" % options.keys end rel = limit(per_page.to_i).page(pagenum) 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 elsif (self.connection.adapter_name =~ /^sqlserver/i) query << " OFFSET #{pager.offset} ROWS FETCH NEXT #{pager.per_page} ROWS ONLY" 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 will_paginate-3.3.1/lib/will_paginate/array.rb000066400000000000000000000024221410517037000213630ustar00rootroot00000000000000require 'will_paginate/collection' class Array # Paginates a static array (extracting a subset of it). The result is a # WillPaginate::Collection instance, which is an array with a few more # properties about its paginated state. # # Parameters: # * :page - current page, defaults to 1 # * :per_page - limit of items per page, defaults to 30 # * :total_entries - total number of items in the array, defaults to # array.length (obviously) # # Example: # arr = ['a', 'b', 'c', 'd', 'e'] # paged = arr.paginate(:per_page => 2) #-> ['a', 'b'] # paged.total_entries #-> 5 # arr.paginate(:page => 2, :per_page => 2) #-> ['c', 'd'] # arr.paginate(:page => 3, :per_page => 2) #-> ['e'] # # This method was originally {suggested by Desi # McAdam}[http://www.desimcadam.com/archives/8] and later proved to be the # most useful method of will_paginate library. def paginate(options = {}) page = options[:page] || 1 per_page = options[:per_page] || WillPaginate.per_page total = options[:total_entries] || self.length WillPaginate::Collection.create(page, per_page, total) do |pager| pager.replace self[pager.offset, pager.per_page].to_a end end end will_paginate-3.3.1/lib/will_paginate/collection.rb000066400000000000000000000120541410517037000224020ustar00rootroot00000000000000require 'will_paginate/per_page' require 'will_paginate/page_number' module WillPaginate # Any will_paginate-compatible collection should have these methods: # # current_page, per_page, offset, total_entries, total_pages # # It can also define some of these optional methods: # # out_of_bounds?, previous_page, next_page # # This module provides few of these methods. module CollectionMethods def total_pages total_entries.zero? ? 1 : (total_entries / per_page.to_f).ceil 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 def next_page current_page < total_pages ? (current_page + 1) : nil 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. def out_of_bounds? current_page > total_pages end end # = The key to pagination # Arrays returned from paginating finds are, in fact, instances of this little # class. You may think of WillPaginate::Collection as an ordinary array with # some extra properties. Those properties are used by view helpers to generate # correct page links. # # WillPaginate::Collection also assists in rolling out your own pagination # solutions: see +create+. # # If you are writing a library that provides a collection which you would like # to conform to this API, you don't have to copy these methods over; simply # make your plugin/gem dependant on this library and do: # # require 'will_paginate/collection' # # WillPaginate::Collection is now available for use class Collection < Array include CollectionMethods attr_reader :current_page, :per_page, :total_entries # Arguments to the constructor are the current page number, per-page limit # and the total number of entries. The last argument is optional because it # is best to do lazy counting; in other words, count *conditionally* after # populating the collection using the +replace+ method. def initialize(page, per_page = WillPaginate.per_page, total = nil) @current_page = WillPaginate::PageNumber(page) @per_page = per_page.to_i self.total_entries = total if total end # Just like +new+, but yields the object after instantiation and returns it # afterwards. This is very useful for manual pagination: # # @entries = WillPaginate::Collection.create(1, 10) do |pager| # result = Post.find(:all, :limit => pager.per_page, :offset => pager.offset) # # inject the result array into the paginated collection: # pager.replace(result) # # unless pager.total_entries # # the pager didn't manage to guess the total count, do it manually # pager.total_entries = Post.count # end # end # # The possibilities with this are endless. For another example, here is how # WillPaginate used to define pagination for Array instances: # # Array.class_eval do # def paginate(page = 1, per_page = 15) # WillPaginate::Collection.create(page, per_page, size) do |pager| # pager.replace self[pager.offset, pager.per_page].to_a # end # end # end # # The Array#paginate API has since then changed, but this still serves as a # fine example of WillPaginate::Collection usage. def self.create(page, per_page, total = nil) pager = new(page, per_page, total) yield pager pager 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. def offset current_page.to_offset(per_page).to_i end def total_entries=(number) @total_entries = number.to_i end # This is a magic wrapper for the original Array#replace method. It serves # for populating the paginated collection after initialization. # # Why magic? Because it tries to guess the total number of entries judging # by the size of given array. If it is shorter than +per_page+ limit, then we # know we're on the last page. This trick is very useful for avoiding # unnecessary hits to the database to do the counting after we fetched the # data for the current page. # # However, after using +replace+ you should always test the value of # +total_entries+ and set it to a proper value if it's +nil+. See the example # in +create+. def replace(array) result = super # The collection is shorter then page limit? Rejoice, because # then we know that we are on the last page! if total_entries.nil? and length < per_page and (current_page == 1 or length > 0) self.total_entries = offset + length end result end end end will_paginate-3.3.1/lib/will_paginate/core_ext.rb000066400000000000000000000013411410517037000220540ustar00rootroot00000000000000require 'set' # copied from ActiveSupport so we don't depend on it unless Hash.method_defined? :except Hash.class_eval do # Returns a new hash without the given keys. def except(*keys) rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) reject { |key,| rejected.include?(key) } end # Replaces the hash without only the given keys. def except!(*keys) replace(except(*keys)) end end end unless String.method_defined? :underscore String.class_eval do def underscore self.to_s.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr("-", "_"). downcase end end end will_paginate-3.3.1/lib/will_paginate/data_mapper.rb000066400000000000000000000052771410517037000225350ustar00rootroot00000000000000require 'dm-core' require 'dm-aggregates' require 'will_paginate/per_page' require 'will_paginate/page_number' require 'will_paginate/collection' module WillPaginate module DataMapper module Pagination def page(num) pagenum = ::WillPaginate::PageNumber(num.nil? ? 1 : num) per_page = query.limit || self.per_page options = {:offset => pagenum.to_offset(per_page).to_i} options[:limit] = per_page unless query.limit col = new_collection(query.merge(options)) col.current_page = pagenum col end 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) options.delete(:page) options[:limit] = per_page.to_i col = all(options).page(pagenum) col.total_entries = total.to_i unless total.nil? || (total.kind_of?(String) && total.strip.empty?) col end end module CollectionMethods include WillPaginate::CollectionMethods attr_accessor :current_page attr_writer :total_entries def paginated? !current_page.nil? end def per_page query.limit || model.per_page end def offset query.offset end def total_entries @total_entries ||= begin if loaded? and @array.size < per_page and (current_page == 1 or @array.size > 0) offset + @array.size else # :reload prevents Collection.filter from being run, which # would cause a stack overflow clean_query = query.merge(:reload => true) # seems like the only way clean_query.instance_variable_set('@limit', nil) clean_query.instance_variable_set('@offset', 0) new_collection(clean_query).count end end end def to_a if paginated? ::WillPaginate::Collection.create(current_page, per_page) do |col| col.replace super col.total_entries ||= total_entries end else super end end private def new_collection(query, resources = nil) col = super col.current_page = self.current_page col end def initialize_copy(original) super @total_entries = nil end end ::DataMapper::Model.append_extensions PerPage ::DataMapper::Model.append_extensions Pagination ::DataMapper::Collection.send(:include, Pagination) ::DataMapper::Collection.send(:include, CollectionMethods) end end will_paginate-3.3.1/lib/will_paginate/deprecation.rb000066400000000000000000000023321410517037000225420ustar00rootroot00000000000000module WillPaginate::Deprecation class << self def warn(message, stack = caller) offending_line = origin_of_call(stack) full_message = "DEPRECATION WARNING: #{message} (called from #{offending_line})" logger = rails_logger || Kernel logger.warn full_message end private def rails_logger defined?(Rails.logger) && Rails.logger end def origin_of_call(stack) lib_root = File.expand_path('../../..', __FILE__) stack.find { |line| line.index(lib_root) != 0 } || stack.first end end class Hash < ::Hash def initialize(values = {}) super() update values @deprecated = {} end def []=(key, value) check_deprecated(key, value) super end def deprecate_key(*keys, &block) message = block_given? ? block : keys.pop Array(keys).each { |key| @deprecated[key] = message } end def merge(another) to_hash.update(another) end def to_hash ::Hash.new.update(self) end private def check_deprecated(key, value) if msg = @deprecated[key] and (!msg.respond_to?(:call) or (msg = msg.call(key, value))) WillPaginate::Deprecation.warn(msg) end end end end will_paginate-3.3.1/lib/will_paginate/i18n.rb000066400000000000000000000010661410517037000210270ustar00rootroot00000000000000module WillPaginate module I18n def self.locale_dir File.expand_path('../locale', __FILE__) end def self.load_path Dir["#{locale_dir}/*.{rb,yml}"] end def will_paginate_translate(keys, options = {}, &block) if defined? ::I18n defaults = Array(keys).dup defaults << block if block_given? ::I18n.translate(defaults.shift, **options.merge(:default => defaults, :scope => :will_paginate)) else key = Array === keys ? keys.first : keys yield key, options end end end end will_paginate-3.3.1/lib/will_paginate/locale/000077500000000000000000000000001410517037000211575ustar00rootroot00000000000000will_paginate-3.3.1/lib/will_paginate/locale/en.yml000066400000000000000000000021611410517037000223040ustar00rootroot00000000000000en: will_paginate: previous_label: "← Previous" next_label: "Next →" page_gap: "…" container_aria_label: "Pagination" page_aria_label: "Page %{page}" page_entries_info: single_page: zero: "No %{model} found" one: "Displaying 1 %{model}" other: "Displaying all %{count} %{model}" single_page_html: zero: "No %{model} found" one: "Displaying 1 %{model}" other: "Displaying all %{count} %{model}" multi_page: "Displaying %{model} %{from} - %{to} of %{count} in total" multi_page_html: "Displaying %{model} %{from} - %{to} of %{count} in total" # models: # entry: # zero: entries # one: entry # few: entries # other: entries # line_item: # page_entries_info: # single_page: # zero: "Your shopping cart is empty" # one: "Displaying one item in your cart" # other: "Displaying all %{count} items" # multi_page: "Displaying items %{from} - %{to} of %{count} in total" will_paginate-3.3.1/lib/will_paginate/mongoid.rb000066400000000000000000000022071410517037000217020ustar00rootroot00000000000000require 'mongoid' require 'will_paginate/collection' module WillPaginate module Mongoid module CriteriaMethods def paginate(options = {}) extend CollectionMethods @current_page = WillPaginate::PageNumber(options[:page] || @current_page || 1) @page_multiplier = current_page - 1 @total_entries = options.delete(:total_entries) pp = (options[:per_page] || per_page || WillPaginate.per_page).to_i limit(pp).skip(@page_multiplier * pp) end def per_page(value = :non_given) if value == :non_given options[:limit] == 0 ? nil : options[:limit] # in new Mongoid versions a nil limit is saved as 0 else limit(value) end end def page(page) paginate(:page => page) end end module CollectionMethods attr_reader :current_page def total_entries @total_entries ||= count end def total_pages (total_entries / per_page.to_f).ceil end def offset @page_multiplier * per_page end end ::Mongoid::Criteria.send(:include, CriteriaMethods) end end will_paginate-3.3.1/lib/will_paginate/page_number.rb000066400000000000000000000023001410517037000225240ustar00rootroot00000000000000require 'forwardable' module WillPaginate # a module that page number exceptions are tagged with module InvalidPage; end # integer representing a page number class PageNumber < Numeric # a value larger than this is not supported in SQL queries BIGINT = 9223372036854775807 extend Forwardable def initialize(value, name) value = Integer(value) if 'offset' == name ? (value < 0 or value > BIGINT) : value < 1 raise RangeError, "invalid #{name}: #{value.inspect}" end @name = name @value = value rescue ArgumentError, TypeError, RangeError => error error.extend InvalidPage raise error end def to_i @value end def_delegators :@value, :coerce, :==, :<=>, :to_s, :+, :-, :*, :/, :to_json def inspect "#{@name} #{to_i}" end def to_offset(per_page) PageNumber.new((to_i - 1) * per_page.to_i, 'offset') end def kind_of?(klass) super || to_i.kind_of?(klass) end alias is_a? kind_of? end # An idemptotent coercion method def self.PageNumber(value, name = 'page') case value when PageNumber then value else PageNumber.new(value, name) end end end will_paginate-3.3.1/lib/will_paginate/per_page.rb000066400000000000000000000007511410517037000220320ustar00rootroot00000000000000module WillPaginate module PerPage def per_page defined?(@per_page) ? @per_page : WillPaginate.per_page end def per_page=(limit) @per_page = limit.to_i end def self.extended(base) base.extend Inheritance if base.is_a? Class end module Inheritance def inherited(subclass) super subclass.per_page = self.per_page end end end extend PerPage # default number of items per page self.per_page = 30 end will_paginate-3.3.1/lib/will_paginate/railtie.rb000066400000000000000000000043421410517037000217010ustar00rootroot00000000000000require 'will_paginate/page_number' require 'will_paginate/collection' require 'will_paginate/i18n' module WillPaginate class Railtie < Rails::Railtie initializer "will_paginate" do |app| ActiveSupport.on_load :active_record do require 'will_paginate/active_record' end ActiveSupport.on_load :action_controller do WillPaginate::Railtie.setup_actioncontroller end ActiveSupport.on_load :action_view do require 'will_paginate/view_helpers/action_view' end # early access to ViewHelpers.pagination_options require 'will_paginate/view_helpers' end def self.setup_actioncontroller ( defined?(ActionDispatch::ExceptionWrapper) ? ActionDispatch::ExceptionWrapper : ActionDispatch::ShowExceptions ).send :include, ShowExceptionsPatch ActionController::Base.extend ControllerRescuePatch end # Extending the exception handler middleware so it properly detects # WillPaginate::InvalidPage regardless of it being a tag module. module ShowExceptionsPatch extend ActiveSupport::Concern included do alias_method :status_code_without_paginate, :status_code alias_method :status_code, :status_code_with_paginate end def status_code_with_paginate(exception = @exception) actual_exception = if exception.respond_to?(:cause) exception.cause || exception elsif exception.respond_to?(:original_exception) exception.original_exception else exception end if actual_exception.is_a?(WillPaginate::InvalidPage) Rack::Utils.status_code(:not_found) else original_method = method(:status_code_without_paginate) if original_method.arity != 0 original_method.call(exception) else original_method.call() end end end end module ControllerRescuePatch def rescue_from(*args, **kwargs, &block) if idx = args.index(WillPaginate::InvalidPage) args[idx] = args[idx].name end super(*args, **kwargs, &block) end end end end ActiveSupport.on_load :i18n do I18n.load_path.concat(WillPaginate::I18n.load_path) end will_paginate-3.3.1/lib/will_paginate/sequel.rb000066400000000000000000000014161410517037000215450ustar00rootroot00000000000000require 'sequel' require 'sequel/extensions/pagination' require 'will_paginate/collection' module WillPaginate # Sequel already supports pagination; we only need to make the # resulting dataset look a bit more like WillPaginate::Collection module SequelMethods include WillPaginate::CollectionMethods def total_pages page_count end def per_page page_size end def size current_page_record_count end alias length size def total_entries pagination_record_count end def out_of_bounds? current_page > total_pages end # Current offset of the paginated collection def offset (current_page - 1) * per_page end end Sequel::Dataset::Pagination.send(:include, SequelMethods) end will_paginate-3.3.1/lib/will_paginate/version.rb000066400000000000000000000002301410517037000217250ustar00rootroot00000000000000module WillPaginate #:nodoc: module VERSION #:nodoc: MAJOR = 3 MINOR = 3 TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end end will_paginate-3.3.1/lib/will_paginate/view_helpers.rb000066400000000000000000000152641410517037000227510ustar00rootroot00000000000000# encoding: utf-8 require 'will_paginate/core_ext' require 'will_paginate/i18n' require 'will_paginate/deprecation' module WillPaginate # = Will Paginate view helpers # # The main view helper is +will_paginate+. It renders the pagination links # for the given collection. The helper itself is lightweight and serves only # as a wrapper around LinkRenderer instantiation; the renderer then does # all the hard work of generating the HTML. module ViewHelpers class << self # Write to this hash to override default options on the global level: # # WillPaginate::ViewHelpers.pagination_options[:page_links] = false # attr_accessor :pagination_options end # default view options self.pagination_options = Deprecation::Hash.new \ :class => 'pagination', :previous_label => nil, :next_label => nil, :inner_window => 4, # links around the current page :outer_window => 1, # links around beginning and end :link_separator => ' ', # single space is friendly to spiders and non-graphic browsers :param_name => :page, :params => nil, :page_links => true, :container => true label_deprecation = Proc.new { |key, value| "set the 'will_paginate.#{key}' key in your i18n locale instead of editing pagination_options" if defined? Rails } pagination_options.deprecate_key(:previous_label, :next_label, &label_deprecation) pagination_options.deprecate_key(:renderer) { |key, _| "pagination_options[#{key.inspect}] shouldn't be set globally" } include WillPaginate::I18n # Returns HTML representing page links for a WillPaginate::Collection-like object. # In case there is no more than one page in total, nil is returned. # # ==== Options # * :class -- CSS class name for the generated DIV (default: "pagination") # * :previous_label -- default: "« Previous" # * :next_label -- default: "Next »" # * :inner_window -- how many links are shown around the current page (default: 4) # * :outer_window -- how many links are around the first and the last page (default: 1) # * :link_separator -- string separator for page HTML elements (default: single space) # * :param_name -- parameter name for page number in URLs (default: :page) # * :params -- additional parameters when generating pagination links # (eg. :controller => "foo", :action => nil) # * :renderer -- class name, class or instance of a link renderer (default in Rails: # WillPaginate::ActionView::LinkRenderer) # * :page_links -- when false, only previous/next links are rendered (default: true) # * :container -- toggles rendering of the DIV container for pagination links, set to # false only when you are rendering your own pagination markup (default: true) # # All options not recognized by will_paginate will become HTML attributes on the container # element for pagination links (the DIV). For example: # # <%= will_paginate @posts, :style => 'color:blue' %> # # will result in: # # # def will_paginate(collection, options = {}) # early exit if there is nothing to render return nil unless collection.total_pages > 1 options = WillPaginate::ViewHelpers.pagination_options.merge(options) options[:previous_label] ||= will_paginate_translate(:previous_label) { '← Previous' } options[:next_label] ||= will_paginate_translate(:next_label) { 'Next →' } # get the renderer instance renderer = case options[:renderer] when nil raise ArgumentError, ":renderer not specified" when String klass = if options[:renderer].respond_to? :constantize then options[:renderer].constantize else Object.const_get(options[:renderer]) # poor man's constantize end klass.new when Class then options[:renderer].new else options[:renderer] end # render HTML for pagination renderer.prepare collection, options, self output = renderer.to_html output = output.html_safe if output.respond_to?(:html_safe) output end # Renders a message containing number of displayed vs. total entries. # # <%= page_entries_info @posts %> # #-> Displaying posts 6 - 12 of 26 in total # # The default output contains HTML. Use ":html => false" for plain text. def page_entries_info(collection, options = {}) model = options[:model] model = collection.first.class unless model or collection.empty? model ||= 'entry' model_key = if model.respond_to? :model_name model.model_name.i18n_key # ActiveModel::Naming else model.to_s.underscore end if options.fetch(:html, true) b, eb = '', '' sp = ' ' html_key = '_html' else b = eb = html_key = '' sp = ' ' end model_count = collection.total_pages > 1 ? 5 : collection.size defaults = ["models.#{model_key}"] defaults << Proc.new { |_, opts| if model.respond_to? :model_name model.model_name.human(:count => opts[:count]) else name = model_key.to_s.tr('_', ' ') raise "can't pluralize model name: #{model.inspect}" unless name.respond_to? :pluralize opts[:count] == 1 ? name : name.pluralize end } model_name = will_paginate_translate defaults, :count => model_count if collection.total_pages < 2 i18n_key = :"page_entries_info.single_page#{html_key}" keys = [:"#{model_key}.#{i18n_key}", i18n_key] will_paginate_translate keys, :count => collection.total_entries, :model => model_name do |_, opts| case opts[:count] when 0; "No #{opts[:model]} found" when 1; "Displaying #{b}1#{eb} #{opts[:model]}" else "Displaying #{b}all#{sp}#{opts[:count]}#{eb} #{opts[:model]}" end end else i18n_key = :"page_entries_info.multi_page#{html_key}" keys = [:"#{model_key}.#{i18n_key}", i18n_key] params = { :model => model_name, :count => collection.total_entries, :from => collection.offset + 1, :to => collection.offset + collection.length } will_paginate_translate keys, params do |_, opts| %{Displaying %s #{b}%d#{sp}-#{sp}%d#{eb} of #{b}%d#{eb} in total} % [ opts[:model], opts[:from], opts[:to], opts[:count] ] end end end end end will_paginate-3.3.1/lib/will_paginate/view_helpers/000077500000000000000000000000001410517037000224145ustar00rootroot00000000000000will_paginate-3.3.1/lib/will_paginate/view_helpers/action_view.rb000066400000000000000000000106371410517037000252570ustar00rootroot00000000000000require 'will_paginate/view_helpers' require 'will_paginate/view_helpers/link_renderer' module WillPaginate # = ActionView helpers # # This module serves for availability in ActionView templates. It also adds a new # view helper: +paginated_section+. # # == Using the helper without arguments # If the helper is called without passing in the collection object, it will # try to read from the instance variable inferred by the controller name. # For example, calling +will_paginate+ while the current controller is # PostsController will result in trying to read from the @posts # variable. Example: # # <%= will_paginate :id => true %> # # ... will result in @post collection getting paginated: # # # module ActionView include ViewHelpers def will_paginate(collection = nil, options = {}) #:nodoc: options, collection = collection, nil if collection.is_a? Hash collection ||= infer_collection_from_controller options = options.symbolize_keys options[:renderer] ||= LinkRenderer super(collection, options) end def page_entries_info(collection = nil, options = {}) #:nodoc: options, collection = collection, nil if collection.is_a? Hash collection ||= infer_collection_from_controller super(collection, options.symbolize_keys) end # Wrapper for rendering pagination links at both top and bottom of a block # of content. # # <%= paginated_section @posts do %> #
    # <% for post in @posts %> #
  1. ...
  2. # <% end %> #
# <% end %> # # will result in: # # #
    # ... #
# # # Arguments are passed to a will_paginate call, so the same options # apply. Don't use the :id option; otherwise you'll finish with two # blocks of pagination links sharing the same ID (which is invalid HTML). def paginated_section(*args, &block) pagination = will_paginate(*args) if pagination pagination + capture(&block) + pagination else capture(&block) end end def will_paginate_translate(keys, options = {}) if respond_to? :translate if Array === keys defaults = keys.dup key = defaults.shift else defaults = nil key = keys end translate(key, **options.merge(:default => defaults, :scope => :will_paginate)) else super end end protected def infer_collection_from_controller collection_name = "@#{controller.controller_name}" collection = instance_variable_get(collection_name) raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " + "forget to pass the collection object for will_paginate?" if collection.nil? collection end class LinkRenderer < ViewHelpers::LinkRenderer protected GET_PARAMS_BLACKLIST = [:script_name, :original_script_name] def default_url_params {} end def url(page) @base_url_params ||= begin url_params = merge_get_params(default_url_params) url_params[:only_path] = true merge_optional_params(url_params) end url_params = @base_url_params.dup add_current_page_param(url_params, page) @template.url_for(url_params) end def merge_get_params(url_params) if @template.respond_to? :request and @template.request and @template.request.get? symbolized_update(url_params, @template.params, GET_PARAMS_BLACKLIST) end url_params end def merge_optional_params(url_params) symbolized_update(url_params, @options[:params]) if @options[:params] url_params end def add_current_page_param(url_params, page) unless param_name.index(/[^\w-]/) url_params[param_name.to_sym] = page else page_param = parse_query_parameters("#{param_name}=#{page}") symbolized_update(url_params, page_param) end end private def parse_query_parameters(params) Rack::Utils.parse_nested_query(params) end end ::ActionView::Base.send :include, self end end will_paginate-3.3.1/lib/will_paginate/view_helpers/hanami.rb000066400000000000000000000020051410517037000241730ustar00rootroot00000000000000require 'hanami/view' require 'will_paginate/view_helpers' require 'will_paginate/view_helpers/link_renderer' module WillPaginate module Hanami module Helpers include ViewHelpers def will_paginate(collection, options = {}) #:nodoc: options = options.merge(:renderer => LinkRenderer) unless options[:renderer] str = super(collection, options) str && raw(str) end end class LinkRenderer < ViewHelpers::LinkRenderer protected def url(page) str = File.join(request_env['SCRIPT_NAME'].to_s, request_env['PATH_INFO']) params = request_env['rack.request.query_hash'].merge(param_name.to_s => page.to_s) params.update @options[:params] if @options[:params] str << '?' << build_query(params) end def request_env @template.params.env end def build_query(params) Rack::Utils.build_nested_query params end end def self.included(base) base.include Helpers end end end will_paginate-3.3.1/lib/will_paginate/view_helpers/link_renderer.rb000066400000000000000000000106741410517037000255740ustar00rootroot00000000000000require 'cgi' require 'will_paginate/core_ext' require 'will_paginate/view_helpers' require 'will_paginate/view_helpers/link_renderer_base' module WillPaginate module ViewHelpers # This class does the heavy lifting of actually building the pagination # links. It is used by +will_paginate+ helper internally. class LinkRenderer < LinkRendererBase # * +collection+ is a WillPaginate::Collection instance or any other object # that conforms to that API # * +options+ are forwarded from +will_paginate+ view helper # * +template+ is the reference to the template being rendered def prepare(collection, options, template) super(collection, options) @template = template @container_attributes = @base_url_params = nil end # Process it! This method returns the complete HTML string which contains # pagination links. Feel free to subclass LinkRenderer and change this # method as you see fit. def to_html html = pagination.map do |item| item.is_a?(Integer) ? page_number(item) : send(item) end.join(@options[:link_separator]) @options[:container] ? html_container(html) : html end # Returns the subset of +options+ this instance was initialized with that # represent HTML attributes for the container element of pagination links. def container_attributes @container_attributes ||= { :role => 'navigation', :"aria-label" => @template.will_paginate_translate(:container_aria_label) { 'Pagination' } }.update @options.except(*(ViewHelpers.pagination_options.keys + [:renderer] - [:class])) end protected def page_number(page) aria_label = @template.will_paginate_translate(:page_aria_label, :page => page.to_i) { "Page #{page}" } if page == current_page tag(:em, page, :class => 'current', :"aria-label" => aria_label, :"aria-current" => 'page') else link(page, page, :rel => rel_value(page), :"aria-label" => aria_label) end end def gap text = @template.will_paginate_translate(:page_gap) { '…' } %(#{text}) end def previous_page num = @collection.current_page > 1 && @collection.current_page - 1 previous_or_next_page(num, @options[:previous_label], 'previous_page') end def next_page num = @collection.current_page < total_pages && @collection.current_page + 1 previous_or_next_page(num, @options[:next_label], 'next_page') end def previous_or_next_page(page, text, classname) if page link(text, page, :class => classname) else tag(:span, text, :class => classname + ' disabled', :"aria-disabled" => true) end end def html_container(html) tag(:div, html, container_attributes) end # Returns URL params for +page_link_or_span+, taking the current GET params # and :params option into account. def url(page) raise NotImplementedError end private def param_name @options[:param_name].to_s end def link(text, target, attributes = {}) if target.is_a?(Integer) attributes[:rel] = rel_value(target) target = url(target) end attributes[:href] = target tag(:a, text, attributes) end def tag(name, value, attributes = {}) string_attributes = attributes.inject('') do |attrs, pair| unless pair.last.nil? attrs << %( #{pair.first}="#{CGI::escapeHTML(pair.last.to_s)}") end attrs end "<#{name}#{string_attributes}>#{value}" end def rel_value(page) case page when @collection.current_page - 1; 'prev' when @collection.current_page + 1; 'next' end end def symbolized_update(target, other, blacklist = nil) other.each_pair do |key, value| key = key.to_sym existing = target[key] next if blacklist && blacklist.include?(key) if value.respond_to?(:each_pair) and (existing.is_a?(Hash) or existing.nil?) symbolized_update(existing || (target[key] = {}), value) else target[key] = value end end end end end end will_paginate-3.3.1/lib/will_paginate/view_helpers/link_renderer_base.rb000066400000000000000000000044551410517037000265660ustar00rootroot00000000000000module WillPaginate module ViewHelpers # This class does the heavy lifting of actually building the pagination # links. It is used by +will_paginate+ helper internally. class LinkRendererBase # * +collection+ is a WillPaginate::Collection instance or any other object # that conforms to that API # * +options+ are forwarded from +will_paginate+ view helper def prepare(collection, options) @collection = collection @options = options # reset values in case we're re-using this instance @total_pages = nil end def pagination items = @options[:page_links] ? windowed_page_numbers : [] items.unshift :previous_page items.push :next_page end protected # Calculates visible page numbers using the :inner_window and # :outer_window options. def windowed_page_numbers inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i window_from = current_page - inner_window window_to = current_page + inner_window # adjust lower or upper limit if either is out of bounds if window_to > total_pages window_from -= window_to - total_pages window_to = total_pages end if window_from < 1 window_to += 1 - window_from window_from = 1 window_to = total_pages if window_to > total_pages end # these are always visible middle = window_from..window_to # left window if outer_window + 3 < middle.first # there's a gap left = (1..(outer_window + 1)).to_a left << :gap else # runs into visible pages left = 1...middle.first end # right window if total_pages - outer_window - 2 > middle.last # again, gap right = ((total_pages - outer_window)..total_pages).to_a right.unshift :gap else # runs into visible pages right = (middle.last + 1)..total_pages end left.to_a + middle.to_a + right.to_a end private def current_page @collection.current_page end def total_pages @total_pages ||= @collection.total_pages end end end end will_paginate-3.3.1/lib/will_paginate/view_helpers/merb.rb000066400000000000000000000012331410517037000236650ustar00rootroot00000000000000require 'will_paginate/core_ext' require 'will_paginate/view_helpers' require 'will_paginate/view_helpers/link_renderer' module WillPaginate module Merb include ViewHelpers def will_paginate(collection, options = {}) #:nodoc: options = options.merge(:renderer => LinkRenderer) unless options[:renderer] super(collection, options) end class LinkRenderer < ViewHelpers::LinkRenderer protected def url(page) params = @template.request.params.except(:action, :controller).merge(param_name => page) @template.url(:this, params) end end ::Merb::AbstractController.send(:include, self) end end will_paginate-3.3.1/lib/will_paginate/view_helpers/sinatra.rb000066400000000000000000000017251410517037000244070ustar00rootroot00000000000000require 'sinatra/base' require 'will_paginate/view_helpers' require 'will_paginate/view_helpers/link_renderer' module WillPaginate module Sinatra module Helpers include ViewHelpers def will_paginate(collection, options = {}) #:nodoc: options = options.merge(:renderer => LinkRenderer) unless options[:renderer] super(collection, options) end end class LinkRenderer < ViewHelpers::LinkRenderer protected def url(page) str = File.join(request.script_name.to_s, request.path_info) params = request.GET.merge(param_name.to_s => page.to_s) params.update @options[:params] if @options[:params] str << '?' << build_query(params) end def request @template.request end def build_query(params) Rack::Utils.build_nested_query params end end def self.registered(app) app.helpers Helpers end ::Sinatra.register self end end will_paginate-3.3.1/script/000077500000000000000000000000001410517037000156375ustar00rootroot00000000000000will_paginate-3.3.1/script/bootstrap000077500000000000000000000012661410517037000176070ustar00rootroot00000000000000#!/bin/bash # vi:ft=sh: set -e if type -p brew >/dev/null; then brew bundle --no-upgrade mysql_prefix="$(brew --prefix mysql@5.7)" openssl_prefix="$(brew --prefix openssl@1.1)" bundle config set --local build.mysql2 --with-mysql-config="${mysql_prefix}/bin/mysql_config" --with-ldflags="-L${openssl_prefix}/lib" while [ ! -e /tmp/mysql.sock ] && [ ! -e /var/run/mysql5/mysqld.sock ]; do echo "Waiting for mysql to start up ..." >&2 sleep 1 done fi mysql -u root -e 'CREATE DATABASE IF NOT EXISTS will_paginate;' psql --dbname will_paginate -c '' 2>/dev/null || createdb will_paginate bundle config set path "$PWD/vendor/bundle" bundle install bundle binstubs rspec-core will_paginate-3.3.1/script/ci-matrix000077500000000000000000000023431410517037000174640ustar00rootroot00000000000000#!/usr/bin/env ruby require "yaml" ci_config = File.expand_path('../../.github/workflows/test.yml', __FILE__) data = YAML.load(File.read(ci_config)) matrix = data.dig('jobs', 'test-rails', 'strategy', 'matrix') ruby_versions = matrix.fetch('ruby') gemfiles = matrix.fetch('gemfile') requirements = { 'environments/Gemfile.rails5.0.rb' => ['>= 2.2', '< 3.0'], 'environments/Gemfile.rails5.1.rb' => ['>= 2.2', '< 3.0'], 'environments/Gemfile.rails5.2.rb' => ['>= 2.2', '< 3.0'], 'environments/Gemfile.rails6.0.rb' => '>= 2.5', 'Gemfile' => '>= 2.5', 'environments/Gemfile.rails-edge.rb' => '>= 2.7', } commands = {} commands['excludes'] = -> { excludes = [] gemfiles.each do |gemfile| req = Gem::Requirement.new(requirements.fetch(gemfile)) ruby_versions.each do |version| unless req.satisfied_by?(Gem::Version.new(version)) excludes << { 'ruby' => version, 'gemfile' => gemfile } end end end matrix['exclude'] = excludes File.open(ci_config, 'w') do |file| yaml_str = YAML.dump(data) file.write(yaml_str) end } cmd = commands.fetch(ARGV[0]) do $stderr.puts "available commands: #{commands.keys.join(', ')}" exit 1 end cmd.(*ARGV[1..-1]) will_paginate-3.3.1/script/release000077500000000000000000000012161410517037000172050ustar00rootroot00000000000000#!/bin/sh set -euo pipefail bin/rspec spec BUNDLE_GEMFILE=environments/Gemfile.non-rails.rb bin/rspec spec-non-rails changelog() { local previous_tag="$(git describe --tags HEAD^ --abbrev=0)" git log --first-parent --format='* %s%n%w(0,2,2)%+b' --reverse "${previous_tag}..HEAD^" "$@" } eval "$(gem build *.gemspec | awk '/(Name|Version|File): /{print tolower($1) $2}' | sed 's/:/=/')" git commit -m "${name} ${version}" -- lib/will_paginate/version.rb git tag "v${version}" git push origin HEAD "v${version}" gem push "$file" rm -rf "$file" { echo "${name} ${version}" echo changelog -- lib } | hub release create -F- --edit "v${version}" will_paginate-3.3.1/script/test_all000077500000000000000000000007101410517037000173720ustar00rootroot00000000000000#!/usr/bin/env bash set -e binstubs_path="bin" if [[ -n $CI && $BUNDLE_GEMFILE == */* ]]; then binstubs_path="${BUNDLE_GEMFILE%/*}/bin" fi export PATH="${binstubs_path}:$PATH" if [[ $BUNDLE_GEMFILE == *non-rails* ]]; then echo "bin/rspec spec-non-rails" exec rspec spec-non-rails fi status=0 for db in sqlite3 mysql postgres; do printf "\e[1;33m[DB] %s\e[m\n" "$db" echo "bin/rspec spec" DB="$db" rspec spec || status="$?" done exit $status will_paginate-3.3.1/spec-non-rails/000077500000000000000000000000001410517037000171655ustar00rootroot00000000000000will_paginate-3.3.1/spec-non-rails/data_mapper_spec.rb000066400000000000000000000064411410517037000230060ustar00rootroot00000000000000require_relative './spec_helper' require 'will_paginate/data_mapper' require_relative './data_mapper_test_connector' describe "WillPaginate::DataMapper" do before(:all) do DataMapper.setup :default, 'sqlite3::memory:' [Animal, Ownership, Human].each do |klass| klass.auto_migrate! end Animal.create(:name => 'Dog', :notes => 'a friend of all') Animal.create(:name => 'Cat', :notes => 'a friend or foe') Animal.create(:name => 'Lion', :notes => 'some roar') end it "has per_page" do Animal.per_page.should == 30 begin Animal.per_page = 10 Animal.per_page.should == 10 subclass = Class.new(Animal) subclass.per_page.should == 10 ensure Animal.per_page = 30 end end it "doesn't make normal collections appear paginated" do Animal.all.should_not be_paginated end it "paginates to first page by default" do animals = Animal.paginate(:page => nil) animals.should be_paginated animals.current_page.should == 1 animals.per_page.should == 30 animals.offset.should == 0 animals.total_entries.should == 3 animals.total_pages.should == 1 end it "paginates to first page, explicit limit" do animals = Animal.paginate(:page => 1, :per_page => 2) animals.current_page.should == 1 animals.per_page.should == 2 animals.total_entries.should == 3 animals.total_pages.should == 2 animals.map {|a| a.name }.should == %w[ Dog Cat ] end it "paginates to second page" do animals = Animal.paginate(:page => 2, :per_page => 2) animals.current_page.should == 2 animals.offset.should == 2 animals.map {|a| a.name }.should == %w[ Lion ] end it "paginates a collection" do friends = Animal.all(:notes.like => '%friend%') friends.paginate(:page => 1).per_page.should == 30 friends.paginate(:page => 1, :per_page => 1).total_entries.should == 2 end it "paginates a limited collection" do animals = Animal.all(:limit => 2).paginate(:page => 1) animals.per_page.should == 2 end it "has page() method" do Animal.page(2).per_page.should == 30 Animal.page(2).offset.should == 30 Animal.page(2).current_page.should == 2 Animal.all(:limit => 2).page(2).per_page.should == 2 end it "has total_pages at 1 for empty collections" do Animal.all(:conditions => ['1=2']).page(1).total_pages.should == 1 end it "overrides total_entries count with a fixed value" do animals = Animal.paginate :page => 1, :per_page => 3, :total_entries => 999 animals.total_entries.should == 999 end it "supports a non-int for total_entries" do topics = Animal.paginate :page => 1, :per_page => 3, :total_entries => "999" topics.total_entries.should == 999 end it "can iterate and then call WP methods" do animals = Animal.all(:limit => 2).page(1) animals.each { |a| } animals.total_entries.should == 3 end it "augments to_a to return a WP::Collection" do animals = Animal.all(:limit => 2).page(1) array = animals.to_a array.size.should == 2 array.should be_kind_of(WillPaginate::Collection) array.current_page.should == 1 array.per_page.should == 2 end it "doesn't have a problem assigning has-one-through relationship" do human = Human.create :name => "Mislav" human.pet = Animal.first end end will_paginate-3.3.1/spec-non-rails/data_mapper_test_connector.rb000066400000000000000000000011731410517037000251020ustar00rootroot00000000000000require 'sqlite3' require 'dm-core' require 'dm-core/support/logger' require 'dm-migrations' class Animal include DataMapper::Resource property :id, Serial property :name, String property :notes, Text end class Ownership include DataMapper::Resource belongs_to :animal, :key => true belongs_to :human, :key => true end class Human include DataMapper::Resource property :id, Serial property :name, String has n, :ownerships has 1, :pet, :model => 'Animal', :through => :ownerships, :via => :animal end if 'irb' == $0 DataMapper.logger.set_log($stdout, :debug) DataMapper.logger.auto_flush = true end will_paginate-3.3.1/spec-non-rails/mongoid_spec.rb000066400000000000000000000106061410517037000221630ustar00rootroot00000000000000require_relative './spec_helper' require 'will_paginate/mongoid' describe WillPaginate::Mongoid do class MongoidModel include Mongoid::Document end before(:all) do Mongoid.configure do |config| mongodb_host = ENV["MONGODB_HOST"] || "localhost" mongodb_port = ENV["MONGODB_PORT"] || "27017" config.clients.default = { hosts: ["#{mongodb_host}:#{mongodb_port}"], database: "will_paginate_test", } config.log_level = :warn end MongoidModel.delete_all 4.times { MongoidModel.create! } end let(:criteria) { MongoidModel.criteria } describe "#page" do it "should forward to the paginate method" do criteria.expects(:paginate).with(:page => 2).returns("itself") criteria.page(2).should == "itself" end it "should not override per_page if set earlier in the chain" do criteria.paginate(:per_page => 10).page(1).per_page.should == 10 criteria.paginate(:per_page => 20).page(1).per_page.should == 20 end end describe "#per_page" do it "should set the limit if given an argument" do criteria.per_page(10).options[:limit].should == 10 end it "should return the current limit if no argument is given" do criteria.per_page.should == nil criteria.per_page(10).per_page.should == 10 end it "should be interchangable with limit" do criteria.limit(15).per_page.should == 15 end it "should be nil'able" do criteria.per_page(nil).per_page.should be_nil end end describe "#paginate" do it "should use criteria" do criteria.paginate.should be_instance_of(::Mongoid::Criteria) end it "should not override page number if set earlier in the chain" do criteria.page(3).paginate.current_page.should == 3 end it "should limit according to per_page parameter" do criteria.paginate(:per_page => 10).options.should include(:limit => 10) end it "should skip according to page and per_page parameters" do criteria.paginate(:page => 2, :per_page => 5).options.should include(:skip => 5) end specify "first fallback value for per_page option is the current limit" do criteria.limit(12).paginate.options.should include(:limit => 12) end specify "second fallback value for per_page option is WillPaginate.per_page" do criteria.paginate.options.should include(:limit => WillPaginate.per_page) end specify "page should default to 1" do criteria.paginate.options.should include(:skip => 0) end it "should convert strings to integers" do criteria.paginate(:page => "2", :per_page => "3").options.should include(:limit => 3) end describe "collection compatibility" do describe "#total_count" do it "should be calculated correctly" do criteria.paginate(:per_page => 1).total_entries.should == 4 criteria.paginate(:per_page => 3).total_entries.should == 4 end it "should be cached" do criteria.expects(:count).once.returns(123) criteria.paginate 2.times { criteria.total_entries.should == 123 } end end it "should calculate total_pages" do criteria.paginate(:per_page => 1).total_pages.should == 4 criteria.paginate(:per_page => 3).total_pages.should == 2 criteria.paginate(:per_page => 10).total_pages.should == 1 end it "should return per_page" do criteria.paginate(:per_page => 1).per_page.should == 1 criteria.paginate(:per_page => 5).per_page.should == 5 end describe "#current_page" do it "should return current_page" do criteria.paginate(:page => 1).current_page.should == 1 criteria.paginate(:page => 3).current_page.should == 3 end it "should be casted to PageNumber" do page = criteria.paginate(:page => 1).current_page (page.instance_of? WillPaginate::PageNumber).should be end end it "should return offset" do criteria.paginate(:page => 1).offset.should == 0 criteria.paginate(:page => 2, :per_page => 5).offset.should == 5 criteria.paginate(:page => 3, :per_page => 10).offset.should == 20 end it "should not pollute plain mongoid criterias" do %w(total_entries total_pages current_page).each do |method| criteria.should_not respond_to(method) end end end end end will_paginate-3.3.1/spec-non-rails/sequel_spec.rb000066400000000000000000000034761410517037000220340ustar00rootroot00000000000000require_relative './spec_helper' require 'sequel' require 'will_paginate/sequel' Sequel.sqlite.create_table :cars do primary_key :id, :integer, :auto_increment => true column :name, :text column :notes, :text end describe Sequel::Dataset::Pagination, 'extension' do class Car < Sequel::Model self.dataset = dataset.extension(:pagination) end it "should have the #paginate method" do Car.dataset.should respond_to(:paginate) end it "should NOT have the #paginate_by_sql method" do Car.dataset.should_not respond_to(:paginate_by_sql) end describe 'pagination' do before(:all) do Car.create(:name => 'Shelby', :notes => "Man's best friend") Car.create(:name => 'Aston Martin', :notes => "Woman's best friend") Car.create(:name => 'Corvette', :notes => 'King of the Jungle') end it "should imitate WillPaginate::Collection" do result = Car.dataset.paginate(1, 2) result.should_not be_empty result.size.should == 2 result.length.should == 2 result.total_entries.should == 3 result.total_pages.should == 2 result.per_page.should == 2 result.current_page.should == 1 end it "should perform" do Car.dataset.paginate(1, 2).all.should == [Car[1], Car[2]] end it "should be empty" do result = Car.dataset.paginate(3, 2) result.should be_empty end it "should perform with #select and #order" do result = Car.select(Sequel.lit("name as foo")).order(:name).paginate(1, 2).all result.size.should == 2 result.first.values[:foo].should == "Aston Martin" end it "should perform with #filter" do results = Car.filter(:name => 'Shelby').paginate(1, 2).all results.size.should == 1 results.first.should == Car.find(:name => 'Shelby') end end end will_paginate-3.3.1/spec-non-rails/spec_helper.rb000066400000000000000000000001131410517037000217760ustar00rootroot00000000000000require 'rspec' RSpec.configure do |config| config.mock_with :mocha end will_paginate-3.3.1/spec/000077500000000000000000000000001410517037000152655ustar00rootroot00000000000000will_paginate-3.3.1/spec/collection_spec.rb000066400000000000000000000100041410517037000207520ustar00rootroot00000000000000require 'will_paginate/array' require 'spec_helper' describe WillPaginate::Collection do before :all do @simple = ('a'..'e').to_a end it "should be a subset of original collection" do @simple.paginate(:page => 1, :per_page => 3).should == %w( a b c ) end it "can be shorter than per_page if on last page" do @simple.paginate(:page => 2, :per_page => 3).should == %w( d e ) end it "should include whole collection if per_page permits" do @simple.paginate(:page => 1, :per_page => 5).should == @simple end it "should be empty if out of bounds" do @simple.paginate(:page => 2, :per_page => 5).should be_empty end it "should default to 1 as current page and 30 per-page" do result = (1..50).to_a.paginate result.current_page.should == 1 result.size.should == 30 end it "should give total_entries precedence over actual size" do %w(a b c).paginate(:total_entries => 5).total_entries.should == 5 end it "should be an augmented Array" do entries = %w(a b c) collection = create(2, 3, 10) do |pager| pager.replace(entries).should == entries end collection.should == entries for method in %w(total_pages each offset size current_page per_page total_entries) collection.should respond_to(method) end collection.should be_kind_of(Array) collection.entries.should be_instance_of(Array) # TODO: move to another expectation: collection.offset.should == 3 collection.total_pages.should == 4 collection.should_not be_out_of_bounds end describe "previous/next pages" do it "should have previous_page nil when on first page" do collection = create(1, 1, 3) collection.previous_page.should be_nil collection.next_page.should == 2 end it "should have both prev/next pages" do collection = create(2, 1, 3) collection.previous_page.should == 1 collection.next_page.should == 3 end it "should have next_page nil when on last page" do collection = create(3, 1, 3) collection.previous_page.should == 2 collection.next_page.should be_nil end end describe "out of bounds" do it "is out of bounds when page number is too high" do create(2, 3, 2).should be_out_of_bounds end it "isn't out of bounds when inside collection" do create(1, 3, 2).should_not be_out_of_bounds end it "isn't out of bounds when the collection is empty" do collection = create(1, 3, 0) collection.should_not be_out_of_bounds collection.total_pages.should == 1 end end describe "guessing total count" do it "can guess when collection is shorter than limit" do collection = create { |p| p.replace array } collection.total_entries.should == 8 end it "should allow explicit total count to override guessed" do collection = create(2, 5, 10) { |p| p.replace array } collection.total_entries.should == 10 end it "should not be able to guess when collection is same as limit" do collection = create { |p| p.replace array(5) } collection.total_entries.should be_nil end it "should not be able to guess when collection is empty" do collection = create { |p| p.replace array(0) } collection.total_entries.should be_nil end it "should be able to guess when collection is empty and this is the first page" do collection = create(1) { |p| p.replace array(0) } collection.total_entries.should == 0 end end it "should not respond to page_count anymore" do Proc.new { create.page_count }.should raise_error(NoMethodError) end it "inherits per_page from global value" do collection = described_class.new(1) collection.per_page.should == 30 end private def create(page = 2, limit = 5, total = nil, &block) if block_given? described_class.create(page, limit, total, &block) else described_class.new(page, limit, total) end end def array(size = 3) Array.new(size) end end will_paginate-3.3.1/spec/console000077500000000000000000000006441410517037000166610ustar00rootroot00000000000000#!/usr/bin/env ruby irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' opts = %w[ --simple-prompt -rirb/completion ] if ARGV.include? '-dm' opts << '-rwill_paginate/data_mapper' << '-rfinders/data_mapper_test_connector' elsif ARGV.include? '-seq' opts << '-rwill_paginate/sequel' << '-rfinders/sequel_test_connector' else opts << '-rconsole_fixtures' end exec 'bundle', 'exec', irb, '-Ilib:spec', *opts will_paginate-3.3.1/spec/console_fixtures.rb000066400000000000000000000014341410517037000212070ustar00rootroot00000000000000require 'bundler' Bundler.setup require 'will_paginate/active_record' require 'finders/activerecord_test_connector' ActiverecordTestConnector.setup windows = RUBY_PLATFORM =~ /(:?mswin|mingw)/ # used just for the `color` method log_subscriber = ActiveSupport::LogSubscriber.log_subscribers.first IGNORE_SQL = /\b(sqlite_master|sqlite_version)\b|^(CREATE TABLE|PRAGMA)\b/ ActiveSupport::Notifications.subscribe(/^sql\./) do |*args| data = args.last unless data[:name] =~ /^Fixture/ or data[:sql] =~ IGNORE_SQL if windows puts data[:sql] else puts log_subscriber.send(:color, data[:sql], :cyan) end end end # load all fixtures ActiverecordTestConnector::Fixtures.create_fixtures \ ActiverecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables will_paginate-3.3.1/spec/database.yml000066400000000000000000000013211410517037000175510ustar00rootroot00000000000000sqlite3: database: ":memory:" adapter: sqlite3 timeout: 500 mysql: adapter: mysql2 database: will_paginate username: <%= ENV["MYSQL_USER"] || "root" %> encoding: utf8 <% if ENV["MYSQL_PORT"] %> host: <%= ENV["MYSQL_HOST"] %> port: <%= ENV["MYSQL_PORT"] %> <% elsif File.exist?("/var/run/mysql5/mysqld.sock") %> host: localhost socket: /var/run/mysql5/mysqld.sock <% elsif File.exist? "/tmp/mysql.sock" %> host: localhost socket: /tmp/mysql.sock <% end %> postgres: adapter: postgresql database: will_paginate min_messages: warning username: <%= ENV["POSTGRES_USER"] %> password: <%= ENV["POSTGRES_PASSWORD"] %> host: <%= ENV["POSTGRES_HOST"] %> port: <%= ENV["POSTGRES_PORT"] %> will_paginate-3.3.1/spec/finders/000077500000000000000000000000001410517037000167175ustar00rootroot00000000000000will_paginate-3.3.1/spec/finders/active_record_spec.rb000066400000000000000000000320161410517037000230710ustar00rootroot00000000000000require 'spec_helper' require 'will_paginate/active_record' require File.expand_path('../activerecord_test_connector', __FILE__) ActiverecordTestConnector.setup describe WillPaginate::ActiveRecord do extend ActiverecordTestConnector::FixtureSetup fixtures :topics, :replies, :users, :projects, :developers_projects it "should integrate with ActiveRecord::Base" do ActiveRecord::Base.should respond_to(:paginate) end it "should paginate" do lambda { users = User.paginate(:page => 1, :per_page => 5).to_a users.length.should == 5 }.should run_queries(2) end it "should fail when encountering unknown params" do lambda { User.paginate :foo => 'bar', :page => 1, :per_page => 4 }.should raise_error(ArgumentError) end describe "relation" do it "should return a relation" do rel = nil lambda { rel = Developer.paginate(:page => 1) rel.per_page.should == 10 rel.current_page.should == 1 }.should run_queries(0) lambda { rel.total_pages.should == 2 }.should run_queries(1) end it "should keep per-class per_page number" do rel = Developer.order('id').paginate(:page => 1) rel.per_page.should == 10 end it "should be able to change per_page number" do rel = Developer.order('id').paginate(:page => 1).limit(5) rel.per_page.should == 5 end it "remembers pagination in sub-relations" do rel = Topic.paginate(:page => 2, :per_page => 3) lambda { rel.total_entries.should == 4 }.should run_queries(1) rel = rel.mentions_activerecord rel.current_page.should == 2 rel.per_page.should == 3 lambda { rel.total_entries.should == 1 }.should run_queries(1) end it "supports the page() method" do rel = Developer.page('1').order('id') rel.current_page.should == 1 rel.per_page.should == 10 rel.offset.should == 0 rel = rel.limit(5).page(2) rel.per_page.should == 5 rel.offset.should == 5 end it "raises on invalid page number" do lambda { Developer.page('foo') }.should raise_error(ArgumentError) end it "supports first limit() then page()" do rel = Developer.limit(3).page(3) rel.offset.should == 6 end it "supports first page() then limit()" do rel = Developer.page(3).limit(3) rel.offset.should == 6 end it "supports #first" do rel = Developer.order('id').page(2).per_page(4) rel.first.should == users(:dev_5) rel.first(2).should == users(:dev_5, :dev_6) end it "supports #last" do rel = Developer.order('id').page(2).per_page(4) rel.last.should == users(:dev_8) rel.last(2).should == users(:dev_7, :dev_8) rel.page(3).last.should == users(:poor_jamis) end end describe "counting" do it "should guess the total count" do lambda { topics = Topic.paginate :page => 2, :per_page => 3 topics.total_entries.should == 4 }.should run_queries(1) end it "should guess that there are no records" do lambda { topics = Topic.where(:project_id => 999).paginate :page => 1, :per_page => 3 topics.total_entries.should == 0 }.should run_queries(1) end it "forgets count in sub-relations" do lambda { topics = Topic.paginate :page => 1, :per_page => 3 topics.total_entries.should == 4 topics.where('1 = 1').total_entries.should == 4 }.should run_queries(2) end it "supports empty? method" do topics = Topic.paginate :page => 1, :per_page => 3 lambda { topics.should_not be_empty }.should run_queries(1) end it "support empty? for grouped queries" do topics = Topic.group(:project_id).paginate :page => 1, :per_page => 3 lambda { topics.should_not be_empty }.should run_queries(1) end it "supports `size` for grouped queries" do topics = Topic.group(:project_id).paginate :page => 1, :per_page => 3 lambda { topics.size.should == {nil=>2, 1=>2} }.should run_queries(1) end it "overrides total_entries count with a fixed value" do lambda { topics = Topic.paginate :page => 1, :per_page => 3, :total_entries => 999 topics.total_entries.should == 999 # value is kept even in sub-relations topics.where('1 = 1').total_entries.should == 999 }.should run_queries(0) end it "supports a non-int for total_entries" do topics = Topic.paginate :page => 1, :per_page => 3, :total_entries => "999" topics.total_entries.should == 999 end it "overrides empty? count call with a total_entries fixed value" do lambda { topics = Topic.paginate :page => 1, :per_page => 3, :total_entries => 999 topics.should_not be_empty }.should run_queries(0) end it "removes :include for count" do lambda { developers = Developer.paginate(:page => 1, :per_page => 1).includes(:projects) developers.total_entries.should == 11 $query_sql.last.should_not =~ /\bJOIN\b/ }.should run_queries(1) end it "keeps :include for count when they are referenced in :conditions" do developers = Developer.paginate(:page => 1, :per_page => 1).includes(:projects) with_condition = developers.where('projects.id > 1') with_condition = with_condition.references(:projects) if with_condition.respond_to?(:references) with_condition.total_entries.should == 1 $query_sql.last.should =~ /\bJOIN\b/ end it "should count with group" do Developer.group(:salary).page(1).total_entries.should == 4 end it "should count with select" do Topic.select('title, content').page(1).total_entries.should == 4 end it "removes :reorder for count with group" do Project.group(:id).reorder(:id).page(1).total_entries $query_sql.last.should_not =~ /\ORDER\b/ end it "should not have zero total_pages when the result set is empty" do Developer.where("1 = 2").page(1).total_pages.should == 1 end end it "should not ignore :select parameter when it says DISTINCT" do users = User.select('DISTINCT salary').paginate :page => 2 users.total_entries.should == 5 end describe "paginate_by_sql" do it "should respond" do User.should respond_to(:paginate_by_sql) end it "should paginate" do lambda { sql = "select content from topics where content like '%futurama%'" topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 1 topics.total_entries.should == 1 topics.first.attributes.has_key?('title').should be(false) }.should run_queries(2) end it "should respect total_entries setting" do lambda { sql = "select content from topics" topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 1, :total_entries => 999 topics.total_entries.should == 999 }.should run_queries(1) end it "defaults to page 1" do sql = "select content from topics" topics = Topic.paginate_by_sql sql, :page => nil, :per_page => 1 topics.current_page.should == 1 topics.size.should == 1 end it "should strip the order when counting" do expected = topics(:ar) lambda { sql = "select id, title, content from topics order by topics.title" topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 2 topics.first.should == expected }.should run_queries(2) $query_sql.last.should include('COUNT') $query_sql.last.should_not include('order by topics.title') end it "shouldn't change the original query string" do query = 'select * from topics where 1 = 2' original_query = query.dup Topic.paginate_by_sql(query, :page => 1) query.should == original_query end end it "doesn't mangle options" do options = { :page => 1 } options.expects(:delete).never options_before = options.dup Topic.paginate(options) options.should == options_before end it "should get first page of Topics with a single query" do lambda { result = Topic.paginate :page => nil result.to_a # trigger loading of records result.current_page.should == 1 result.total_pages.should == 1 result.size.should == 4 }.should run_queries(1) end it "should get second (inexistent) page of Topics, requiring 1 query" do lambda { result = Topic.paginate :page => 2 result.total_pages.should == 1 result.should be_empty }.should run_queries(1) end describe "associations" do it "should paginate" do dhh = users(:david) expected_name_ordered = projects(:action_controller, :active_record) expected_id_ordered = projects(:active_record, :action_controller) lambda { # with association-specified order result = ignore_deprecation { dhh.projects.includes(:topics).order('projects.name').paginate(:page => 1) } result.to_a.should == expected_name_ordered result.total_entries.should == 2 }.should run_queries(2) # with explicit order result = dhh.projects.paginate(:page => 1).reorder('projects.id') result.should == expected_id_ordered result.total_entries.should == 2 lambda { dhh.projects.order('projects.id').limit(4).to_a }.should_not raise_error result = dhh.projects.paginate(:page => 1, :per_page => 4).reorder('projects.id') result.should == expected_id_ordered # has_many with implicit order topic = Topic.find(1) expected = replies(:spam, :witty_retort) # FIXME: wow, this is ugly topic.replies.paginate(:page => 1).map(&:id).sort.should == expected.map(&:id).sort topic.replies.paginate(:page => 1).reorder('replies.id ASC').should == expected.reverse end it "should paginate through association extension" do project = Project.order('id').first expected = [replies(:brave)] lambda { result = project.replies.only_recent.paginate(:page => 1) result.should == expected }.should run_queries(1) end end it "should paginate with joins" do result = nil join_sql = 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id' lambda { result = Developer.where('developers_projects.project_id = 1').joins(join_sql).paginate(:page => 1) result.to_a # trigger loading of records result.size.should == 2 developer_names = result.map(&:name) developer_names.should include('David') developer_names.should include('Jamis') }.should run_queries(1) lambda { expected = result.to_a result = Developer.where('developers_projects.project_id = 1').joins(join_sql).paginate(:page => 1) result.should == expected result.total_entries.should == 2 }.should run_queries(1) end it "should paginate with group" do result = nil lambda { result = Developer.select('salary').order('salary').group('salary'). paginate(:page => 1, :per_page => 10).to_a }.should run_queries(1) expected = users(:david, :jamis, :dev_10, :poor_jamis).map(&:salary).sort result.map(&:salary).should == expected end it "should not paginate with dynamic finder" do lambda { Developer.paginate_by_salary(100000, :page => 1, :per_page => 5) }.should raise_error(NoMethodError) end describe "scopes" do it "should paginate" do result = Developer.poor.paginate :page => 1, :per_page => 1 result.size.should == 1 result.total_entries.should == 2 end it "should paginate on habtm association" do project = projects(:active_record) lambda { result = ignore_deprecation { project.developers.poor.paginate :page => 1, :per_page => 1 } result.size.should == 1 result.total_entries.should == 1 }.should run_queries(2) end it "should paginate on hmt association" do project = projects(:active_record) expected = [replies(:brave)] lambda { result = project.replies.recent.paginate :page => 1, :per_page => 1 result.should == expected result.total_entries.should == 1 }.should run_queries(2) end it "should paginate on has_many association" do project = projects(:active_record) expected = [topics(:ar)] lambda { result = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1 result.should == expected result.total_entries.should == 1 }.should run_queries(2) end end it "should not paginate an array of IDs" do lambda { Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id') }.should raise_error(ArgumentError) end it "errors out for invalid values" do |variable| lambda { # page that results in an offset larger than BIGINT Project.page(307445734561825862) }.should raise_error(WillPaginate::InvalidPage, "invalid offset: 9223372036854775830") end end will_paginate-3.3.1/spec/finders/activerecord_test_connector.rb000066400000000000000000000054511410517037000250340ustar00rootroot00000000000000require 'active_record' require 'active_record/fixtures' require 'stringio' require 'erb' $query_count = 0 $query_sql = [] ignore_sql = / ^( PRAGMA | SHOW\ (max_identifier_length|search_path) | SELECT\ (currval|CAST|@@IDENTITY|@@ROWCOUNT) | SHOW\ ((FULL\ )?FIELDS|TABLES) )\b | \bFROM\ (sqlite_master|pg_tables|pg_attribute)\b /x ActiveSupport::Notifications.subscribe(/^sql\./) do |*args| payload = args.last unless payload[:name] =~ /^Fixture/ or payload[:sql] =~ ignore_sql $query_count += 1 $query_sql << payload[:sql] end end module ActiverecordTestConnector extend self attr_accessor :connected FIXTURES_PATH = File.expand_path('../../fixtures', __FILE__) # Set our defaults self.connected = false def setup unless self.connected setup_connection load_schema add_load_path FIXTURES_PATH self.connected = true end end private def add_load_path(path) dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies dep.autoload_paths.unshift path end def setup_connection db = ENV['DB'].blank?? 'sqlite3' : ENV['DB'] erb = ERB.new(File.read(File.expand_path('../../database.yml', __FILE__))) configurations = YAML.load(erb.result) raise "no configuration for '#{db}'" unless configurations.key? db configuration = configurations[db] # ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb' puts "using #{configuration['adapter']} adapter" ActiveRecord::Base.configurations = { db => configuration } ActiveRecord::Base.establish_connection(db.to_sym) ActiveRecord::Base.default_timezone = :utc end def load_schema begin $stdout = StringIO.new ActiveRecord::Migration.verbose = false load File.join(FIXTURES_PATH, 'schema.rb') ensure $stdout = STDOUT end end module FixtureSetup def fixtures(*tables) table_names = tables.map { |t| t.to_s } fixtures = ActiveRecord::FixtureSet.create_fixtures(ActiverecordTestConnector::FIXTURES_PATH, table_names) @@loaded_fixtures = {} @@fixture_cache = {} unless fixtures.nil? fixtures.each { |f| @@loaded_fixtures[f.table_name] = f } end table_names.each do |table_name| define_method(table_name) do |*names| @@fixture_cache[table_name] ||= {} instances = names.map do |name| if @@loaded_fixtures[table_name][name.to_s] @@fixture_cache[table_name][name] ||= @@loaded_fixtures[table_name][name.to_s].find else raise StandardError, "No fixture with name '#{name}' found for table '#{table_name}'" end end instances.size == 1 ? instances.first : instances end end end end end will_paginate-3.3.1/spec/fixtures/000077500000000000000000000000001410517037000171365ustar00rootroot00000000000000will_paginate-3.3.1/spec/fixtures/admin.rb000066400000000000000000000000551410517037000205530ustar00rootroot00000000000000class Admin < User has_many :companies end will_paginate-3.3.1/spec/fixtures/developer.rb000066400000000000000000000003221410517037000214450ustar00rootroot00000000000000class Developer < User has_and_belongs_to_many :projects, :join_table => 'developers_projects' scope :poor, lambda { where(['salary <= ?', 80000]).order('salary') } def self.per_page() 10 end end will_paginate-3.3.1/spec/fixtures/developers_projects.yml000066400000000000000000000003321410517037000237400ustar00rootroot00000000000000david_action_controller: developer_id: 1 project_id: 2 joined_on: 2004-10-10 david_active_record: developer_id: 1 project_id: 1 joined_on: 2004-10-10 jamis_active_record: developer_id: 2 project_id: 1will_paginate-3.3.1/spec/fixtures/project.rb000066400000000000000000000007051410517037000211330ustar00rootroot00000000000000class Project < ActiveRecord::Base has_and_belongs_to_many :developers, :join_table => 'developers_projects' has_many :topics # :finder_sql => 'SELECT * FROM topics WHERE (topics.project_id = #{id})', # :counter_sql => 'SELECT COUNT(*) FROM topics WHERE (topics.project_id = #{id})' has_many :replies, :through => :topics do def only_recent(params = {}) where(['replies.created_at > ?', 15.minutes.ago]) end end end will_paginate-3.3.1/spec/fixtures/projects.yml000066400000000000000000000001421410517037000215070ustar00rootroot00000000000000active_record: id: 1 name: Active Record action_controller: id: 2 name: Action Controller will_paginate-3.3.1/spec/fixtures/replies.yml000066400000000000000000000010601410517037000213210ustar00rootroot00000000000000witty_retort: id: 1 topic_id: 1 content: Birdman is better! created_at: <%= 6.hours.ago.utc.to_s(:db) %> another: id: 2 topic_id: 2 content: Nuh uh! created_at: <%= 1.hour.ago.utc.to_s(:db) %> spam: id: 3 topic_id: 1 content: Nice site! created_at: <%= 1.hour.ago.utc.to_s(:db) %> decisive: id: 4 topic_id: 4 content: "I'm getting to the bottom of this" created_at: <%= 30.minutes.ago.utc.to_s(:db) %> brave: id: 5 topic_id: 4 content: "AR doesn't scare me a bit" created_at: <%= 10.minutes.ago.utc.to_s(:db) %> will_paginate-3.3.1/spec/fixtures/reply.rb000066400000000000000000000003031410517037000206120ustar00rootroot00000000000000class Reply < ActiveRecord::Base scope :recent, lambda { where(['replies.created_at > ?', 15.minutes.ago]). order('replies.created_at DESC') } validates_presence_of :content end will_paginate-3.3.1/spec/fixtures/schema.rb000066400000000000000000000021261410517037000207240ustar00rootroot00000000000000ActiveRecord::Schema.define do create_table "users", :force => true do |t| t.column "name", :text t.column "salary", :integer, :default => 70000 t.column "created_at", :datetime t.column "updated_at", :datetime t.column "type", :text end create_table "projects", :force => true do |t| t.column "name", :text end create_table "developers_projects", :id => false, :force => true do |t| t.column "developer_id", :integer, :null => false t.column "project_id", :integer, :null => false t.column "joined_on", :date t.column "access_level", :integer, :default => 1 end create_table "topics", :force => true do |t| t.column "project_id", :integer t.column "title", :string t.column "subtitle", :string t.column "content", :text t.column "created_at", :datetime t.column "updated_at", :datetime end create_table "replies", :force => true do |t| t.column "content", :text t.column "created_at", :datetime t.column "updated_at", :datetime t.column "topic_id", :integer end end will_paginate-3.3.1/spec/fixtures/topic.rb000066400000000000000000000003121410517037000205750ustar00rootroot00000000000000class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy belongs_to :project scope :mentions_activerecord, lambda { where(['topics.title LIKE ?', '%ActiveRecord%']) } end will_paginate-3.3.1/spec/fixtures/topics.yml000066400000000000000000000013541410517037000211650ustar00rootroot00000000000000futurama: id: 1 title: Isnt futurama awesome? subtitle: It really is, isnt it. content: I like futurama created_at: <%= 1.day.ago.utc.to_s(:db) %> updated_at: harvey_birdman: id: 2 title: Harvey Birdman is the king of all men subtitle: yup content: He really is created_at: <%= 2.hours.ago.utc.to_s(:db) %> updated_at: rails: id: 3 project_id: 1 title: Rails is nice subtitle: It makes me happy content: except when I have to hack internals to fix pagination. even then really. created_at: <%= 20.minutes.ago.utc.to_s(:db) %> ar: id: 4 project_id: 1 title: ActiveRecord sometimes freaks me out content: "I mean, what's the deal with eager loading?" created_at: <%= 15.minutes.ago.utc.to_s(:db) %> will_paginate-3.3.1/spec/fixtures/user.rb000066400000000000000000000000441410517037000204370ustar00rootroot00000000000000class User < ActiveRecord::Base end will_paginate-3.3.1/spec/fixtures/users.yml000066400000000000000000000006501410517037000210230ustar00rootroot00000000000000david: id: 1 name: David salary: 80000 type: Developer jamis: id: 2 name: Jamis salary: 150000 type: Developer <% for digit in 3..10 %> dev_<%= digit %>: id: <%= digit %> name: fixture_<%= digit %> salary: 100000 type: Developer <% end %> poor_jamis: id: 11 name: Jamis salary: 9000 type: Developer admin: id: 12 name: admin type: Admin goofy: id: 13 name: Goofy type: Admin will_paginate-3.3.1/spec/matchers/000077500000000000000000000000001410517037000170735ustar00rootroot00000000000000will_paginate-3.3.1/spec/matchers/deprecation_matcher.rb000066400000000000000000000007631410517037000234260ustar00rootroot00000000000000require 'stringio' class DeprecationMatcher def initialize(message) @message = message end def matches?(block) @actual = hijack_stderr(&block) PhraseMatcher.new("DEPRECATION WARNING: #{@message}").matches?(@actual) end def failure_message "expected deprecation warning #{@message.inspect}, got #{@actual.inspect}" end private def hijack_stderr err = $stderr $stderr = StringIO.new yield $stderr.string.rstrip ensure $stderr = err end end will_paginate-3.3.1/spec/matchers/phrase_matcher.rb000066400000000000000000000006361410517037000224120ustar00rootroot00000000000000class PhraseMatcher def initialize(string) @string = string @pattern = /\b#{Regexp.escape string}\b/ end def matches?(actual) @actual = actual.to_s @actual =~ @pattern end def failure_message "expected #{@actual.inspect} to contain phrase #{@string.inspect}" end def negative_failure_message "expected #{@actual.inspect} not to contain phrase #{@string.inspect}" end end will_paginate-3.3.1/spec/matchers/query_count_matcher.rb000066400000000000000000000012141410517037000234760ustar00rootroot00000000000000class QueryCountMatcher def initialize(num) @expected_count = num end def matches?(block) run(block) if @expected_count.respond_to? :include? @expected_count.include? @count else @count == @expected_count end end def run(block) $query_count = 0 $query_sql = [] block.call ensure @queries = $query_sql.dup @count = $query_count end def performed_queries @queries end def failure_message "expected #{@expected_count} queries, got #{@count}\n#{@queries.join("\n")}" end def negative_failure_message "expected query count not to be #{@expected_count}" end end will_paginate-3.3.1/spec/page_number_spec.rb000066400000000000000000000045311410517037000211130ustar00rootroot00000000000000require 'spec_helper' require 'will_paginate/page_number' require 'json' describe WillPaginate::PageNumber do describe "valid" do def num WillPaginate::PageNumber.new('12', 'page') end it "== 12" do num.should eq(12) end it "inspects to 'page 12'" do num.inspect.should eq('page 12') end it "is a PageNumber" do (num.instance_of? WillPaginate::PageNumber).should be end it "is a kind of Numeric" do (num.is_a? Numeric).should be end it "is a kind of Integer" do (num.is_a? Integer).should be end it "isn't directly a Integer" do (num.instance_of? Integer).should_not be end it "passes the PageNumber=== type check" do |variable| (WillPaginate::PageNumber === num).should be end it "passes the Numeric=== type check" do |variable| (Numeric === num).should be end it "fails the Numeric=== type check" do |variable| (Integer === num).should_not be end it "serializes as JSON number" do JSON.dump(page: num).should eq('{"page":12}') end end describe "invalid" do def create(value, name = 'page') described_class.new(value, name) end it "errors out on non-int values" do lambda { create(nil) }.should raise_error(WillPaginate::InvalidPage) lambda { create('') }.should raise_error(WillPaginate::InvalidPage) lambda { create('Schnitzel') }.should raise_error(WillPaginate::InvalidPage) end it "errors out on zero or less" do lambda { create(0) }.should raise_error(WillPaginate::InvalidPage) lambda { create(-1) }.should raise_error(WillPaginate::InvalidPage) end it "doesn't error out on zero for 'offset'" do lambda { create(0, 'offset') }.should_not raise_error lambda { create(-1, 'offset') }.should raise_error(WillPaginate::InvalidPage) end end describe "coercion method" do it "defaults to 'page' name" do num = WillPaginate::PageNumber(12) num.inspect.should eq('page 12') end it "accepts a custom name" do num = WillPaginate::PageNumber(12, 'monkeys') num.inspect.should eq('monkeys 12') end it "doesn't affect PageNumber instances" do num = WillPaginate::PageNumber(12) num2 = WillPaginate::PageNumber(num) num2.object_id.should eq(num.object_id) end end end will_paginate-3.3.1/spec/per_page_spec.rb000066400000000000000000000014201410517037000204030ustar00rootroot00000000000000require 'spec_helper' require 'will_paginate/per_page' describe WillPaginate::PerPage do class MyModel extend WillPaginate::PerPage end it "has the default value" do MyModel.per_page.should == 30 WillPaginate.per_page = 10 begin MyModel.per_page.should == 10 ensure WillPaginate.per_page = 30 end end it "casts values to int" do WillPaginate.per_page = '10' begin MyModel.per_page.should == 10 ensure WillPaginate.per_page = 30 end end it "has an explicit value" do MyModel.per_page = 12 begin MyModel.per_page.should == 12 subclass = Class.new(MyModel) subclass.per_page.should == 12 ensure MyModel.send(:remove_instance_variable, '@per_page') end end end will_paginate-3.3.1/spec/spec_helper.rb000066400000000000000000000015471410517037000201120ustar00rootroot00000000000000require 'rspec' require 'view_helpers/view_example_group' Dir[File.expand_path('../matchers/*_matcher.rb', __FILE__)].each { |matcher| require matcher } RSpec.configure do |config| config.include Module.new { protected def include_phrase(string) PhraseMatcher.new(string) end def have_deprecation(msg) DeprecationMatcher.new(msg) end def run_queries(num) QueryCountMatcher.new(num) end def ignore_deprecation ActiveSupport::Deprecation.silence { yield } end def show_queries(&block) counter = QueryCountMatcher.new(nil) counter.run block ensure queries = counter.performed_queries if queries.any? puts queries else puts "no queries" end end } config.mock_with :mocha config.backtrace_exclusion_patterns << /view_example_group/ end will_paginate-3.3.1/spec/view_helpers/000077500000000000000000000000001410517037000177615ustar00rootroot00000000000000will_paginate-3.3.1/spec/view_helpers/action_view_spec.rb000066400000000000000000000325561410517037000236420ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' require 'action_controller' require 'action_view' require 'will_paginate/view_helpers/action_view' require 'will_paginate/collection' Routes = ActionDispatch::Routing::RouteSet.new Routes.draw do get 'dummy/page/:page' => 'dummy#index' get 'dummy/dots/page.:page' => 'dummy#dots' get 'ibocorp(/:page)' => 'ibocorp#index', :constraints => { :page => /\d+/ }, :defaults => { :page => 1 } get 'foo/bar' => 'foo#bar' get 'baz/list' => 'baz#list' end describe WillPaginate::ActionView do before(:all) do I18n.load_path.concat WillPaginate::I18n.load_path I18n.enforce_available_locales = false ActionController::Parameters.permit_all_parameters = false end before(:each) do I18n.reload! end before(:each) do @assigns = {} @controller = DummyController.new @request = @controller.request @template = '<%= will_paginate collection, options %>' end attr_reader :assigns, :controller, :request def render(locals) lookup_context = [] lookup_context = ActionView::LookupContext.new(lookup_context) klass = ActionView::Base klass = klass.with_empty_template_cache if klass.respond_to?(:with_empty_template_cache) @view = klass.new(lookup_context, @assigns, @controller) @view.request = @request @view.singleton_class.send(:include, @controller._routes.url_helpers) @view.render(:inline => @template, :locals => locals) end ## basic pagination ## it "should render" do paginate do |pagination| assert_select 'a[href]', 3 do |elements| validate_page_numbers [2,3,2], elements text(elements[2]).should == 'Next →' end assert_select 'span', 1 do |spans| spans[0]['class'].should == 'previous_page disabled' text(spans[0]).should == '← Previous' end assert_select 'em.current', '1' text(pagination[0]).should == '← Previous 1 2 3 Next →' end end it "should override existing page param value" do request.params :page => 1 paginate do |pagination| assert_select 'a[href]', 3 do |elements| validate_page_numbers [2,3,2], elements end end end it "should render nothing when there is only 1 page" do paginate(:per_page => 30).should be_empty end it "should paginate with options" do paginate({ :page => 2 }, :class => 'will_paginate', :previous_label => 'Prev', :next_label => 'Next') do assert_select 'a[href]', 4 do |elements| validate_page_numbers [1,1,3,3], elements # test rel attribute values: text(elements[0]).should == 'Prev' elements[0]['rel'].should == 'prev' text(elements[1]).should == '1' elements[1]['rel'].should == 'prev' text(elements[3]).should == 'Next' elements[3]['rel'].should == 'next' end assert_select '.current', '2' end end it "should paginate using a custom renderer class" do paginate({}, :renderer => AdditionalLinkAttributesRenderer) do assert_select 'a[default=true]', 3 end end it "should paginate using a custom renderer instance" do renderer = WillPaginate::ActionView::LinkRenderer.new def renderer.gap() '~~' end paginate({ :per_page => 2 }, :inner_window => 0, :outer_window => 0, :renderer => renderer) do assert_select 'span.my-gap', '~~' end renderer = AdditionalLinkAttributesRenderer.new(:title => 'rendered') paginate({}, :renderer => renderer) do assert_select 'a[title=rendered]', 3 end end it "should have classnames on previous/next links" do paginate do |pagination| assert_select 'span.disabled.previous_page:first-child' assert_select 'a.next_page[href]:last-child' end end it "should match expected markup" do paginate expected = <<-HTML HTML expected.strip!.gsub!(/\s{2,}/, ' ') expected_dom = parse_html_document(expected) if expected_dom.respond_to?(:canonicalize) html_document.canonicalize.should == expected_dom.canonicalize else html_document.root.should == expected_dom.root end end it "should output escaped URLs" do paginate({:page => 1, :per_page => 1, :total_entries => 2}, :page_links => false, :params => { :tag => '
' }) assert_select 'a[href]', 1 do |links| query = links.first['href'].split('?', 2)[1] parts = query.gsub('&', '&').split('&').sort parts.should == %w(page=2 tag=%3Cbr%3E) end end ## advanced options for pagination ## it "should be able to render without container" do paginate({}, :container => false) assert_select 'div.pagination', 0, 'main DIV present when it shouldn\'t' assert_select 'a[href]', 3 end it "should be able to render without page links" do paginate({ :page => 2 }, :page_links => false) do assert_select 'a[href]', 2 do |elements| validate_page_numbers [1,3], elements end end end ## other helpers ## it "should render a paginated section" do @template = <<-ERB <%= paginated_section collection, options do %> <%= content_tag :div, '', :id => "developers" %> <% end %> ERB paginate assert_select 'div.pagination', 2 assert_select 'div.pagination + div#developers', 1 end it "should not render a paginated section with a single page" do @template = <<-ERB <%= paginated_section collection, options do %> <%= content_tag :div, '', :id => "developers" %> <% end %> ERB paginate(:total_entries => 1) assert_select 'div.pagination', 0 assert_select 'div#developers', 1 end ## parameter handling in page links ## it "should preserve parameters on GET" do request.params :foo => { :bar => 'baz' } paginate assert_links_match /foo\[bar\]=baz/ end it "doesn't allow tampering with host, port, protocol" do request.params :host => 'disney.com', :port => '99', :protocol => 'ftp' paginate assert_links_match %r{^/foo/bar} assert_no_links_match /disney/ assert_no_links_match /99/ assert_no_links_match /ftp/ end it "doesn't allow tampering with script_name" do request.params :script_name => 'p0wned', :original_script_name => 'p0wned' paginate assert_links_match %r{^/foo/bar} assert_no_links_match /p0wned/ end it "should not preserve parameters on POST" do request.post request.params :foo => 'bar' paginate assert_no_links_match /foo=bar/ end it "should add additional parameters to links" do paginate({}, :params => { :foo => 'bar' }) assert_links_match /foo=bar/ end it "should add anchor parameter" do paginate({}, :params => { :anchor => 'anchor' }) assert_links_match /#anchor$/ end it "should remove arbitrary parameters" do request.params :foo => 'bar' paginate({}, :params => { :foo => nil }) assert_no_links_match /foo=bar/ end it "should override default route parameters" do paginate({}, :params => { :controller => 'baz', :action => 'list' }) assert_links_match %r{\Wbaz/list\W} end it "should paginate with custom page parameter" do paginate({ :page => 2 }, :param_name => :developers_page) do assert_select 'a[href]', 4 do |elements| validate_page_numbers [1,1,3,3], elements, :developers_page end end end it "should paginate with complex custom page parameter" do request.params :developers => { :page => 2 } paginate({ :page => 2 }, :param_name => 'developers[page]') do assert_select 'a[href]', 4 do |links| assert_links_match /\?developers\[page\]=\d+$/, links validate_page_numbers [1,1,3,3], links, 'developers[page]' end end end it "should paginate with custom route page parameter" do request.symbolized_path_parameters.update :controller => 'dummy', :action => 'index' paginate :per_page => 2 do assert_select 'a[href]', 6 do |links| assert_links_match %r{/page/(\d+)$}, links, [2, 3, 4, 5, 6, 2] end end end it "should paginate with custom route with dot separator page parameter" do request.symbolized_path_parameters.update :controller => 'dummy', :action => 'dots' paginate :per_page => 2 do assert_select 'a[href]', 6 do |links| assert_links_match %r{/page\.(\d+)$}, links, [2, 3, 4, 5, 6, 2] end end end it "should paginate with custom route and first page number implicit" do request.symbolized_path_parameters.update :controller => 'ibocorp', :action => 'index' paginate :page => 2, :per_page => 2 do assert_select 'a[href]', 7 do |links| assert_links_match %r{/ibocorp(?:/(\d+))?$}, links, [nil, nil, 3, 4, 5, 6, 3] end end # Routes.recognize_path('/ibocorp/2').should == {:page=>'2', :action=>'index', :controller=>'ibocorp'} # Routes.recognize_path('/ibocorp/foo').should == {:action=>'foo', :controller=>'ibocorp'} end ## internal hardcore stuff ## it "should be able to guess the collection name" do collection = mock collection.expects(:total_pages).returns(1) @template = '<%= will_paginate options %>' controller.controller_name = 'developers' assigns['developers'] = collection paginate(nil) end it "should fail if the inferred collection is nil" do @template = '<%= will_paginate options %>' controller.controller_name = 'developers' lambda { paginate(nil) }.should raise_error(ActionView::TemplateError, /@developers/) end ## i18n it "is able to translate previous/next labels" do translation :will_paginate => { :previous_label => 'Go back', :next_label => 'Load more' } paginate do |pagination| assert_select 'span.disabled:first-child', 'Go back' assert_select 'a[rel=next]', 'Load more' end end it "renders using ActionView helpers on a custom object" do helper = Class.new { attr_reader :controller include ActionView::Helpers::UrlHelper include Routes.url_helpers include WillPaginate::ActionView }.new helper.default_url_options[:controller] = 'dummy' collection = WillPaginate::Collection.new(2, 1, 3) @render_output = helper.will_paginate(collection) assert_select 'a[href]', 4 do |links| urls = links.map {|l| l['href'] }.uniq urls.should == ['/dummy/page/1', '/dummy/page/3'] end end it "renders using ActionDispatch helper on a custom object" do helper = Class.new { include ActionDispatch::Routing::UrlFor include Routes.url_helpers include WillPaginate::ActionView }.new helper.default_url_options.update \ :only_path => true, :controller => 'dummy' collection = WillPaginate::Collection.new(2, 1, 3) @render_output = helper.will_paginate(collection) assert_select 'a[href]', 4 do |links| urls = links.map {|l| l['href'] }.uniq urls.should == ['/dummy/page/1', '/dummy/page/3'] end end # TODO: re-enable once Rails 6.1.4 ships xit "page_entries_info" do @template = "<%= page_entries_info collection, options %>" output = render( collection: WillPaginate::Collection.new(1, 1, 3), options: {html: false}, ) output.should == "Displaying entries 1 - 0 of 3 in total" end private def translation(data) I18n.available_locales # triggers loading existing translations I18n.backend.store_translations(:en, data) end # Normalizes differences between HTML::Document and Nokogiri::HTML def text(node) node.inner_text.gsub('→', '→').gsub('←', '←') end end class AdditionalLinkAttributesRenderer < WillPaginate::ActionView::LinkRenderer def initialize(link_attributes = nil) super() @additional_link_attributes = link_attributes || { :default => 'true' } end def link(text, target, attributes = {}) super(text, target, attributes.merge(@additional_link_attributes)) end end class DummyController attr_reader :request attr_accessor :controller_name include ActionController::UrlFor include Routes.url_helpers def initialize @request = DummyRequest.new(self) end def params @request.params end def env {} end def _prefixes [] end end class IbocorpController < DummyController end class DummyRequest attr_accessor :symbolized_path_parameters alias :path_parameters :symbolized_path_parameters def initialize(controller) @controller = controller @get = true @params = {}.with_indifferent_access @symbolized_path_parameters = { :controller => 'foo', :action => 'bar' } end def routes @controller._routes end def get? @get end def post @get = false end def relative_url_root '' end def script_name '' end def params(more = nil) @params.update(more) if more ActionController::Parameters.new(@params) end def host_with_port 'example.com' end alias host host_with_port def optional_port '' end def protocol 'http:' end end will_paginate-3.3.1/spec/view_helpers/base_spec.rb000066400000000000000000000111331410517037000222310ustar00rootroot00000000000000require 'spec_helper' require 'will_paginate/view_helpers' require 'will_paginate/array' require 'active_support' require 'active_support/core_ext/string/inflections' require 'active_support/inflections' describe WillPaginate::ViewHelpers do before(:all) do # make sure default translations aren't loaded I18n.load_path.clear I18n.enforce_available_locales = false end before(:each) do I18n.reload! end include WillPaginate::ViewHelpers describe "will_paginate" do it "should render" do collection = WillPaginate::Collection.new(1, 2, 4) renderer = mock 'Renderer' renderer.expects(:prepare).with(collection, instance_of(Hash), self) renderer.expects(:to_html).returns('') will_paginate(collection, :renderer => renderer).should == '' end it "should return nil for single-page collections" do collection = mock 'Collection', :total_pages => 1 will_paginate(collection).should be_nil end it "should call html_safe on result" do collection = WillPaginate::Collection.new(1, 2, 4) html = mock 'HTML' html.expects(:html_safe).returns(html) renderer = mock 'Renderer' renderer.stubs(:prepare) renderer.expects(:to_html).returns(html) will_paginate(collection, :renderer => renderer).should eql(html) end end describe "pagination_options" do let(:pagination_options) { WillPaginate::ViewHelpers.pagination_options } it "deprecates setting :renderer" do begin lambda { pagination_options[:renderer] = 'test' }.should have_deprecation("pagination_options[:renderer] shouldn't be set") ensure pagination_options.delete :renderer end end end describe "page_entries_info" do before :all do @array = ('a'..'z').to_a end def info(params, options = {}) collection = Hash === params ? @array.paginate(params) : params page_entries_info collection, {:html => false}.merge(options) end it "should display middle results and total count" do info(:page => 2, :per_page => 5).should == "Displaying strings 6 - 10 of 26 in total" end it "uses translation if available" do translation :will_paginate => { :page_entries_info => {:multi_page => 'Showing %{from} - %{to}'} } info(:page => 2, :per_page => 5).should == "Showing 6 - 10" end it "uses specific translation if available" do translation :will_paginate => { :page_entries_info => {:multi_page => 'Showing %{from} - %{to}'}, :string => { :page_entries_info => {:multi_page => 'Strings %{from} to %{to}'} } } info(:page => 2, :per_page => 5).should == "Strings 6 to 10" end it "should output HTML by default" do info({ :page => 2, :per_page => 5 }, :html => true).should == "Displaying strings 6 - 10 of 26 in total" end it "should display shortened end results" do info(:page => 7, :per_page => 4).should include_phrase('strings 25 - 26') end it "should handle longer class names" do collection = @array.paginate(:page => 2, :per_page => 5) model = stub('Class', :name => 'ProjectType', :to_s => 'ProjectType') collection.first.stubs(:class).returns(model) info(collection).should include_phrase('project types') end it "should adjust output for single-page collections" do info(('a'..'d').to_a.paginate(:page => 1, :per_page => 5)).should == "Displaying all 4 strings" info(['a'].paginate(:page => 1, :per_page => 5)).should == "Displaying 1 string" end it "should display 'no entries found' for empty collections" do info([].paginate(:page => 1, :per_page => 5)).should == "No entries found" end it "uses model_name.human when available" do name = stub('model name', :i18n_key => :flower_key) name.expects(:human).with(:count => 1).returns('flower') model = stub('Class', :model_name => name) collection = [1].paginate(:page => 1) info(collection, :model => model).should == "Displaying 1 flower" end it "uses custom translation instead of model_name.human" do name = stub('model name', :i18n_key => :flower_key) name.expects(:human).never model = stub('Class', :model_name => name) translation :will_paginate => {:models => {:flower_key => 'tulip'}} collection = [1].paginate(:page => 1) info(collection, :model => model).should == "Displaying 1 tulip" end private def translation(data) I18n.backend.store_translations(:en, data) end end end will_paginate-3.3.1/spec/view_helpers/link_renderer_base_spec.rb000066400000000000000000000052341410517037000251410ustar00rootroot00000000000000require 'spec_helper' require 'will_paginate/view_helpers/link_renderer_base' require 'will_paginate/collection' describe WillPaginate::ViewHelpers::LinkRendererBase do before do @renderer = described_class.new end it "should raise error when unprepared" do lambda { @renderer.pagination }.should raise_error end it "should prepare with collection and options" do prepare({}) @renderer.send(:current_page).should == 1 end it "should have total_pages accessor" do prepare :total_pages => 42 @renderer.send(:total_pages).should == 42 end it "should clear old cached values when prepared" do prepare(:total_pages => 1) @renderer.send(:total_pages).should == 1 # prepare with different object: prepare(:total_pages => 2) @renderer.send(:total_pages).should == 2 end it "should have pagination definition" do prepare({ :total_pages => 1 }, :page_links => true) @renderer.pagination.should == [:previous_page, 1, :next_page] end describe "visible page numbers" do it "should calculate windowed visible links" do prepare({ :page => 6, :total_pages => 11 }, :inner_window => 1, :outer_window => 1) showing_pages 1, 2, :gap, 5, 6, 7, :gap, 10, 11 end it "should eliminate small gaps" do prepare({ :page => 6, :total_pages => 11 }, :inner_window => 2, :outer_window => 1) # pages 4 and 8 appear instead of the gap showing_pages 1..11 end it "should support having no windows at all" do prepare({ :page => 4, :total_pages => 7 }, :inner_window => 0, :outer_window => 0) showing_pages 1, :gap, 4, :gap, 7 end it "should adjust upper limit if lower is out of bounds" do prepare({ :page => 1, :total_pages => 10 }, :inner_window => 2, :outer_window => 1) showing_pages 1, 2, 3, 4, 5, :gap, 9, 10 end it "should adjust lower limit if upper is out of bounds" do prepare({ :page => 10, :total_pages => 10 }, :inner_window => 2, :outer_window => 1) showing_pages 1, 2, :gap, 6, 7, 8, 9, 10 end def showing_pages(*pages) pages = pages.first.to_a if Array === pages.first or Range === pages.first @renderer.send(:windowed_page_numbers).should == pages end end protected def collection(params = {}) if params[:total_pages] params[:per_page] = 1 params[:total_entries] = params[:total_pages] end WillPaginate::Collection.new(params[:page] || 1, params[:per_page] || 30, params[:total_entries]) end def prepare(collection_options, options = {}) @renderer.prepare(collection(collection_options), options) end end will_paginate-3.3.1/spec/view_helpers/view_example_group.rb000066400000000000000000000046571410517037000242230ustar00rootroot00000000000000require 'active_support' require 'stringio' require 'minitest/assertions' require 'rails/dom/testing/assertions' require 'will_paginate/array' module ViewExampleGroup include Rails::Dom::Testing::Assertions::SelectorAssertions include Minitest::Assertions def assert(value, message) raise message unless value end def paginate(collection = {}, options = {}, &block) if collection.instance_of? Hash page_options = { :page => 1, :total_entries => 11, :per_page => 4 }.merge(collection) collection = [1].paginate(page_options) end locals = { :collection => collection, :options => options } @render_output = render(locals) @html_document = nil if block_given? classname = options[:class] || WillPaginate::ViewHelpers.pagination_options[:class] assert_select("div.#{classname}", 1, 'no main DIV', &block) end @render_output end def parse_html_document(html) Nokogiri::HTML::Document.parse(html) end def html_document @html_document ||= parse_html_document(@render_output) end def document_root_element html_document.root end def response_from_page_or_rjs html_document.root end def validate_page_numbers(expected, links, param_name = :page) param_pattern = /\W#{Regexp.escape(param_name.to_s)}=([^&]*)/ links.map { |el| unescape_href(el) =~ param_pattern $1 ? $1.to_i : $1 }.should == expected end def assert_links_match(pattern, links = nil, numbers = nil) links ||= assert_select 'div.pagination a[href]' do |elements| elements end pages = [] if numbers links.each do |el| href = unescape_href(el) href.should =~ pattern if numbers href =~ pattern pages << ($1.nil?? nil : $1.to_i) end end pages.should == numbers if numbers end def assert_no_links_match(pattern) assert_select 'div.pagination a[href]' do |elements| elements.each do |el| unescape_href(el).should_not =~ pattern end end end def unescape_href(el) CGI.unescape CGI.unescapeHTML(el['href']) end def build_message(message, pattern, *args) built_message = pattern.dup for value in args built_message.sub! '?', value.inspect end built_message end end RSpec.configure do |config| config.include ViewExampleGroup, :type => :view, :example_group => { :file_path => %r{spec/view_helpers/} } end will_paginate-3.3.1/will_paginate.gemspec000066400000000000000000000032711410517037000205220ustar00rootroot00000000000000# encoding: utf-8 require 'rbconfig' require File.expand_path('../lib/will_paginate/version', __FILE__) Gem::Specification.new do |s| s.name = 'will_paginate' s.version = WillPaginate::VERSION::STRING s.required_ruby_version = '>= 2.0' s.summary = "Pagination plugin for web frameworks and other apps" s.description = "will_paginate provides a simple API for performing paginated queries with Active Record, DataMapper and Sequel, and includes helpers for rendering pagination links in Rails, Sinatra, Hanami, and Merb web apps." s.authors = ['Mislav Marohnić'] s.email = 'mislav.marohnic@gmail.com' s.homepage = 'https://github.com/mislav/will_paginate' s.license = 'MIT' s.metadata = { 'bug_tracker_uri' => 'https://github.com/mislav/will_paginate/issues', 'changelog_uri' => "https://github.com/mislav/will_paginate/releases/tag/v#{s.version}", 'documentation_uri' => "https://www.rubydoc.info/gems/will_paginate/#{s.version}", 'source_code_uri' => "https://github.com/mislav/will_paginate/tree/v#{s.version}", 'wiki_uri' => 'https://github.com/mislav/will_paginate/wiki' } s.rdoc_options = ['--main', 'README.md', '--charset=UTF-8'] s.extra_rdoc_files = ['README.md', 'LICENSE'] s.files = Dir['Rakefile', '{bin,lib,test,spec}/**/*', 'README*', 'LICENSE*'] # include only files in version control git_dir = File.expand_path('../.git', __FILE__) void = defined?(File::NULL) ? File::NULL : RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw/ ? 'NUL' : '/dev/null' if File.directory?(git_dir) and system "git --version >>#{void} 2>&1" s.files &= `git --git-dir='#{git_dir}' ls-files -z`.split("\0") end end