kaminari-rails4 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gemtest +0 -0
  4. data/.gitignore +9 -0
  5. data/.rspec +2 -0
  6. data/.travis.yml +16 -0
  7. data/CHANGELOG +351 -0
  8. data/Gemfile +4 -0
  9. data/MIT-LICENSE +20 -0
  10. data/README.rdoc +300 -0
  11. data/Rakefile +42 -0
  12. data/app/helpers/kaminari_helper.rb +5 -0
  13. data/app/views/kaminari/_first_page.html.erb +11 -0
  14. data/app/views/kaminari/_first_page.html.haml +9 -0
  15. data/app/views/kaminari/_first_page.html.slim +10 -0
  16. data/app/views/kaminari/_gap.html.erb +8 -0
  17. data/app/views/kaminari/_gap.html.haml +8 -0
  18. data/app/views/kaminari/_gap.html.slim +9 -0
  19. data/app/views/kaminari/_last_page.html.erb +11 -0
  20. data/app/views/kaminari/_last_page.html.haml +9 -0
  21. data/app/views/kaminari/_last_page.html.slim +10 -0
  22. data/app/views/kaminari/_next_page.html.erb +11 -0
  23. data/app/views/kaminari/_next_page.html.haml +9 -0
  24. data/app/views/kaminari/_next_page.html.slim +10 -0
  25. data/app/views/kaminari/_page.html.erb +12 -0
  26. data/app/views/kaminari/_page.html.haml +10 -0
  27. data/app/views/kaminari/_page.html.slim +11 -0
  28. data/app/views/kaminari/_paginator.html.erb +23 -0
  29. data/app/views/kaminari/_paginator.html.haml +18 -0
  30. data/app/views/kaminari/_paginator.html.slim +19 -0
  31. data/app/views/kaminari/_prev_page.html.erb +11 -0
  32. data/app/views/kaminari/_prev_page.html.haml +9 -0
  33. data/app/views/kaminari/_prev_page.html.slim +10 -0
  34. data/config/locales/kaminari.yml +19 -0
  35. data/gemfiles/active_record_30.gemfile +7 -0
  36. data/gemfiles/active_record_31.gemfile +7 -0
  37. data/gemfiles/active_record_32.gemfile +10 -0
  38. data/gemfiles/active_record_40.gemfile +8 -0
  39. data/gemfiles/data_mapper_12.gemfile +15 -0
  40. data/gemfiles/mongo_mapper.gemfile +10 -0
  41. data/gemfiles/mongoid_24.gemfile +7 -0
  42. data/gemfiles/mongoid_30.gemfile +12 -0
  43. data/gemfiles/sinatra.gemfile +13 -0
  44. data/kaminari.gemspec +36 -0
  45. data/lib/generators/kaminari/config_generator.rb +16 -0
  46. data/lib/generators/kaminari/templates/kaminari_config.rb +10 -0
  47. data/lib/generators/kaminari/views_generator.rb +118 -0
  48. data/lib/kaminari.rb +38 -0
  49. data/lib/kaminari/config.rb +51 -0
  50. data/lib/kaminari/engine.rb +4 -0
  51. data/lib/kaminari/grape.rb +4 -0
  52. data/lib/kaminari/helpers/action_view_extension.rb +151 -0
  53. data/lib/kaminari/helpers/paginator.rb +186 -0
  54. data/lib/kaminari/helpers/sinatra_helpers.rb +144 -0
  55. data/lib/kaminari/helpers/tags.rb +95 -0
  56. data/lib/kaminari/hooks.rb +33 -0
  57. data/lib/kaminari/models/active_record_extension.rb +22 -0
  58. data/lib/kaminari/models/active_record_model_extension.rb +20 -0
  59. data/lib/kaminari/models/active_record_relation_methods.rb +29 -0
  60. data/lib/kaminari/models/array_extension.rb +58 -0
  61. data/lib/kaminari/models/configuration_methods.rb +48 -0
  62. data/lib/kaminari/models/data_mapper_collection_methods.rb +15 -0
  63. data/lib/kaminari/models/data_mapper_extension.rb +48 -0
  64. data/lib/kaminari/models/mongo_mapper_extension.rb +18 -0
  65. data/lib/kaminari/models/mongoid_criteria_methods.rb +23 -0
  66. data/lib/kaminari/models/mongoid_extension.rb +33 -0
  67. data/lib/kaminari/models/page_scope_methods.rb +70 -0
  68. data/lib/kaminari/models/plucky_criteria_methods.rb +18 -0
  69. data/lib/kaminari/railtie.rb +7 -0
  70. data/lib/kaminari/sinatra.rb +5 -0
  71. data/lib/kaminari/version.rb +3 -0
  72. data/spec/config/config_spec.rb +91 -0
  73. data/spec/fake_app/active_record/config.rb +3 -0
  74. data/spec/fake_app/active_record/models.rb +57 -0
  75. data/spec/fake_app/data_mapper/config.rb +1 -0
  76. data/spec/fake_app/data_mapper/models.rb +27 -0
  77. data/spec/fake_app/mongo_mapper/config.rb +2 -0
  78. data/spec/fake_app/mongo_mapper/models.rb +9 -0
  79. data/spec/fake_app/mongoid/config.rb +16 -0
  80. data/spec/fake_app/mongoid/models.rb +22 -0
  81. data/spec/fake_app/rails_app.rb +56 -0
  82. data/spec/fake_app/sinatra_app.rb +22 -0
  83. data/spec/fake_gem.rb +4 -0
  84. data/spec/helpers/action_view_extension_spec.rb +262 -0
  85. data/spec/helpers/helpers_spec.rb +135 -0
  86. data/spec/helpers/sinatra_helpers_spec.rb +170 -0
  87. data/spec/helpers/tags_spec.rb +140 -0
  88. data/spec/models/active_record/active_record_relation_methods_spec.rb +47 -0
  89. data/spec/models/active_record/default_per_page_spec.rb +32 -0
  90. data/spec/models/active_record/max_pages_spec.rb +23 -0
  91. data/spec/models/active_record/max_per_page_spec.rb +32 -0
  92. data/spec/models/active_record/scopes_spec.rb +242 -0
  93. data/spec/models/array_spec.rb +150 -0
  94. data/spec/models/data_mapper/data_mapper_spec.rb +179 -0
  95. data/spec/models/mongo_mapper/mongo_mapper_spec.rb +84 -0
  96. data/spec/models/mongoid/mongoid_spec.rb +126 -0
  97. data/spec/requests/users_spec.rb +53 -0
  98. data/spec/spec_helper.rb +33 -0
  99. data/spec/spec_helper_for_sinatra.rb +33 -0
  100. data/spec/support/database_cleaner.rb +16 -0
  101. data/spec/support/matchers.rb +52 -0
  102. metadata +300 -0
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "kaminari/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'kaminari-rails4'
7
+ s.version = Kaminari::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Akira Matsuda']
10
+ s.email = ['ronnie@dio.jp']
11
+ s.homepage = 'https://github.com/amatsuda/kaminari'
12
+ s.summary = 'A pagination engine plugin for Rails 3+ or other modern frameworks'
13
+ s.description = 'Kaminari is a Scope & Engine based, clean, powerful, agnostic, customizable and sophisticated paginator for Rails 3+'
14
+
15
+ s.rubyforge_project = 'kaminari-rails4'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.extra_rdoc_files = ['README.rdoc']
20
+ s.require_paths = ['lib']
21
+
22
+ s.licenses = ['MIT']
23
+
24
+ s.add_dependency 'activesupport', ['>= 3.0.0']
25
+ s.add_dependency 'actionpack', ['>= 3.0.0']
26
+
27
+ s.add_development_dependency 'bundler', ['>= 1.0.0']
28
+ s.add_development_dependency 'rake', ['>= 0']
29
+ s.add_development_dependency 'sqlite3', ['>= 0']
30
+ s.add_development_dependency 'tzinfo', ['>= 0']
31
+ s.add_development_dependency 'rspec', ['>= 0']
32
+ s.add_development_dependency 'rr', ['>= 0']
33
+ s.add_development_dependency 'capybara', ['>= 1.0']
34
+ s.add_development_dependency 'database_cleaner', ['>= 0']
35
+ s.add_development_dependency 'rdoc', ['>= 0']
36
+ end
@@ -0,0 +1,16 @@
1
+ module Kaminari
2
+ module Generators
3
+ class ConfigGenerator < Rails::Generators::Base
4
+ source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
5
+
6
+ desc <<DESC
7
+ Description:
8
+ Copies Kaminari configuration file to your application's initializer directory.
9
+ DESC
10
+
11
+ def copy_config_file
12
+ template 'kaminari_config.rb', 'config/initializers/kaminari_config.rb'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ Kaminari.configure do |config|
2
+ # config.default_per_page = 25
3
+ # config.max_per_page = nil
4
+ # config.window = 4
5
+ # config.outer_window = 0
6
+ # config.left = 0
7
+ # config.right = 0
8
+ # config.page_method_name = :page
9
+ # config.param_name = :page
10
+ end
@@ -0,0 +1,118 @@
1
+ module Kaminari
2
+ module Generators
3
+
4
+ class ViewsGenerator < Rails::Generators::NamedBase
5
+ source_root File.expand_path('../../../../app/views/kaminari', __FILE__)
6
+
7
+ class_option :template_engine, :type => :string, :aliases => '-e', :desc => 'Template engine for the views. Available options are "erb", "haml", and "slim".'
8
+
9
+ def self.banner #:nodoc:
10
+ <<-BANNER.chomp
11
+ rails g kaminari:views THEME [options]
12
+
13
+ Copies all paginator partial templates to your application.
14
+ You can choose a template THEME by specifying one from the list below:
15
+
16
+ - default
17
+ The default one.
18
+ This one is used internally while you don't override the partials.
19
+ #{themes.map {|t| " - #{t.name}\n#{t.description}"}.join("\n")}
20
+ BANNER
21
+ end
22
+
23
+ desc ''
24
+ def copy_or_fetch #:nodoc:
25
+ return copy_default_views if file_name == 'default'
26
+
27
+ themes = self.class.themes
28
+ if theme = themes.detect {|t| t.name == file_name}
29
+ download_templates theme
30
+ else
31
+ say %Q[no such theme: #{file_name}\n avaliable themes: #{themes.map(&:name).join ", "}]
32
+ end
33
+ end
34
+
35
+ private
36
+ def self.themes
37
+ begin
38
+ @themes ||= GitHubApiHelper.get_files_in_master.group_by {|fn, _| fn[0...(fn.index('/') || 0)]}.delete_if {|fn, _| fn.blank?}.map do |name, files|
39
+ Theme.new name, files
40
+ end
41
+ rescue SocketError
42
+ []
43
+ end
44
+ end
45
+
46
+ def download_templates(theme)
47
+ theme.templates_for(template_engine).each do |template|
48
+ say " downloading #{template.name} from kaminari_themes..."
49
+ create_file template.name, GitHubApiHelper.get_content_for("#{theme.name}/#{template.name}")
50
+ end
51
+ end
52
+
53
+ def copy_default_views
54
+ filename_pattern = File.join self.class.source_root, "*.html.#{template_engine}"
55
+ Dir.glob(filename_pattern).map {|f| File.basename f}.each do |f|
56
+ copy_file f, "app/views/kaminari/#{f}"
57
+ end
58
+ end
59
+
60
+ def template_engine
61
+ options[:template_engine].try(:to_s).try(:downcase) || 'erb'
62
+ end
63
+ end
64
+
65
+ Template = Struct.new(:name, :sha) do
66
+ def description?
67
+ name == 'DESCRIPTION'
68
+ end
69
+
70
+ def view?
71
+ name =~ /^app\/views\//
72
+ end
73
+
74
+ def engine #:nodoc:
75
+ File.extname(name).sub /^\./, ''
76
+ end
77
+ end
78
+
79
+ class Theme
80
+ attr_accessor :name
81
+ def initialize(name, templates) #:nodoc:
82
+ @name, @templates = name, templates.map {|fn, sha| Template.new fn.sub(/^#{name}\//, ''), sha}
83
+ end
84
+
85
+ def description #:nodoc:
86
+ file = @templates.detect(&:description?)
87
+ return "#{' ' * 12}#{name}" unless file
88
+ GitHubApiHelper.get_content_for("#{@name}/#{file.name}").chomp.gsub(/^/, ' ' * 12)
89
+ end
90
+
91
+ def templates_for(template_engine) #:nodoc:
92
+ @templates.select {|t| !t.description?}.select {|t| !t.view? || (t.engine == template_engine)}
93
+ end
94
+ end
95
+
96
+ module GitHubApiHelper
97
+ def get_files_in_master
98
+ master_tree_sha = open('https://api.github.com/repos/amatsuda/kaminari_themes/git/refs/heads/master') do |json|
99
+ ActiveSupport::JSON.decode(json)['object']['sha']
100
+ end
101
+ open('https://api.github.com/repos/amatsuda/kaminari_themes/git/trees/' + master_tree_sha + '?recursive=1') do |json|
102
+ blobs = ActiveSupport::JSON.decode(json)['tree'].find_all {|i| i['type'] == 'blob' }
103
+ blobs.map do |blob|
104
+ [blob['path'], blob['sha']]
105
+ end
106
+ end
107
+ end
108
+ module_function :get_files_in_master
109
+
110
+ def get_content_for(path)
111
+ open('https://api.github.com/repos/amatsuda/kaminari_themes/contents/' + path) do |json|
112
+ Base64.decode64(ActiveSupport::JSON.decode(json)['content'])
113
+ end
114
+ end
115
+ module_function :get_content_for
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,38 @@
1
+ module Kaminari
2
+ end
3
+
4
+ # load Rails/Railtie
5
+ begin
6
+ require 'rails'
7
+ rescue LoadError
8
+ #do nothing
9
+ end
10
+
11
+ $stderr.puts <<-EOC if !defined?(Rails) && !defined?(Sinatra) && !defined?(Grape)
12
+ warning: no framework detected.
13
+
14
+ Your Gemfile might not be configured properly.
15
+ ---- e.g. ----
16
+ Rails:
17
+ gem 'kaminari'
18
+
19
+ Sinatra/Padrino:
20
+ gem 'kaminari', :require => 'kaminari/sinatra'
21
+
22
+ Grape:
23
+ gem 'kaminari', :require => 'kaminari/grape'
24
+
25
+ EOC
26
+
27
+ # load Kaminari components
28
+ require 'kaminari/config'
29
+ require 'kaminari/helpers/paginator'
30
+ require 'kaminari/models/page_scope_methods'
31
+ require 'kaminari/models/configuration_methods'
32
+ require 'kaminari/hooks'
33
+
34
+ # if not using Railtie, call `Kaminari::Hooks.init` directly
35
+ if defined? Rails
36
+ require 'kaminari/railtie'
37
+ require 'kaminari/engine'
38
+ end
@@ -0,0 +1,51 @@
1
+ require 'active_support/configurable'
2
+
3
+ module Kaminari
4
+ # Configures global settings for Kaminari
5
+ # Kaminari.configure do |config|
6
+ # config.default_per_page = 10
7
+ # end
8
+ def self.configure(&block)
9
+ yield @config ||= Kaminari::Configuration.new
10
+ end
11
+
12
+ # Global settings for Kaminari
13
+ def self.config
14
+ @config
15
+ end
16
+
17
+ # need a Class for 3.0
18
+ class Configuration #:nodoc:
19
+ include ActiveSupport::Configurable
20
+ config_accessor :default_per_page
21
+ config_accessor :max_per_page
22
+ config_accessor :window
23
+ config_accessor :outer_window
24
+ config_accessor :left
25
+ config_accessor :right
26
+ config_accessor :page_method_name
27
+ config_accessor :max_pages
28
+
29
+ def param_name
30
+ config.param_name.respond_to?(:call) ? config.param_name.call : config.param_name
31
+ end
32
+
33
+ # define param_name writer (copied from AS::Configurable)
34
+ writer, line = 'def param_name=(value); config.param_name = value; end', __LINE__
35
+ singleton_class.class_eval writer, __FILE__, line
36
+ class_eval writer, __FILE__, line
37
+ end
38
+
39
+ # this is ugly. why can't we pass the default value to config_accessor...?
40
+ configure do |config|
41
+ config.default_per_page = 25
42
+ config.max_per_page = nil
43
+ config.window = 4
44
+ config.outer_window = 0
45
+ config.left = 0
46
+ config.right = 0
47
+ config.page_method_name = :page
48
+ config.param_name = :page
49
+ config.max_pages = nil
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ module Kaminari #:nodoc:
2
+ class Engine < ::Rails::Engine #:nodoc:
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'grape'
2
+ require 'kaminari'
3
+
4
+ Kaminari::Hooks.init
@@ -0,0 +1,151 @@
1
+ module Kaminari
2
+ # = Helpers
3
+ module ActionViewExtension
4
+ # A helper that renders the pagination links.
5
+ #
6
+ # <%= paginate @articles %>
7
+ #
8
+ # ==== Options
9
+ # * <tt>:window</tt> - The "inner window" size (4 by default).
10
+ # * <tt>:outer_window</tt> - The "outer window" size (0 by default).
11
+ # * <tt>:left</tt> - The "left outer window" size (0 by default).
12
+ # * <tt>:right</tt> - The "right outer window" size (0 by default).
13
+ # * <tt>:params</tt> - url_for parameters for the links (:controller, :action, etc.)
14
+ # * <tt>:param_name</tt> - parameter name for page number in the links (:page by default)
15
+ # * <tt>:remote</tt> - Ajax? (false by default)
16
+ # * <tt>:ANY_OTHER_VALUES</tt> - Any other hash key & values would be directly passed into each tag as :locals value.
17
+ def paginate(scope, options = {}, &block)
18
+ paginator = Kaminari::Helpers::Paginator.new self, options.reverse_merge(:current_page => scope.current_page, :total_pages => scope.total_pages, :per_page => scope.limit_value, :param_name => Kaminari.config.param_name, :remote => false)
19
+ paginator.to_s
20
+ end
21
+
22
+ # A simple "Twitter like" pagination link that creates a link to the previous page.
23
+ #
24
+ # ==== Examples
25
+ # Basic usage:
26
+ #
27
+ # <%= link_to_previous_page @items, 'Previous Page' %>
28
+ #
29
+ # Ajax:
30
+ #
31
+ # <%= link_to_previous_page @items, 'Previous Page', :remote => true %>
32
+ #
33
+ # By default, it renders nothing if there are no more results on the previous page.
34
+ # You can customize this output by passing a block.
35
+ #
36
+ # <%= link_to_previous_page @users, 'Previous Page' do %>
37
+ # <span>At the Beginning</span>
38
+ # <% end %>
39
+ def link_to_previous_page(scope, name, options = {}, &block)
40
+ params = options.delete(:params) || {}
41
+ param_name = options.delete(:param_name) || Kaminari.config.param_name
42
+ link_to_unless scope.first_page?, name, params.merge(param_name => (scope.current_page - 1)), options.reverse_merge(:rel => 'previous') do
43
+ block.call if block
44
+ end
45
+ end
46
+
47
+ # A simple "Twitter like" pagination link that creates a link to the next page.
48
+ #
49
+ # ==== Examples
50
+ # Basic usage:
51
+ #
52
+ # <%= link_to_next_page @items, 'Next Page' %>
53
+ #
54
+ # Ajax:
55
+ #
56
+ # <%= link_to_next_page @items, 'Next Page', :remote => true %>
57
+ #
58
+ # By default, it renders nothing if there are no more results on the next page.
59
+ # You can customize this output by passing a block.
60
+ #
61
+ # <%= link_to_next_page @users, 'Next Page' do %>
62
+ # <span>No More Pages</span>
63
+ # <% end %>
64
+ def link_to_next_page(scope, name, options = {}, &block)
65
+ params = options.delete(:params) || {}
66
+ param_name = options.delete(:param_name) || Kaminari.config.param_name
67
+ link_to_unless scope.last_page?, name, params.merge(param_name => (scope.current_page + 1)), options.reverse_merge(:rel => 'next') do
68
+ block.call if block
69
+ end
70
+ end
71
+
72
+ # Renders a helpful message with numbers of displayed vs. total entries.
73
+ # Ported from mislav/will_paginate
74
+ #
75
+ # ==== Examples
76
+ # Basic usage:
77
+ #
78
+ # <%= page_entries_info @posts %>
79
+ # #-> Displaying posts 6 - 10 of 26 in total
80
+ #
81
+ # By default, the message will use the humanized class name of objects
82
+ # in collection: for instance, "project types" for ProjectType models.
83
+ # The namespace will be cutted out and only the last name will be used.
84
+ # Override this with the <tt>:entry_name</tt> parameter:
85
+ #
86
+ # <%= page_entries_info @posts, :entry_name => 'item' %>
87
+ # #-> Displaying items 6 - 10 of 26 in total
88
+ def page_entries_info(collection, options = {})
89
+ entry_name = if options[:entry_name]
90
+ options[:entry_name]
91
+ elsif collection.empty? || collection.is_a?(::Kaminari::PaginatableArray)
92
+ 'entry'
93
+ else
94
+ if collection.respond_to? :model # DataMapper
95
+ collection.model.model_name.human.downcase
96
+ else # AR
97
+ collection.model_name.human.downcase
98
+ end
99
+ end
100
+ entry_name = entry_name.pluralize unless collection.total_count == 1
101
+
102
+ if collection.total_pages < 2
103
+ t('helpers.page_entries_info.one_page.display_entries', :entry_name => entry_name, :count => collection.total_count)
104
+ else
105
+ first = collection.offset_value + 1
106
+ last = collection.last_page? ? collection.total_count : collection.offset_value + collection.limit_value
107
+ t('helpers.page_entries_info.more_pages.display_entries', :entry_name => entry_name, :first => first, :last => last, :total => collection.total_count)
108
+ end.html_safe
109
+ end
110
+
111
+ # Renders rel="next" and rel="prev" links to be used in the head.
112
+ #
113
+ # ==== Examples
114
+ # Basic usage:
115
+ #
116
+ # In head:
117
+ # <head>
118
+ # <title>My Website</title>
119
+ # <%= yield :head %>
120
+ # </head>
121
+ #
122
+ # Somewhere in body:
123
+ # <% content_for :head do %>
124
+ # <%= rel_next_prev_link_tags @items %>
125
+ # <% end %>
126
+ #
127
+ # #-> <link rel="next" href="/items/page/3" /><link rel="prev" href="/items/page/1" />
128
+ #
129
+ def rel_next_prev_link_tags(scope, options = {})
130
+ params = options.delete(:params) || {}
131
+ param_name = options.delete(:param_name) || Kaminari.config.param_name
132
+
133
+ output = ""
134
+
135
+ if !scope.first_page? && !scope.last_page?
136
+ # If not first and not last, then output both links.
137
+ output << '<link rel="next" href="' + url_for(params.merge(param_name => (scope.current_page + 1))) + '"/>'
138
+ output << '<link rel="prev" href="' + url_for(params.merge(param_name => (scope.current_page - 1))) + '"/>'
139
+ elsif scope.first_page?
140
+ # If first page, add next link unless last page.
141
+ output << '<link rel="next" href="' + url_for(params.merge(param_name => (scope.current_page + 1))) + '"/>' unless scope.last_page?
142
+ else
143
+ # If last page, add prev link unless first page.
144
+ output << '<link rel="prev" href="' + url_for(params.merge(param_name => (scope.current_page - 1))) + '"/>' unless scope.first_page?
145
+ end
146
+
147
+ output.html_safe
148
+ end
149
+
150
+ end
151
+ end
@@ -0,0 +1,186 @@
1
+ require 'active_support/inflector'
2
+ require 'action_view'
3
+ require 'action_view/log_subscriber'
4
+ require 'action_view/context'
5
+ require 'kaminari/helpers/tags'
6
+
7
+ module Kaminari
8
+ module Helpers
9
+ # The main container tag
10
+ class Paginator < Tag
11
+ # so that this instance can actually "render"
12
+ include ::ActionView::Context
13
+
14
+ def initialize(template, options) #:nodoc:
15
+ @window_options = {}.tap do |h|
16
+ h[:window] = options.delete(:window) || options.delete(:inner_window) || Kaminari.config.window
17
+ outer_window = options.delete(:outer_window) || Kaminari.config.outer_window
18
+ h[:left] = options.delete(:left) || Kaminari.config.left
19
+ h[:left] = outer_window if h[:left] == 0
20
+ h[:right] = options.delete(:right) || Kaminari.config.right
21
+ h[:right] = outer_window if h[:right] == 0
22
+ end
23
+ @template, @options = template, options
24
+ @theme = @options[:theme] ? "#{@options[:theme]}/" : ''
25
+ @options[:current_page] = PageProxy.new @window_options.merge(@options), @options[:current_page], nil
26
+ #FIXME for compatibility. remove num_pages at some time in the future
27
+ @options[:total_pages] ||= @options[:num_pages]
28
+ @options[:num_pages] ||= @options[:total_pages]
29
+ @last = nil
30
+ # initialize the output_buffer for Context
31
+ @output_buffer = ActionView::OutputBuffer.new
32
+ end
33
+
34
+ # render given block as a view template
35
+ def render(&block)
36
+ instance_eval(&block) if @options[:total_pages] > 1
37
+ @output_buffer
38
+ end
39
+
40
+ # enumerate each page providing PageProxy object as the block parameter
41
+ # Because of performance reason, this doesn't actually enumerate all pages but pages that are seemingly relevant to the paginator.
42
+ # "Relevant" pages are:
43
+ # * pages inside the left outer window plus one for showing the gap tag
44
+ # * pages inside the inner window plus one on the left plus one on the right for showing the gap tags
45
+ # * pages inside the right outer window plus one for showing the gap tag
46
+ def each_relevant_page
47
+ return to_enum(:each_relevant_page) unless block_given?
48
+
49
+ relevant_pages(@window_options.merge(@options)).each do |i|
50
+ yield PageProxy.new(@window_options.merge(@options), i, @last)
51
+ end
52
+ end
53
+ alias each_page each_relevant_page
54
+
55
+ def relevant_pages(options)
56
+ left_window_plus_one = 1.upto(options[:left] + 1).to_a
57
+ right_window_plus_one = (options[:total_pages] - options[:right]).upto(options[:total_pages]).to_a
58
+ inside_window_plus_each_sides = (options[:current_page] - options[:window] - 1).upto(options[:current_page] + options[:window] + 1).to_a
59
+
60
+ (left_window_plus_one + inside_window_plus_each_sides + right_window_plus_one).uniq.sort.reject {|x| (x < 1) || (x > options[:total_pages])}
61
+ end
62
+ private :relevant_pages
63
+
64
+ def page_tag(page)
65
+ @last = Page.new @template, @options.merge(:page => page)
66
+ end
67
+
68
+ %w[first_page prev_page next_page last_page gap].each do |tag|
69
+ eval <<-DEF
70
+ def #{tag}_tag
71
+ @last = #{tag.classify}.new @template, @options
72
+ end
73
+ DEF
74
+ end
75
+
76
+ def to_s #:nodoc:
77
+ subscriber = ActionView::LogSubscriber.log_subscribers.detect {|ls| ls.is_a? ActionView::LogSubscriber}
78
+
79
+ # There is a logging subscriber
80
+ # and we don't want it to log render_partial
81
+ # It is threadsafe, but might not repress logging
82
+ # consistently in a high-load environment
83
+ if subscriber
84
+ unless defined? subscriber.render_partial_with_logging
85
+ class << subscriber
86
+ alias_method :render_partial_with_logging, :render_partial
87
+ attr_accessor :render_without_logging
88
+ # ugly hack to make a renderer where
89
+ # we can turn logging on or off
90
+ def render_partial(event)
91
+ render_partial_with_logging(event) unless render_without_logging
92
+ end
93
+ end
94
+ end
95
+
96
+ subscriber.render_without_logging = true
97
+ ret = super @window_options.merge(@options).merge :paginator => self
98
+ subscriber.render_without_logging = false
99
+
100
+ ret
101
+ else
102
+ super @window_options.merge(@options).merge :paginator => self
103
+ end
104
+ end
105
+
106
+ # Wraps a "page number" and provides some utility methods
107
+ class PageProxy
108
+ include Comparable
109
+
110
+ def initialize(options, page, last) #:nodoc:
111
+ @options, @page, @last = options, page, last
112
+ end
113
+
114
+ # the page number
115
+ def number
116
+ @page
117
+ end
118
+
119
+ # current page or not
120
+ def current?
121
+ @page == @options[:current_page]
122
+ end
123
+
124
+ # the first page or not
125
+ def first?
126
+ @page == 1
127
+ end
128
+
129
+ # the last page or not
130
+ def last?
131
+ @page == @options[:total_pages]
132
+ end
133
+
134
+ # the previous page or not
135
+ def prev?
136
+ @page == @options[:current_page] - 1
137
+ end
138
+
139
+ # the next page or not
140
+ def next?
141
+ @page == @options[:current_page] + 1
142
+ end
143
+
144
+ # within the left outer window or not
145
+ def left_outer?
146
+ @page <= @options[:left]
147
+ end
148
+
149
+ # within the right outer window or not
150
+ def right_outer?
151
+ @options[:total_pages] - @page < @options[:right]
152
+ end
153
+
154
+ # inside the inner window or not
155
+ def inside_window?
156
+ (@options[:current_page] - @page).abs <= @options[:window]
157
+ end
158
+
159
+ # The last rendered tag was "truncated" or not
160
+ def was_truncated?
161
+ @last.is_a? Gap
162
+ end
163
+
164
+ def to_i
165
+ number
166
+ end
167
+
168
+ def to_s
169
+ number.to_s
170
+ end
171
+
172
+ def +(other)
173
+ to_i + other.to_i
174
+ end
175
+
176
+ def -(other)
177
+ to_i - other.to_i
178
+ end
179
+
180
+ def <=>(other)
181
+ to_i <=> other.to_i
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end