kaminari 0.12.4 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of kaminari might be problematic. Click here for more details.
- data/.travis.yml +6 -0
- data/CHANGELOG +62 -0
- data/{LICENSE.txt → MIT-LICENSE} +0 -0
- data/README.rdoc +81 -14
- data/kaminari.gemspec +18 -5
- data/lib/generators/kaminari/templates/kaminari_config.rb +1 -0
- data/lib/generators/kaminari/views_generator.rb +1 -1
- data/lib/kaminari.rb +73 -2
- data/lib/kaminari/config.rb +3 -1
- data/lib/kaminari/helpers/action_view_extension.rb +74 -20
- data/lib/kaminari/helpers/paginator.rb +23 -5
- data/lib/kaminari/helpers/sinatra_helpers.rb +119 -0
- data/lib/kaminari/hooks.rb +35 -0
- data/lib/kaminari/models/active_record_extension.rb +12 -15
- data/lib/kaminari/models/active_record_model_extension.rb +20 -0
- data/lib/kaminari/models/active_record_relation_methods.rb +23 -13
- data/lib/kaminari/models/array_extension.rb +33 -15
- data/lib/kaminari/models/data_mapper_collection_methods.rb +15 -0
- data/lib/kaminari/models/data_mapper_extension.rb +48 -0
- data/lib/kaminari/models/mongo_mapper_extension.rb +3 -3
- data/lib/kaminari/models/mongoid_criteria_methods.rb +15 -10
- data/lib/kaminari/models/mongoid_extension.rb +7 -5
- data/lib/kaminari/models/page_scope_methods.rb +27 -26
- data/lib/kaminari/models/plucky_criteria_methods.rb +9 -12
- data/lib/kaminari/railtie.rb +2 -31
- data/lib/kaminari/sinatra.rb +13 -0
- data/lib/kaminari/version.rb +1 -1
- data/spec/config/config_spec.rb +1 -1
- data/spec/fake_app.rb +4 -0
- data/spec/fake_gem.rb +6 -0
- data/spec/helpers/action_view_extension_spec.rb +105 -10
- data/spec/helpers/helpers_spec.rb +1 -1
- data/spec/helpers/sinatra_helpers_spec.rb +174 -0
- data/spec/helpers/tags_spec.rb +1 -1
- data/spec/models/active_record_relation_methods_spec.rb +1 -1
- data/spec/models/array_spec.rb +17 -1
- data/spec/models/data_mapper_spec.rb +181 -0
- data/spec/models/default_per_page_spec.rb +1 -1
- data/spec/models/mongo_mapper_spec.rb +14 -11
- data/spec/models/mongoid_spec.rb +63 -6
- data/spec/models/scopes_spec.rb +154 -140
- data/spec/{acceptance → requests}/users_spec.rb +3 -2
- data/spec/spec_helper.rb +3 -1
- data/spec/spec_helper_for_sinatra.rb +13 -0
- data/spec/support/matchers.rb +6 -0
- metadata +263 -170
- data/spec/acceptance/acceptance_helper.rb +0 -5
- data/spec/acceptance/support/helpers.rb +0 -5
- data/spec/acceptance/support/paths.rb +0 -9
@@ -1,5 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'action_view'
|
3
|
+
require 'action_view/log_subscriber'
|
2
4
|
require 'action_view/context'
|
5
|
+
require 'kaminari/helpers/tags'
|
3
6
|
|
4
7
|
module Kaminari
|
5
8
|
module Helpers
|
@@ -31,13 +34,28 @@ module Kaminari
|
|
31
34
|
end
|
32
35
|
|
33
36
|
# enumerate each page providing PageProxy object as the block parameter
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
# Because of performance reason, this doesn't actually enumerate all pages but pages that are seemingly relevant to the paginator.
|
38
|
+
# "Relevant" pages are:
|
39
|
+
# * pages inside the left outer window plus one for showing the gap tag
|
40
|
+
# * pages inside the inner window plus one on the left plus one on the right for showing the gap tags
|
41
|
+
# * pages inside the right outer window plus one for showing the gap tag
|
42
|
+
def each_relevant_page
|
43
|
+
return to_enum(:each_relevant_page) unless block_given?
|
44
|
+
|
45
|
+
relevant_pages(@window_options.merge(@options)).each do |i|
|
38
46
|
yield PageProxy.new(@window_options.merge(@options), i, @last)
|
39
47
|
end
|
40
48
|
end
|
49
|
+
alias each_page each_relevant_page
|
50
|
+
|
51
|
+
def relevant_pages(options)
|
52
|
+
left_window_plus_one = 1.upto(options[:left] + 1).to_a
|
53
|
+
right_window_plus_one = (options[:num_pages] - options[:right]).upto(options[:num_pages]).to_a
|
54
|
+
inside_window_plus_each_sides = (options[:current_page] - options[:window] - 1).upto(options[:current_page] + options[:window] + 1).to_a
|
55
|
+
|
56
|
+
(left_window_plus_one + inside_window_plus_each_sides + right_window_plus_one).uniq.sort.reject {|x| (x < 1) || (x > options[:num_pages])}
|
57
|
+
end
|
58
|
+
private :relevant_pages
|
41
59
|
|
42
60
|
def page_tag(page)
|
43
61
|
@last = Page.new @template, @options.merge(:page => page)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'active_support/core_ext/object'
|
2
|
+
require 'active_support/core_ext/string'
|
3
|
+
|
4
|
+
begin
|
5
|
+
|
6
|
+
require 'padrino-helpers'
|
7
|
+
module Kaminari::Helpers
|
8
|
+
module SinatraHelpers
|
9
|
+
class << self
|
10
|
+
def registered(app)
|
11
|
+
app.register Padrino::Helpers
|
12
|
+
app.helpers HelperMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
alias included registered
|
16
|
+
end
|
17
|
+
|
18
|
+
class ActionViewTemplateProxy
|
19
|
+
def initialize(opts={})
|
20
|
+
@current_path = opts[:current_path]
|
21
|
+
@param_name = (opts[:param_name] || :page).to_sym
|
22
|
+
@current_params = opts[:current_params]
|
23
|
+
@current_params.delete(@param_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def render(*args)
|
27
|
+
base = ActionView::Base.new.tap do |a|
|
28
|
+
a.view_paths << File.expand_path('../../../../app/views', __FILE__)
|
29
|
+
end
|
30
|
+
base.render(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def url_for(params)
|
34
|
+
extra_params = {}
|
35
|
+
if page = params[@param_name] and page != 1
|
36
|
+
extra_params[@param_name] = page
|
37
|
+
end
|
38
|
+
query = @current_params.merge(extra_params)
|
39
|
+
@current_path + (query.empty? ? '' : "?#{query.to_query}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def params
|
43
|
+
@current_params
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module HelperMethods
|
48
|
+
# A helper that renders the pagination links - for Sinatra.
|
49
|
+
#
|
50
|
+
# <%= paginate @articles %>
|
51
|
+
#
|
52
|
+
# ==== Options
|
53
|
+
# * <tt>:window</tt> - The "inner window" size (4 by default).
|
54
|
+
# * <tt>:outer_window</tt> - The "outer window" size (0 by default).
|
55
|
+
# * <tt>:left</tt> - The "left outer window" size (0 by default).
|
56
|
+
# * <tt>:right</tt> - The "right outer window" size (0 by default).
|
57
|
+
# * <tt>:params</tt> - url_for parameters for the links (:id, :locale, etc.)
|
58
|
+
# * <tt>:param_name</tt> - parameter name for page number in the links (:page by default)
|
59
|
+
# * <tt>:remote</tt> - Ajax? (false by default)
|
60
|
+
# * <tt>:ANY_OTHER_VALUES</tt> - Any other hash key & values would be directly passed into each tag as :locals value.
|
61
|
+
def paginate(scope, options = {}, &block)
|
62
|
+
current_path = env['PATH_INFO'] rescue nil
|
63
|
+
current_params = Rack::Utils.parse_query(env['QUERY_STRING']).symbolize_keys rescue {}
|
64
|
+
paginator = Kaminari::Helpers::Paginator.new(
|
65
|
+
ActionViewTemplateProxy.new(:current_params => current_params, :current_path => current_path, :param_name => options[:param_name] || Kaminari.config.param_name),
|
66
|
+
options.reverse_merge(:current_page => scope.current_page, :num_pages => scope.num_pages, :per_page => scope.limit_value, :param_name => Kaminari.config.param_name, :remote => false)
|
67
|
+
)
|
68
|
+
paginator.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
# A simple "Twitter like" pagination link that creates a link to the next page.
|
72
|
+
# Works on Sinatra.
|
73
|
+
#
|
74
|
+
# ==== Examples
|
75
|
+
# Basic usage:
|
76
|
+
#
|
77
|
+
# <%= link_to_next_page @items, 'Next Page' %>
|
78
|
+
#
|
79
|
+
# Ajax:
|
80
|
+
#
|
81
|
+
# <%= link_to_next_page @items, 'Next Page', :remote => true %>
|
82
|
+
#
|
83
|
+
# By default, it renders nothing if there are no more results on the next page.
|
84
|
+
# You can customize this output by passing a parameter <tt>:placeholder</tt>.
|
85
|
+
#
|
86
|
+
# <%= link_to_next_page @items, 'Next Page', :placeholder => %{<span>No More Pages</span>} %>
|
87
|
+
#
|
88
|
+
def link_to_next_page(scope, name, options = {})
|
89
|
+
params = options.delete(:params) || (Rack::Utils.parse_query(env['QUERY_STRING']).symbolize_keys rescue {})
|
90
|
+
param_name = options.delete(:param_name) || Kaminari.config.param_name
|
91
|
+
placeholder = options.delete(:placeholder) || ""
|
92
|
+
query = params.merge(param_name => (scope.current_page + 1))
|
93
|
+
unless scope.last_page?
|
94
|
+
link_to name, env['PATH_INFO'] + (query.empty? ? '' : "?#{query.to_query}"), options.merge(:rel => 'next')
|
95
|
+
else
|
96
|
+
placeholder
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
if defined? I18n
|
104
|
+
I18n.load_path += Dir.glob(File.expand_path('../../../../config/locales/*.yml', __FILE__))
|
105
|
+
end
|
106
|
+
|
107
|
+
rescue LoadError
|
108
|
+
|
109
|
+
$stderr.puts "[!]You shold install `padrino-helpers' gem if you want to use kaminari's pagination helpers with Sinatra."
|
110
|
+
$stderr.puts "[!]Kaminari::Helpers::SinatraHelper does nothing now..."
|
111
|
+
|
112
|
+
module Kaminari::Helpers
|
113
|
+
module SinatraHelper
|
114
|
+
def self.registered(*)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Kaminari
|
2
|
+
class Hooks
|
3
|
+
def self.init!
|
4
|
+
ActiveSupport.on_load(:active_record) do
|
5
|
+
require 'kaminari/models/active_record_extension'
|
6
|
+
::ActiveRecord::Base.send :include, Kaminari::ActiveRecordExtension
|
7
|
+
end
|
8
|
+
|
9
|
+
if defined? ::Mongoid
|
10
|
+
require 'kaminari/models/mongoid_extension'
|
11
|
+
::Mongoid::Document.send :include, Kaminari::MongoidExtension::Document
|
12
|
+
::Mongoid::Criteria.send :include, Kaminari::MongoidExtension::Criteria
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveSupport.on_load(:mongo_mapper) do
|
16
|
+
require 'kaminari/models/mongo_mapper_extension'
|
17
|
+
::MongoMapper::Document.send :include, Kaminari::MongoMapperExtension::Document
|
18
|
+
::Plucky::Query.send :include, Kaminari::PluckyCriteriaMethods
|
19
|
+
::Plucky::Query.send :include, Kaminari::PageScopeMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
if defined? ::DataMapper
|
23
|
+
require 'kaminari/models/data_mapper_extension'
|
24
|
+
::DataMapper::Collection.send :include, Kaminari::DataMapperExtension::Collection
|
25
|
+
::DataMapper::Model.append_extensions Kaminari::DataMapperExtension::Model
|
26
|
+
# ::DataMapper::Model.send :extend, Kaminari::DataMapperExtension::Model
|
27
|
+
end
|
28
|
+
require 'kaminari/models/array_extension'
|
29
|
+
|
30
|
+
ActiveSupport.on_load(:action_view) do
|
31
|
+
::ActionView::Base.send :include, Kaminari::ActionViewExtension
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,24 +1,21 @@
|
|
1
|
-
require
|
1
|
+
require 'kaminari/models/active_record_model_extension'
|
2
2
|
|
3
3
|
module Kaminari
|
4
4
|
module ActiveRecordExtension
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
included do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
include Kaminari::
|
12
|
-
|
13
|
-
# Fetch the values at the specified page number
|
14
|
-
# Model.page(5)
|
15
|
-
scope :page, Proc.new {|num|
|
16
|
-
limit(default_per_page).offset(default_per_page * ([num.to_i, 1].max - 1))
|
17
|
-
} do
|
18
|
-
include Kaminari::ActiveRecordRelationMethods
|
19
|
-
include Kaminari::PageScopeMethods
|
20
|
-
end
|
7
|
+
# Future subclasses will pick up the model extension
|
8
|
+
class << self
|
9
|
+
def inherited_with_kaminari(kls) #:nodoc:
|
10
|
+
inherited_without_kaminari kls
|
11
|
+
kls.send(:include, Kaminari::ActiveRecordModelExtension) if kls.superclass == ActiveRecord::Base
|
21
12
|
end
|
13
|
+
alias_method_chain :inherited, :kaminari
|
14
|
+
end
|
15
|
+
|
16
|
+
# Existing subclasses pick up the model extension as well
|
17
|
+
self.descendants.each do |kls|
|
18
|
+
kls.send(:include, Kaminari::ActiveRecordModelExtension) if kls.superclass == ActiveRecord::Base
|
22
19
|
end
|
23
20
|
end
|
24
21
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'kaminari/models/active_record_relation_methods'
|
2
|
+
|
3
|
+
module Kaminari
|
4
|
+
module ActiveRecordModelExtension
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
self.send(:include, Kaminari::ConfigurationMethods)
|
9
|
+
|
10
|
+
# Fetch the values at the specified page number
|
11
|
+
# Model.page(5)
|
12
|
+
self.scope Kaminari.config.page_method_name, Proc.new {|num|
|
13
|
+
limit(default_per_page).offset(default_per_page * ([num.to_i, 1].max - 1))
|
14
|
+
} do
|
15
|
+
include Kaminari::ActiveRecordRelationMethods
|
16
|
+
include Kaminari::PageScopeMethods
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,23 +1,33 @@
|
|
1
1
|
module Kaminari
|
2
2
|
module ActiveRecordRelationMethods
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def count #:nodoc:
|
9
|
-
limit_value ? length : super
|
10
|
-
end
|
3
|
+
# a workaround for AR 3.0.x that returns 0 for #count when page > 1
|
4
|
+
# if +limit_value+ is specified, load all the records and count them
|
5
|
+
if ActiveRecord::VERSION::STRING < '3.1'
|
6
|
+
def count #:nodoc:
|
7
|
+
limit_value ? length : super
|
11
8
|
end
|
9
|
+
end
|
12
10
|
|
13
|
-
|
14
|
-
|
11
|
+
def total_count #:nodoc:
|
12
|
+
# #count overrides the #select which could include generated columns referenced in #order, so skip #order here, where it's irrelevant to the result anyway
|
13
|
+
@total_count ||= begin
|
15
14
|
c = except(:offset, :limit, :order)
|
15
|
+
|
16
|
+
# a workaround for 3.1.beta1 bug. see: https://github.com/rails/rails/issues/406
|
17
|
+
c = c.reorder nil
|
18
|
+
|
16
19
|
# Remove includes only if they are irrelevant
|
17
20
|
c = c.except(:includes) unless references_eager_loaded_tables?
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
|
22
|
+
# a workaround to count the actual model instances on distinct query because count + distinct returns wrong value in some cases. see https://github.com/amatsuda/kaminari/pull/160
|
23
|
+
uses_distinct_sql_statement = c.to_sql =~ /DISTINCT/i
|
24
|
+
if uses_distinct_sql_statement
|
25
|
+
c.length
|
26
|
+
else
|
27
|
+
# .group returns an OrderdHash that responds to #count
|
28
|
+
c = c.count
|
29
|
+
c.respond_to?(:count) ? c.count : c
|
30
|
+
end
|
21
31
|
end
|
22
32
|
end
|
23
33
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'active_support/core_ext/module'
|
1
2
|
module Kaminari
|
2
3
|
# Kind of Array that can paginate
|
3
4
|
class PaginatableArray < Array
|
@@ -5,38 +6,55 @@ module Kaminari
|
|
5
6
|
|
6
7
|
attr_internal_accessor :limit_value, :offset_value
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
# ==== Options
|
10
|
+
# * <tt>:limit</tt> - limit
|
11
|
+
# * <tt>:offset</tt> - offset
|
12
|
+
# * <tt>:total_count</tt> - total_count
|
13
|
+
def initialize(original_array = [], options = {})
|
14
|
+
@_original_array, @_limit_value, @_offset_value, @_total_count = original_array, (options[:limit] || default_per_page).to_i, options[:offset].to_i, options[:total_count]
|
15
|
+
|
16
|
+
if options[:limit] && options[:offset]
|
17
|
+
class << self
|
18
|
+
include Kaminari::PageScopeMethods
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if options[:total_count]
|
23
|
+
super original_array
|
24
|
+
else
|
25
|
+
super(original_array[@_offset_value, @_limit_value] || [])
|
26
|
+
end
|
11
27
|
end
|
12
28
|
|
13
29
|
# items at the specified "page"
|
14
|
-
|
15
|
-
|
16
|
-
|
30
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
31
|
+
def #{Kaminari.config.page_method_name}(num = 1)
|
32
|
+
offset(limit_value * ([num.to_i, 1].max - 1))
|
33
|
+
end
|
34
|
+
RUBY
|
17
35
|
|
18
36
|
# returns another chunk of the original array
|
19
37
|
def limit(num)
|
20
|
-
self.class.new @_original_array, num,
|
38
|
+
self.class.new @_original_array, :limit => num, :offset => @_offset_value, :total_count => @_total_count
|
21
39
|
end
|
22
40
|
|
23
41
|
# total item numbers of the original array
|
24
42
|
def total_count
|
25
|
-
@_original_array.count
|
43
|
+
@_total_count || @_original_array.count
|
26
44
|
end
|
27
45
|
|
28
46
|
# returns another chunk of the original array
|
29
47
|
def offset(num)
|
30
|
-
|
31
|
-
class << arr
|
32
|
-
include Kaminari::PageScopeMethods
|
33
|
-
end
|
34
|
-
arr
|
48
|
+
self.class.new @_original_array, :limit => @_limit_value, :offset => num, :total_count => @_total_count
|
35
49
|
end
|
36
50
|
end
|
37
51
|
|
38
52
|
# Wrap an Array object to make it paginatable
|
39
|
-
|
40
|
-
|
53
|
+
# ==== Options
|
54
|
+
# * <tt>:limit</tt> - limit
|
55
|
+
# * <tt>:offset</tt> - offset
|
56
|
+
# * <tt>:total_count</tt> - total_count
|
57
|
+
def self.paginate_array(array, options = {})
|
58
|
+
PaginatableArray.new array, options
|
41
59
|
end
|
42
60
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Kaminari
|
2
|
+
module DataMapperCollectionMethods
|
3
|
+
def limit_value #:nodoc:
|
4
|
+
query.options[:limit] || 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def offset_value #:nodoc:
|
8
|
+
query.options[:offset] || 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def total_count #:nodoc:
|
12
|
+
model.count(query.options.except(:limit, :offset, :order))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'kaminari/models/data_mapper_collection_methods'
|
2
|
+
|
3
|
+
module Kaminari
|
4
|
+
module DataMapperExtension
|
5
|
+
module Paginatable
|
6
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
7
|
+
def #{Kaminari.config.page_method_name}(num = 1)
|
8
|
+
num = [num.to_i, 1].max - 1
|
9
|
+
all(:limit => default_per_page, :offset => default_per_page * num).extend Paginating
|
10
|
+
end
|
11
|
+
RUBY
|
12
|
+
end
|
13
|
+
|
14
|
+
module Paginating
|
15
|
+
include Kaminari::PageScopeMethods
|
16
|
+
|
17
|
+
def all(options={})
|
18
|
+
super.extend Paginating
|
19
|
+
end
|
20
|
+
|
21
|
+
def per(num)
|
22
|
+
super.extend Paginating
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Collection
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
included do
|
29
|
+
include Kaminari::ConfigurationMethods::ClassMethods
|
30
|
+
include Kaminari::DataMapperCollectionMethods
|
31
|
+
include Paginatable
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module Model
|
36
|
+
include Kaminari::ConfigurationMethods::ClassMethods
|
37
|
+
include Paginatable
|
38
|
+
|
39
|
+
def limit(val)
|
40
|
+
all(:limit => val)
|
41
|
+
end
|
42
|
+
|
43
|
+
def offset(val)
|
44
|
+
all(:offset => val)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|