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.

Files changed (49) hide show
  1. data/.travis.yml +6 -0
  2. data/CHANGELOG +62 -0
  3. data/{LICENSE.txt → MIT-LICENSE} +0 -0
  4. data/README.rdoc +81 -14
  5. data/kaminari.gemspec +18 -5
  6. data/lib/generators/kaminari/templates/kaminari_config.rb +1 -0
  7. data/lib/generators/kaminari/views_generator.rb +1 -1
  8. data/lib/kaminari.rb +73 -2
  9. data/lib/kaminari/config.rb +3 -1
  10. data/lib/kaminari/helpers/action_view_extension.rb +74 -20
  11. data/lib/kaminari/helpers/paginator.rb +23 -5
  12. data/lib/kaminari/helpers/sinatra_helpers.rb +119 -0
  13. data/lib/kaminari/hooks.rb +35 -0
  14. data/lib/kaminari/models/active_record_extension.rb +12 -15
  15. data/lib/kaminari/models/active_record_model_extension.rb +20 -0
  16. data/lib/kaminari/models/active_record_relation_methods.rb +23 -13
  17. data/lib/kaminari/models/array_extension.rb +33 -15
  18. data/lib/kaminari/models/data_mapper_collection_methods.rb +15 -0
  19. data/lib/kaminari/models/data_mapper_extension.rb +48 -0
  20. data/lib/kaminari/models/mongo_mapper_extension.rb +3 -3
  21. data/lib/kaminari/models/mongoid_criteria_methods.rb +15 -10
  22. data/lib/kaminari/models/mongoid_extension.rb +7 -5
  23. data/lib/kaminari/models/page_scope_methods.rb +27 -26
  24. data/lib/kaminari/models/plucky_criteria_methods.rb +9 -12
  25. data/lib/kaminari/railtie.rb +2 -31
  26. data/lib/kaminari/sinatra.rb +13 -0
  27. data/lib/kaminari/version.rb +1 -1
  28. data/spec/config/config_spec.rb +1 -1
  29. data/spec/fake_app.rb +4 -0
  30. data/spec/fake_gem.rb +6 -0
  31. data/spec/helpers/action_view_extension_spec.rb +105 -10
  32. data/spec/helpers/helpers_spec.rb +1 -1
  33. data/spec/helpers/sinatra_helpers_spec.rb +174 -0
  34. data/spec/helpers/tags_spec.rb +1 -1
  35. data/spec/models/active_record_relation_methods_spec.rb +1 -1
  36. data/spec/models/array_spec.rb +17 -1
  37. data/spec/models/data_mapper_spec.rb +181 -0
  38. data/spec/models/default_per_page_spec.rb +1 -1
  39. data/spec/models/mongo_mapper_spec.rb +14 -11
  40. data/spec/models/mongoid_spec.rb +63 -6
  41. data/spec/models/scopes_spec.rb +154 -140
  42. data/spec/{acceptance → requests}/users_spec.rb +3 -2
  43. data/spec/spec_helper.rb +3 -1
  44. data/spec/spec_helper_for_sinatra.rb +13 -0
  45. data/spec/support/matchers.rb +6 -0
  46. metadata +263 -170
  47. data/spec/acceptance/acceptance_helper.rb +0 -5
  48. data/spec/acceptance/support/helpers.rb +0 -5
  49. data/spec/acceptance/support/paths.rb +0 -9
@@ -1,5 +1,8 @@
1
- require File.join(File.dirname(__FILE__), 'tags')
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
- def each_page
35
- return to_enum(:each_page) unless block_given?
36
-
37
- 1.upto(@options[:num_pages]) do |i|
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 File.join(File.dirname(__FILE__), 'active_record_relation_methods')
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
- def self.inherited(kls) #:nodoc:
8
- super
9
-
10
- kls.class_eval do
11
- include Kaminari::ConfigurationMethods
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
- extend ActiveSupport::Concern
4
- module InstanceMethods
5
- # a workaround for AR 3.0.x that returns 0 for #count when page > 1
6
- # if +limit_value+ is specified, load all the records and count them
7
- if ActiveRecord::VERSION::STRING < '3.1'
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
- def total_count #:nodoc:
14
- # #count overrides the #select which could include generated columns referenced in #order, so skip #order here, where it's irrelevant to the result anyway
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
- # .group returns an OrderdHash that responds to #count
19
- c = c.count
20
- c.respond_to?(:count) ? c.count : c
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
- def initialize(original_array, limit_val = default_per_page, offset_val = 0) #:nodoc:
9
- @_original_array, @_limit_value, @_offset_value = original_array, limit_val, offset_val
10
- super(original_array[offset_val, limit_val] || [])
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
- def page(num = 1)
15
- offset(limit_value * ([num.to_i, 1].max - 1))
16
- end
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, offset_value
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
- arr = self.class.new @_original_array, limit_value, num
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
- def self.paginate_array(array)
40
- PaginatableArray.new array
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