agnostic-will_paginate 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/.autotest +54 -0
  2. data/.gitignore +4 -0
  3. data/.gitmodules +3 -0
  4. data/.manifest +61 -0
  5. data/CHANGELOG.rdoc +105 -0
  6. data/LICENSE +18 -0
  7. data/README.rdoc +125 -0
  8. data/Rakefile +58 -0
  9. data/init.rb +1 -0
  10. data/lib/will_paginate.rb +45 -0
  11. data/lib/will_paginate/array.rb +33 -0
  12. data/lib/will_paginate/collection.rb +145 -0
  13. data/lib/will_paginate/core_ext.rb +69 -0
  14. data/lib/will_paginate/deprecation.rb +50 -0
  15. data/lib/will_paginate/finders.rb +9 -0
  16. data/lib/will_paginate/finders/active_record.rb +192 -0
  17. data/lib/will_paginate/finders/active_record/named_scope.rb +170 -0
  18. data/lib/will_paginate/finders/active_record/named_scope_patch.rb +39 -0
  19. data/lib/will_paginate/finders/active_resource.rb +51 -0
  20. data/lib/will_paginate/finders/base.rb +112 -0
  21. data/lib/will_paginate/finders/data_mapper.rb +30 -0
  22. data/lib/will_paginate/finders/sequel.rb +22 -0
  23. data/lib/will_paginate/version.rb +9 -0
  24. data/lib/will_paginate/view_helpers.rb +42 -0
  25. data/lib/will_paginate/view_helpers/action_view.rb +158 -0
  26. data/lib/will_paginate/view_helpers/base.rb +126 -0
  27. data/lib/will_paginate/view_helpers/link_renderer.rb +130 -0
  28. data/lib/will_paginate/view_helpers/link_renderer_base.rb +83 -0
  29. data/lib/will_paginate/view_helpers/merb.rb +13 -0
  30. data/spec/collection_spec.rb +147 -0
  31. data/spec/console +8 -0
  32. data/spec/console_fixtures.rb +8 -0
  33. data/spec/database.yml +22 -0
  34. data/spec/finders/active_record_spec.rb +461 -0
  35. data/spec/finders/active_resource_spec.rb +52 -0
  36. data/spec/finders/activerecord_test_connector.rb +108 -0
  37. data/spec/finders/data_mapper_spec.rb +62 -0
  38. data/spec/finders/data_mapper_test_connector.rb +20 -0
  39. data/spec/finders/sequel_spec.rb +53 -0
  40. data/spec/finders/sequel_test_connector.rb +9 -0
  41. data/spec/finders_spec.rb +76 -0
  42. data/spec/fixtures/admin.rb +3 -0
  43. data/spec/fixtures/developer.rb +13 -0
  44. data/spec/fixtures/developers_projects.yml +13 -0
  45. data/spec/fixtures/project.rb +15 -0
  46. data/spec/fixtures/projects.yml +6 -0
  47. data/spec/fixtures/replies.yml +29 -0
  48. data/spec/fixtures/reply.rb +7 -0
  49. data/spec/fixtures/schema.rb +38 -0
  50. data/spec/fixtures/topic.rb +6 -0
  51. data/spec/fixtures/topics.yml +30 -0
  52. data/spec/fixtures/user.rb +2 -0
  53. data/spec/fixtures/users.yml +35 -0
  54. data/spec/rcov.opts +2 -0
  55. data/spec/spec.opts +2 -0
  56. data/spec/spec_helper.rb +75 -0
  57. data/spec/tasks.rake +60 -0
  58. data/spec/view_helpers/action_view_spec.rb +344 -0
  59. data/spec/view_helpers/base_spec.rb +64 -0
  60. data/spec/view_helpers/link_renderer_base_spec.rb +84 -0
  61. data/spec/view_helpers/view_example_group.rb +111 -0
  62. metadata +152 -0
@@ -0,0 +1,158 @@
1
+ require 'will_paginate/view_helpers/base'
2
+ require 'action_view'
3
+ require 'action_pack/version'
4
+ require 'will_paginate/view_helpers/link_renderer'
5
+
6
+ module WillPaginate
7
+ module ViewHelpers
8
+ # = ActionView helpers
9
+ #
10
+ # This module serves for availability in ActionView templates. It also adds a new
11
+ # view helper: +paginated_section+.
12
+ #
13
+ # == Using the helper without arguments
14
+ # If the helper is called without passing in the collection object, it will
15
+ # try to read from the instance variable inferred by the controller name.
16
+ # For example, calling +will_paginate+ while the current controller is
17
+ # PostsController will result in trying to read from the <tt>@posts</tt>
18
+ # variable. Example:
19
+ #
20
+ # <%= will_paginate :id => true %>
21
+ #
22
+ # ... will result in <tt>@post</tt> collection getting paginated:
23
+ #
24
+ # <div class="pagination" id="posts_pagination"> ... </div>
25
+ #
26
+ module ActionView
27
+ include WillPaginate::ViewHelpers::Base
28
+
29
+ def will_paginate(collection = nil, options = {}) #:nodoc:
30
+ options, collection = collection, nil if collection.is_a? Hash
31
+ collection ||= infer_collection_from_controller
32
+
33
+ super(collection, options.symbolize_keys)
34
+ end
35
+
36
+ def page_entries_info(collection = nil, options = {}) #:nodoc:
37
+ options, collection = collection, nil if collection.is_a? Hash
38
+ collection ||= infer_collection_from_controller
39
+
40
+ super(collection, options.symbolize_keys)
41
+ end
42
+
43
+ # Wrapper for rendering pagination links at both top and bottom of a block
44
+ # of content.
45
+ #
46
+ # <% paginated_section @posts do %>
47
+ # <ol id="posts">
48
+ # <% for post in @posts %>
49
+ # <li> ... </li>
50
+ # <% end %>
51
+ # </ol>
52
+ # <% end %>
53
+ #
54
+ # will result in:
55
+ #
56
+ # <div class="pagination"> ... </div>
57
+ # <ol id="posts">
58
+ # ...
59
+ # </ol>
60
+ # <div class="pagination"> ... </div>
61
+ #
62
+ # Arguments are passed to a <tt>will_paginate</tt> call, so the same options
63
+ # apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two
64
+ # blocks of pagination links sharing the same ID (which is invalid HTML).
65
+ def paginated_section(*args, &block)
66
+ pagination = will_paginate(*args).to_s
67
+
68
+ unless ::ActionView::Base.respond_to? :erb_variable
69
+ concat pagination
70
+ yield
71
+ concat pagination
72
+ else
73
+ content = pagination + capture(&block) + pagination
74
+ concat(content, block.binding)
75
+ end
76
+ end
77
+
78
+ protected
79
+
80
+ def infer_collection_from_controller
81
+ collection_name = "@#{controller.controller_name}"
82
+ collection = instance_variable_get(collection_name)
83
+ raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " +
84
+ "forget to pass the collection object for will_paginate?" if collection.nil?
85
+ collection
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ ActionView::Base.send :include, WillPaginate::ViewHelpers::ActionView
92
+ # :stopdoc:
93
+ if defined?(ActionController::Base) and ActionController::Base.respond_to? :rescue_responses
94
+ ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found
95
+ end
96
+
97
+ WillPaginate::ViewHelpers::LinkRenderer.class_eval do
98
+ protected
99
+
100
+ def default_url_params
101
+ { :escape => false }
102
+ end
103
+
104
+ def url(page)
105
+ @base_url_params ||= begin
106
+ url_params = base_url_params
107
+ merge_optional_params(url_params)
108
+ url_params
109
+ end
110
+
111
+ url_params = @base_url_params.dup
112
+ add_current_page_param(url_params, page)
113
+
114
+ @template.url_for(url_params)
115
+ end
116
+
117
+ def base_url_params
118
+ url_params = default_url_params
119
+ # page links should preserve GET parameters
120
+ symbolized_update(url_params, @template.params) if get_request?
121
+ url_params
122
+ end
123
+
124
+ def merge_optional_params(url_params)
125
+ symbolized_update(url_params, @options[:params]) if @options[:params]
126
+ end
127
+
128
+ def add_current_page_param(url_params, page)
129
+ unless param_name.index(/[^\w-]/)
130
+ url_params[param_name.to_sym] = page
131
+ else
132
+ page_param = parse_query_parameters("#{param_name}=#{page}")
133
+ symbolized_update(url_params, page_param)
134
+ end
135
+ end
136
+
137
+ def get_request?
138
+ @template.request.get?
139
+ end
140
+
141
+ private
142
+
143
+ def parse_query_parameters(params)
144
+ if defined? Rack::Utils
145
+ # For Rails > 2.3
146
+ Rack::Utils.parse_nested_query(params)
147
+ elsif defined?(ActionController::AbstractRequest)
148
+ ActionController::AbstractRequest.parse_query_parameters(params)
149
+ elsif defined?(ActionController::UrlEncodedPairParser)
150
+ # For Rails > 2.2
151
+ ActionController::UrlEncodedPairParser.parse_query_parameters(params)
152
+ elsif defined?(CGIMethods)
153
+ CGIMethods.parse_query_parameters(params)
154
+ else
155
+ raise "unsupported ActionPack version"
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,126 @@
1
+ require 'will_paginate/core_ext'
2
+ require 'will_paginate/view_helpers'
3
+
4
+ module WillPaginate
5
+ module ViewHelpers
6
+ # = The main view helpers module
7
+ #
8
+ # This is the base module which provides the +will_paginate+ view helper.
9
+ module Base
10
+ # Renders Digg/Flickr-style pagination for a WillPaginate::Collection object. Nil is
11
+ # returned if there is only one page in total; pagination links aren't needed in that case.
12
+ #
13
+ # ==== Options
14
+ # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination")
15
+ # * <tt>:previous_label</tt> -- default: "« Previous"
16
+ # * <tt>:next_label</tt> -- default: "Next »"
17
+ # * <tt>:inner_window</tt> -- how many links are shown around the current page (default: 4)
18
+ # * <tt>:outer_window</tt> -- how many links are around the first and the last page (default: 1)
19
+ # * <tt>:separator</tt> -- string separator for page HTML elements (default: single space)
20
+ # * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
21
+ # * <tt>:params</tt> -- additional parameters when generating pagination links
22
+ # (eg. <tt>:controller => "foo", :action => nil</tt>)
23
+ # * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default:
24
+ # <tt>WillPaginate::LinkRenderer</tt>)
25
+ # * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
26
+ # * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
27
+ # false only when you are rendering your own pagination markup (default: true)
28
+ # * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID
29
+ # automatically generated from the class name of objects in collection: for example, paginating
30
+ # ArticleComment models would yield an ID of "article_comments_pagination".
31
+ #
32
+ # All options beside listed ones are passed as HTML attributes to the container
33
+ # element for pagination links (the DIV). For example:
34
+ #
35
+ # <%= will_paginate @posts, :id => 'wp_posts' %>
36
+ #
37
+ # ... will result in:
38
+ #
39
+ # <div class="pagination" id="wp_posts"> ... </div>
40
+ #
41
+ def will_paginate(collection, options = {})
42
+ # early exit if there is nothing to render
43
+ return nil unless collection.total_pages > 1
44
+
45
+ options = WillPaginate::ViewHelpers.pagination_options.merge(options)
46
+
47
+ if options[:prev_label]
48
+ WillPaginate::Deprecation::warn(":prev_label view parameter is now :previous_label; the old name has been deprecated.")
49
+ options[:previous_label] = options.delete(:prev_label)
50
+ end
51
+
52
+ # get the renderer instance
53
+ renderer = case options[:renderer]
54
+ when String
55
+ options[:renderer].constantize.new
56
+ when Class
57
+ options[:renderer].new
58
+ else
59
+ options[:renderer]
60
+ end
61
+ # render HTML for pagination
62
+ renderer.prepare collection, options, self
63
+ renderer.to_html
64
+ end
65
+
66
+ # Renders a helpful message with numbers of displayed vs. total entries.
67
+ # You can use this as a blueprint for your own, similar helpers.
68
+ #
69
+ # <%= page_entries_info @posts %>
70
+ # #-> Displaying posts 6 - 10 of 26 in total
71
+ #
72
+ # By default, the message will use the humanized class name of objects
73
+ # in collection: for instance, "project types" for ProjectType models.
74
+ # Override this to your liking with the <tt>:entry_name</tt> parameter:
75
+ #
76
+ # <%= page_entries_info @posts, :entry_name => 'item' %>
77
+ # #-> Displaying items 6 - 10 of 26 in total
78
+ #
79
+ # Entry name is entered in singular and pluralized with
80
+ # <tt>String#pluralize</tt> method from ActiveSupport. If it isn't
81
+ # loaded, specify plural with <tt>:plural_name</tt> parameter:
82
+ #
83
+ # <%= page_entries_info @posts, :entry_name => 'item', :plural_name => 'items' %>
84
+ #
85
+ # By default, this method produces HTML output. You can trigger plain
86
+ # text output by passing <tt>:html => false</tt> in options.
87
+ def page_entries_info(collection, options = {})
88
+ entry_name = options[:entry_name] || (collection.empty?? 'entry' :
89
+ collection.first.class.name.underscore.gsub('_', ' '))
90
+
91
+ plural_name = if options[:plural_name]
92
+ options[:plural_name]
93
+ elsif entry_name == 'entry'
94
+ plural_name = 'entries'
95
+ elsif entry_name.respond_to? :pluralize
96
+ plural_name = entry_name.pluralize
97
+ else
98
+ entry_name + 's'
99
+ end
100
+
101
+ unless options[:html] == false
102
+ b = '<b>'
103
+ eb = '</b>'
104
+ sp = '&nbsp;'
105
+ else
106
+ b = eb = ''
107
+ sp = ' '
108
+ end
109
+
110
+ if collection.total_pages < 2
111
+ case collection.size
112
+ when 0; "No #{plural_name} found"
113
+ when 1; "Displaying #{b}1#{eb} #{entry_name}"
114
+ else; "Displaying #{b}all #{collection.size}#{eb} #{plural_name}"
115
+ end
116
+ else
117
+ %{Displaying #{plural_name} #{b}%d#{sp}-#{sp}%d#{eb} of #{b}%d#{eb} in total} % [
118
+ collection.offset + 1,
119
+ collection.offset + collection.length,
120
+ collection.total_entries
121
+ ]
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,130 @@
1
+ require 'cgi'
2
+ require 'will_paginate/core_ext'
3
+ require 'will_paginate/view_helpers/link_renderer_base'
4
+
5
+ module WillPaginate
6
+ module ViewHelpers
7
+ # This class does the heavy lifting of actually building the pagination
8
+ # links. It is used by +will_paginate+ helper internally.
9
+ class LinkRenderer < LinkRendererBase
10
+
11
+ # * +collection+ is a WillPaginate::Collection instance or any other object
12
+ # that conforms to that API
13
+ # * +options+ are forwarded from +will_paginate+ view helper
14
+ # * +template+ is the reference to the template being rendered
15
+ def prepare(collection, options, template)
16
+ super(collection, options)
17
+ @template = template
18
+ @container_attributes = @base_url_params = nil
19
+ end
20
+
21
+ # Process it! This method returns the complete HTML string which contains
22
+ # pagination links. Feel free to subclass LinkRenderer and change this
23
+ # method as you see fit.
24
+ def to_html
25
+ html = pagination.map do |item|
26
+ item.is_a?(Fixnum) ?
27
+ page_number(item) :
28
+ send(item)
29
+ end.join(@options[:separator])
30
+
31
+ @options[:container] ? html_container(html) : html
32
+ end
33
+
34
+ # Returns the subset of +options+ this instance was initialized with that
35
+ # represent HTML attributes for the container element of pagination links.
36
+ def container_attributes
37
+ @container_attributes ||= begin
38
+ attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class])
39
+ # pagination of Post models will have the ID of "posts_pagination"
40
+ if @options[:container] and @options[:id] === true
41
+ attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination'
42
+ end
43
+ attributes
44
+ end
45
+ end
46
+
47
+ protected
48
+
49
+ def page_number(page)
50
+ unless page == current_page
51
+ link(page, page, :rel => rel_value(page))
52
+ else
53
+ tag(:em, page)
54
+ end
55
+ end
56
+
57
+ def gap
58
+ '<span class="gap">&hellip;</span>'
59
+ end
60
+
61
+ def previous_page
62
+ previous_or_next_page(@collection.previous_page, @options[:previous_label], 'previous_page')
63
+ end
64
+
65
+ def next_page
66
+ previous_or_next_page(@collection.next_page, @options[:next_label], 'next_page')
67
+ end
68
+
69
+ def previous_or_next_page(page, text, classname)
70
+ if page
71
+ link(text, page, :class => classname)
72
+ else
73
+ tag(:span, text, :class => classname + ' disabled')
74
+ end
75
+ end
76
+
77
+ def html_container(html)
78
+ tag(:div, html, container_attributes)
79
+ end
80
+
81
+ # Returns URL params for +page_link_or_span+, taking the current GET params
82
+ # and <tt>:params</tt> option into account.
83
+ def url(page)
84
+ raise NotImplementedError
85
+ end
86
+
87
+ private
88
+
89
+ def link(text, target, attributes = {})
90
+ if target.is_a? Fixnum
91
+ attributes[:rel] = rel_value(target)
92
+ target = url(target)
93
+ end
94
+ attributes[:href] = target
95
+ tag(:a, text, attributes)
96
+ end
97
+
98
+ def tag(name, value, attributes = {})
99
+ string_attributes = attributes.inject('') do |attrs, pair|
100
+ unless pair.last.nil?
101
+ attrs << %( #{pair.first}="#{CGI::escapeHTML(pair.last.to_s)}")
102
+ end
103
+ attrs
104
+ end
105
+ "<#{name}#{string_attributes}>#{value}</#{name}>"
106
+ end
107
+
108
+ def rel_value(page)
109
+ case page
110
+ when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
111
+ when @collection.next_page; 'next'
112
+ when 1; 'start'
113
+ end
114
+ end
115
+
116
+ def symbolized_update(target, other)
117
+ other.each do |key, value|
118
+ key = key.to_sym
119
+ existing = target[key]
120
+
121
+ if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
122
+ symbolized_update(existing || (target[key] = {}), value)
123
+ else
124
+ target[key] = value
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,83 @@
1
+ require 'will_paginate/view_helpers'
2
+
3
+ module WillPaginate
4
+ module ViewHelpers
5
+ # This class does the heavy lifting of actually building the pagination
6
+ # links. It is used by +will_paginate+ helper internally.
7
+ class LinkRendererBase
8
+
9
+ # * +collection+ is a WillPaginate::Collection instance or any other object
10
+ # that conforms to that API
11
+ # * +options+ are forwarded from +will_paginate+ view helper
12
+ def prepare(collection, options)
13
+ @collection = collection
14
+ @options = options
15
+
16
+ # reset values in case we're re-using this instance
17
+ @total_pages = @param_name = nil
18
+ end
19
+
20
+ def pagination
21
+ items = @options[:page_links] ? windowed_page_numbers : []
22
+ items.unshift :previous_page
23
+ items.push :next_page
24
+ end
25
+
26
+ protected
27
+
28
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
29
+ # <tt>:outer_window</tt> options.
30
+ def windowed_page_numbers
31
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
32
+ window_from = current_page - inner_window
33
+ window_to = current_page + inner_window
34
+
35
+ # adjust lower or upper limit if other is out of bounds
36
+ if window_to > total_pages
37
+ window_from -= window_to - total_pages
38
+ window_to = total_pages
39
+ end
40
+ if window_from < 1
41
+ window_to += 1 - window_from
42
+ window_from = 1
43
+ window_to = total_pages if window_to > total_pages
44
+ end
45
+
46
+ # these are always visible
47
+ middle = window_from..window_to
48
+
49
+ # left window
50
+ if outer_window + 3 < middle.first # there's a gap
51
+ left = (1..(outer_window + 1)).to_a
52
+ left << :gap
53
+ else # runs into visible pages
54
+ left = 1...middle.first
55
+ end
56
+
57
+ # right window
58
+ if total_pages - outer_window - 2 > middle.last # again, gap
59
+ right = ((total_pages - outer_window)..total_pages).to_a
60
+ right.unshift :gap
61
+ else # runs into visible pages
62
+ right = (middle.last + 1)..total_pages
63
+ end
64
+
65
+ left.to_a + middle.to_a + right.to_a
66
+ end
67
+
68
+ private
69
+
70
+ def current_page
71
+ @collection.current_page
72
+ end
73
+
74
+ def total_pages
75
+ @collection.total_pages
76
+ end
77
+
78
+ def param_name
79
+ @param_name ||= @options[:param_name].to_s
80
+ end
81
+ end
82
+ end
83
+ end