will_mostly_paginate 2.4.2

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.
@@ -0,0 +1,170 @@
1
+ module WillPaginate
2
+ # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate,
3
+ # but in other aspects when managing complex conditions that you want to be reusable.
4
+ module NamedScope
5
+ # All subclasses of ActiveRecord::Base have two named_scopes:
6
+ # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
7
+ # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
8
+ #
9
+ # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
10
+ # intermediate values (scopes) around as first-class objects is convenient.
11
+ def self.included(base)
12
+ base.class_eval do
13
+ extend ClassMethods
14
+ named_scope :scoped, lambda { |scope| scope }
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def scopes
20
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
21
+ end
22
+
23
+ # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
24
+ # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
25
+ #
26
+ # class Shirt < ActiveRecord::Base
27
+ # named_scope :red, :conditions => {:color => 'red'}
28
+ # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
29
+ # end
30
+ #
31
+ # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
32
+ # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
33
+ #
34
+ # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
35
+ # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
36
+ # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
37
+ # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
38
+ # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
39
+ #
40
+ # These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
41
+ # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
42
+ # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
43
+ #
44
+ # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to
45
+ # <tt>has_many</tt> associations. If,
46
+ #
47
+ # class Person < ActiveRecord::Base
48
+ # has_many :shirts
49
+ # end
50
+ #
51
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
52
+ # only shirts.
53
+ #
54
+ # Named scopes can also be procedural.
55
+ #
56
+ # class Shirt < ActiveRecord::Base
57
+ # named_scope :colored, lambda { |color|
58
+ # { :conditions => { :color => color } }
59
+ # }
60
+ # end
61
+ #
62
+ # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
63
+ #
64
+ # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
65
+ #
66
+ # class Shirt < ActiveRecord::Base
67
+ # named_scope :red, :conditions => {:color => 'red'} do
68
+ # def dom_id
69
+ # 'red_shirts'
70
+ # end
71
+ # end
72
+ # end
73
+ #
74
+ #
75
+ # For testing complex named scopes, you can examine the scoping options using the
76
+ # <tt>proxy_options</tt> method on the proxy itself.
77
+ #
78
+ # class Shirt < ActiveRecord::Base
79
+ # named_scope :colored, lambda { |color|
80
+ # { :conditions => { :color => color } }
81
+ # }
82
+ # end
83
+ #
84
+ # expected_options = { :conditions => { :colored => 'red' } }
85
+ # assert_equal expected_options, Shirt.colored('red').proxy_options
86
+ def named_scope(name, options = {})
87
+ name = name.to_sym
88
+ scopes[name] = lambda do |parent_scope, *args|
89
+ Scope.new(parent_scope, case options
90
+ when Hash
91
+ options
92
+ when Proc
93
+ options.call(*args)
94
+ end) { |*a| yield(*a) if block_given? }
95
+ end
96
+ (class << self; self end).instance_eval do
97
+ define_method name do |*args|
98
+ scopes[name].call(self, *args)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ class Scope
105
+ attr_reader :proxy_scope, :proxy_options
106
+
107
+ [].methods.each do |m|
108
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/
109
+ delegate m, :to => :proxy_found
110
+ end
111
+ end
112
+
113
+ delegate :scopes, :with_scope, :to => :proxy_scope
114
+
115
+ def initialize(proxy_scope, options)
116
+ [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
117
+ extend Module.new { |*args| yield(*args) } if block_given?
118
+ @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
119
+ end
120
+
121
+ def reload
122
+ load_found; self
123
+ end
124
+
125
+ def first(*args)
126
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
127
+ proxy_found.first(*args)
128
+ else
129
+ find(:first, *args)
130
+ end
131
+ end
132
+
133
+ def last(*args)
134
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
135
+ proxy_found.last(*args)
136
+ else
137
+ find(:last, *args)
138
+ end
139
+ end
140
+
141
+ def empty?
142
+ @found ? @found.empty? : count.zero?
143
+ end
144
+
145
+ def respond_to?(method, include_private = false)
146
+ super || @proxy_scope.respond_to?(method, include_private)
147
+ end
148
+
149
+ protected
150
+ def proxy_found
151
+ @found || load_found
152
+ end
153
+
154
+ private
155
+ def method_missing(method, *args)
156
+ if scopes.include?(method)
157
+ scopes[method].call(self, *args)
158
+ else
159
+ with_scope :find => proxy_options do
160
+ proxy_scope.send(method, *args) { |*a| yield(*a) if block_given? }
161
+ end
162
+ end
163
+ end
164
+
165
+ def load_found
166
+ @found = find(:all)
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,37 @@
1
+ ActiveRecord::Associations::AssociationProxy.class_eval do
2
+ protected
3
+ def with_scope(*args)
4
+ @reflection.klass.send(:with_scope, *args) { |*a| yield(*a) if block_given? }
5
+ end
6
+ end
7
+
8
+ [ ActiveRecord::Associations::AssociationCollection,
9
+ ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass|
10
+ klass.class_eval do
11
+ protected
12
+ alias :method_missing_without_scopes :method_missing_without_paginate
13
+ def method_missing_without_paginate(method, *args)
14
+ if @reflection.klass.scopes.include?(method)
15
+ @reflection.klass.scopes[method].call(self, *args) { |*a| yield(*a) if block_given? }
16
+ else
17
+ method_missing_without_scopes(method, *args) { |*a| yield(*a) if block_given? }
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ # Rails 1.2.6
24
+ ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
25
+ protected
26
+ def method_missing(method, *args)
27
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
28
+ super
29
+ elsif @reflection.klass.scopes.include?(method)
30
+ @reflection.klass.scopes[method].call(self, *args)
31
+ else
32
+ @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
33
+ @reflection.klass.send(method, *args) { |*a| yield(*a) if block_given? }
34
+ end
35
+ end
36
+ end
37
+ end if ActiveRecord::Base.respond_to? :find_first
@@ -0,0 +1,9 @@
1
+ module WillPaginate
2
+ module VERSION
3
+ MAJOR = 2
4
+ MINOR = 4
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,430 @@
1
+ require 'will_mostly_paginate/core_ext'
2
+
3
+ module WillPaginate
4
+ # = Will Paginate view helpers
5
+ #
6
+ # The main view helper, #will_paginate, renders
7
+ # pagination links for the given collection. The helper itself is lightweight
8
+ # and serves only as a wrapper around LinkRenderer instantiation; the
9
+ # renderer then does all the hard work of generating the HTML.
10
+ #
11
+ # == Global options for helpers
12
+ #
13
+ # Options for pagination helpers are optional and get their default values from the
14
+ # <tt>WillPaginate::ViewHelpers.pagination_options</tt> hash. You can write to this hash to
15
+ # override default options on the global level:
16
+ #
17
+ # WillPaginate::ViewHelpers.pagination_options[:previous_label] = 'Previous page'
18
+ #
19
+ # By putting this into "config/initializers/will_paginate.rb" (or simply environment.rb in
20
+ # older versions of Rails) you can easily translate link texts to previous
21
+ # and next pages, as well as override some other defaults to your liking.
22
+ module ViewHelpers
23
+ # default options that can be overridden on the global level
24
+ @@pagination_options = {
25
+ :class => 'pagination',
26
+ :previous_label => '&laquo; Previous',
27
+ :next_label => 'Next &raquo;',
28
+ :inner_window => 4, # links around the current page
29
+ :outer_window => 1, # links around beginning and end
30
+ :separator => ' ', # single space is friendly to spiders and non-graphic browsers
31
+ :param_name => :page,
32
+ :params => nil,
33
+ :renderer => 'WillPaginate::LinkRenderer',
34
+ :page_links => true,
35
+ :container => true
36
+ }
37
+ mattr_reader :pagination_options
38
+
39
+ # Renders Digg/Flickr-style pagination for a WillPaginate::Collection
40
+ # object. Nil is returned if there is only one page in total; no point in
41
+ # rendering the pagination in that case...
42
+ #
43
+ # ==== Options
44
+ # Display options:
45
+ # * <tt>:previous_label</tt> -- default: "« Previous" (this parameter is called <tt>:prev_label</tt> in versions <b>2.3.2</b> and older!)
46
+ # * <tt>:next_label</tt> -- default: "Next »"
47
+ # * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
48
+ # * <tt>:inner_window</tt> -- how many links are shown around the current page (default: 4)
49
+ # * <tt>:outer_window</tt> -- how many links are around the first and the last page (default: 1)
50
+ # * <tt>:separator</tt> -- string separator for page HTML elements (default: single space)
51
+ #
52
+ # HTML options:
53
+ # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination")
54
+ # * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
55
+ # false only when you are rendering your own pagination markup (default: true)
56
+ # * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID
57
+ # automatically generated from the class name of objects in collection: for example, paginating
58
+ # ArticleComment models would yield an ID of "article_comments_pagination".
59
+ #
60
+ # Advanced options:
61
+ # * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
62
+ # * <tt>:params</tt> -- additional parameters when generating pagination links
63
+ # (eg. <tt>:controller => "foo", :action => nil</tt>)
64
+ # * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default:
65
+ # <tt>WillPaginate::LinkRenderer</tt>)
66
+ #
67
+ # All options not recognized by will_paginate will become HTML attributes on the container
68
+ # element for pagination links (the DIV). For example:
69
+ #
70
+ # <%= will_paginate @posts, :style => 'font-size: small' %>
71
+ #
72
+ # ... will result in:
73
+ #
74
+ # <div class="pagination" style="font-size: small"> ... </div>
75
+ #
76
+ # ==== Using the helper without arguments
77
+ # If the helper is called without passing in the collection object, it will
78
+ # try to read from the instance variable inferred by the controller name.
79
+ # For example, calling +will_paginate+ while the current controller is
80
+ # PostsController will result in trying to read from the <tt>@posts</tt>
81
+ # variable. Example:
82
+ #
83
+ # <%= will_paginate :id => true %>
84
+ #
85
+ # ... will result in <tt>@post</tt> collection getting paginated:
86
+ #
87
+ # <div class="pagination" id="posts_pagination"> ... </div>
88
+ #
89
+ def will_paginate(collection = nil, options = {})
90
+ options, collection = collection, nil if collection.is_a? Hash
91
+ unless collection or !controller
92
+ collection_name = "@#{controller.controller_name}"
93
+ collection = instance_variable_get(collection_name)
94
+ raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " +
95
+ "forget to pass the collection object for will_paginate?" unless collection
96
+ end
97
+ # early exit if there is nothing to render
98
+ if collection.page_all
99
+ return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(collection) > 1
100
+ else
101
+ return nil if ((collection.current_page == 1) && !collection.next_page)
102
+ end
103
+
104
+ options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options
105
+ if options[:prev_label]
106
+ WillPaginate::Deprecation::warn(":prev_label view parameter is now :previous_label; the old name has been deprecated", caller)
107
+ options[:previous_label] = options.delete(:prev_label)
108
+ end
109
+
110
+ # get the renderer instance
111
+ renderer = case options[:renderer]
112
+ when String
113
+ options[:renderer].to_s.constantize.new
114
+ when Class
115
+ options[:renderer].new
116
+ else
117
+ options[:renderer]
118
+ end
119
+ # render HTML for pagination
120
+ renderer.prepare collection, options, self
121
+ renderer.to_html
122
+ end
123
+
124
+ # Wrapper for rendering pagination links at both top and bottom of a block
125
+ # of content.
126
+ #
127
+ # <% paginated_section @posts do %>
128
+ # <ol id="posts">
129
+ # <% for post in @posts %>
130
+ # <li> ... </li>
131
+ # <% end %>
132
+ # </ol>
133
+ # <% end %>
134
+ #
135
+ # will result in:
136
+ #
137
+ # <div class="pagination"> ... </div>
138
+ # <ol id="posts">
139
+ # ...
140
+ # </ol>
141
+ # <div class="pagination"> ... </div>
142
+ #
143
+ # Arguments are passed to a <tt>will_paginate</tt> call, so the same options
144
+ # apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two
145
+ # blocks of pagination links sharing the same ID (which is invalid HTML).
146
+ def paginated_section(*args, &block)
147
+ pagination = will_paginate(*args).to_s
148
+
149
+ unless ActionView::Base.respond_to? :erb_variable
150
+ concat pagination
151
+ yield
152
+ concat pagination
153
+ else
154
+ content = pagination + capture(&block) + pagination
155
+ concat(content, block.binding)
156
+ end
157
+ end
158
+
159
+ # Renders a helpful message with numbers of displayed vs. total entries.
160
+ # You can use this as a blueprint for your own, similar helpers.
161
+ #
162
+ # <%= page_entries_info @posts %>
163
+ # #-> Displaying posts 6 - 10 of 26 in total
164
+ #
165
+ # By default, the message will use the humanized class name of objects
166
+ # in collection: for instance, "project types" for ProjectType models.
167
+ # Override this with the <tt>:entry_name</tt> parameter:
168
+ #
169
+ # <%= page_entries_info @posts, :entry_name => 'item' %>
170
+ # #-> Showing 6-10 of 26 posts
171
+ def page_entries_info(collection, options = {})
172
+ entry_name = options[:entry_name] || (collection.empty? ? 'entry' : collection.first.class.name.underscore.sub('_', ' '))
173
+
174
+ s = entry_name
175
+ p = entry_name.pluralize
176
+ n1 = collection.offset + 1
177
+ n2 = collection.offset + collection.length
178
+ t = collection.total_entries
179
+
180
+ if !collection.page_all
181
+ "Showing #{p} #{n1}-#{n2}"
182
+ elsif collection.total_pages < 2
183
+ case collection.size
184
+ when 0; "No #{p} found"
185
+ when 1; "Showing one #{s}"
186
+ else; "Showing all #{t} #{p}"
187
+ end
188
+ else
189
+ "Showing #{n1}-#{n2} of #{t} #{p}"
190
+ end
191
+ end
192
+
193
+ if respond_to? :safe_helper
194
+ safe_helper :will_paginate, :paginated_section, :page_entries_info
195
+ end
196
+
197
+ def self.total_pages_for_collection(collection) #:nodoc:
198
+ if collection.respond_to?('page_count') and !collection.respond_to?('total_pages')
199
+ WillPaginate::Deprecation.warn %{
200
+ You are using a paginated collection of class #{collection.class.name}
201
+ which conforms to the old API of WillPaginate::Collection by using
202
+ `page_count`, while the current method name is `total_pages`. Please
203
+ upgrade yours or 3rd-party code that provides the paginated collection}, caller
204
+ class << collection
205
+ def total_pages; page_count; end
206
+ end
207
+ end
208
+ collection.total_pages
209
+ end
210
+ end
211
+
212
+ # This class does the heavy lifting of actually building the pagination
213
+ # links. It is used by the <tt>will_paginate</tt> helper internally.
214
+ class LinkRenderer
215
+
216
+ # The gap in page links is represented by:
217
+ #
218
+ # <span class="gap">&hellip;</span>
219
+ attr_accessor :gap_marker
220
+
221
+ def initialize
222
+ @gap_marker = '<span class="gap">&hellip;</span>'
223
+ end
224
+
225
+ # * +collection+ is a WillPaginate::Collection instance or any other object
226
+ # that conforms to that API
227
+ # * +options+ are forwarded from +will_paginate+ view helper
228
+ # * +template+ is the reference to the template being rendered
229
+ def prepare(collection, options, template)
230
+ @collection = collection
231
+ @options = options
232
+ @template = template
233
+
234
+ # reset values in case we're re-using this instance
235
+ @total_pages = @param_name = @url_string = nil
236
+ end
237
+
238
+ # Process it! This method returns the complete HTML string which contains
239
+ # pagination links. Feel free to subclass LinkRenderer and change this
240
+ # method as you see fit.
241
+ def to_html
242
+ links = @options[:page_links] ? windowed_links : []
243
+ # previous/next buttons
244
+ links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:previous_label])
245
+ links.push gap_marker if (!page_all && @collection.next_page)
246
+ links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label])
247
+
248
+ html = links.join(@options[:separator])
249
+ html = html.html_safe if html.respond_to? :html_safe
250
+ @options[:container] ? @template.content_tag(:div, html, html_attributes) : html
251
+ end
252
+
253
+ # Returns the subset of +options+ this instance was initialized with that
254
+ # represent HTML attributes for the container element of pagination links.
255
+ def html_attributes
256
+ return @html_attributes if @html_attributes
257
+ @html_attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class])
258
+ # pagination of Post models will have the ID of "posts_pagination"
259
+ if @options[:container] and @options[:id] === true
260
+ @html_attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination'
261
+ end
262
+ @html_attributes
263
+ end
264
+
265
+ protected
266
+
267
+ # Collects link items for visible page numbers.
268
+ def windowed_links
269
+ prev = nil
270
+
271
+ visible_page_numbers.inject [] do |links, n|
272
+ # detect gaps:
273
+ links << gap_marker if prev and n > prev + 1
274
+ links << page_link_or_span(n, 'current')
275
+ prev = n
276
+ links
277
+ end
278
+ end
279
+
280
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
281
+ # <tt>:outer_window</tt> options.
282
+ def visible_page_numbers
283
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
284
+ window_from = current_page - inner_window
285
+ window_to = if page_all
286
+ current_page + inner_window
287
+ elsif @collection.next_page && (inner_window > 1)
288
+ @collection.next_page
289
+ else
290
+ current_page
291
+ end
292
+
293
+ # adjust lower or upper limit if other is out of bounds
294
+ if page_all && (window_to > total_pages)
295
+ window_from -= window_to - total_pages
296
+ window_to = total_pages
297
+ end
298
+ if window_from < 1
299
+ window_to += 1 - window_from if page_all
300
+ window_from = 1
301
+ window_to = total_pages if (page_all && (window_to > total_pages))
302
+ end
303
+
304
+ visible = (1..(page_all ? total_pages : window_to)).to_a
305
+ left_gap = (2 + outer_window)...window_from
306
+ visible -= left_gap.to_a if left_gap.last - left_gap.first > 1
307
+ if page_all
308
+ right_gap = (window_to + 1)...(total_pages - outer_window)
309
+ visible -= right_gap.to_a if right_gap.last - right_gap.first > 1
310
+ end
311
+
312
+ visible
313
+ end
314
+
315
+ def page_link_or_span(page, span_class, text = nil)
316
+ text ||= page.to_s
317
+ text = text.html_safe if text.respond_to? :html_safe
318
+
319
+ if page and page != current_page
320
+ classnames = span_class && span_class.index(' ') && span_class.split(' ', 2).last
321
+ page_link page, text, :rel => rel_value(page), :class => classnames
322
+ else
323
+ page_span page, text, :class => span_class
324
+ end
325
+ end
326
+
327
+ def page_link(page, text, attributes = {})
328
+ @template.link_to text, url_for(page), attributes
329
+ end
330
+
331
+ def page_span(page, text, attributes = {})
332
+ @template.content_tag :span, text, attributes
333
+ end
334
+
335
+ # Returns URL params for +page_link_or_span+, taking the current GET params
336
+ # and <tt>:params</tt> option into account.
337
+ def url_for(page)
338
+ page_one = page == 1
339
+ unless @url_string and !page_one
340
+ @url_params = {}
341
+ # page links should preserve GET parameters
342
+ stringified_merge @url_params, @template.params if @template.request.get?
343
+ stringified_merge @url_params, @options[:params] if @options[:params]
344
+
345
+ if complex = param_name.index(/[^\w-]/)
346
+ page_param = parse_query_parameters("#{param_name}=#{page}")
347
+
348
+ stringified_merge @url_params, page_param
349
+ else
350
+ @url_params[param_name] = page_one ? 1 : 2
351
+ end
352
+
353
+ url = @template.url_for(@url_params)
354
+ return url if page_one
355
+
356
+ if complex
357
+ @url_string = url.sub(%r!((?:\?|&amp;)#{CGI.escape param_name}=)#{page}!, "\\1\0")
358
+ return url
359
+ else
360
+ @url_string = url
361
+ @url_params[param_name] = 3
362
+ @template.url_for(@url_params).split(//).each_with_index do |char, i|
363
+ if char == '3' and url[i, 1] == '2'
364
+ @url_string[i] = "\0"
365
+ break
366
+ end
367
+ end
368
+ end
369
+ end
370
+ # finally!
371
+ @url_string.sub "\0", page.to_s
372
+ end
373
+
374
+ private
375
+
376
+ def rel_value(page)
377
+ case page
378
+ when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
379
+ when @collection.next_page; 'next'
380
+ when 1; 'start'
381
+ end
382
+ end
383
+
384
+ def current_page
385
+ @collection.current_page
386
+ end
387
+
388
+ def page_all
389
+ @collection.page_all
390
+ end
391
+
392
+ def total_pages
393
+ @total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@collection)
394
+ end
395
+
396
+ def param_name
397
+ @param_name ||= @options[:param_name].to_s
398
+ end
399
+
400
+ # Recursively merge into target hash by using stringified keys from the other one
401
+ def stringified_merge(target, other)
402
+ other.each do |key, value|
403
+ key = key.to_s # this line is what it's all about!
404
+ existing = target[key]
405
+
406
+ if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
407
+ stringified_merge(existing || (target[key] = {}), value)
408
+ else
409
+ target[key] = value
410
+ end
411
+ end
412
+ end
413
+
414
+ def parse_query_parameters(params)
415
+ if defined? Rack::Utils
416
+ # For Rails > 2.3
417
+ Rack::Utils.parse_nested_query(params)
418
+ elsif defined?(ActionController::AbstractRequest)
419
+ ActionController::AbstractRequest.parse_query_parameters(params)
420
+ elsif defined?(ActionController::UrlEncodedPairParser)
421
+ # For Rails > 2.2
422
+ ActionController::UrlEncodedPairParser.parse_query_parameters(params)
423
+ elsif defined?(CGIMethods)
424
+ CGIMethods.parse_query_parameters(params)
425
+ else
426
+ raise "unsupported ActionPack version"
427
+ end
428
+ end
429
+ end
430
+ end