will_paginate 2.3.16 → 3.0.pre

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 (83) hide show
  1. data/CHANGELOG.rdoc +24 -80
  2. data/LICENSE +1 -1
  3. data/README.rdoc +125 -0
  4. data/Rakefile +26 -22
  5. data/lib/will_paginate/array.rb +25 -8
  6. data/lib/will_paginate/collection.rb +15 -28
  7. data/lib/will_paginate/core_ext.rb +26 -0
  8. data/lib/will_paginate/deprecation.rb +50 -0
  9. data/lib/will_paginate/finders/active_record.rb +158 -0
  10. data/lib/will_paginate/finders/active_resource.rb +51 -0
  11. data/lib/will_paginate/finders/base.rb +112 -0
  12. data/lib/will_paginate/finders/data_mapper.rb +30 -0
  13. data/lib/will_paginate/finders/sequel.rb +23 -0
  14. data/lib/will_paginate/finders.rb +9 -0
  15. data/lib/will_paginate/railtie.rb +26 -0
  16. data/lib/will_paginate/version.rb +5 -5
  17. data/lib/will_paginate/view_helpers/action_view.rb +142 -0
  18. data/lib/will_paginate/view_helpers/base.rb +126 -0
  19. data/lib/will_paginate/view_helpers/link_renderer.rb +130 -0
  20. data/lib/will_paginate/view_helpers/link_renderer_base.rb +83 -0
  21. data/lib/will_paginate/view_helpers/merb.rb +13 -0
  22. data/lib/will_paginate/view_helpers.rb +25 -436
  23. data/lib/will_paginate.rb +10 -84
  24. data/spec/collection_spec.rb +147 -0
  25. data/spec/console +8 -0
  26. data/spec/console_fixtures.rb +8 -0
  27. data/spec/database.yml +22 -0
  28. data/spec/finders/active_record_spec.rb +377 -0
  29. data/spec/finders/active_resource_spec.rb +52 -0
  30. data/spec/finders/activerecord_test_connector.rb +114 -0
  31. data/spec/finders/data_mapper_spec.rb +62 -0
  32. data/spec/finders/data_mapper_test_connector.rb +20 -0
  33. data/spec/finders/sequel_spec.rb +53 -0
  34. data/spec/finders/sequel_test_connector.rb +9 -0
  35. data/spec/finders_spec.rb +76 -0
  36. data/{test → spec}/fixtures/developer.rb +2 -3
  37. data/{test → spec}/fixtures/project.rb +2 -6
  38. data/{test → spec}/fixtures/projects.yml +1 -1
  39. data/{test → spec}/fixtures/reply.rb +1 -1
  40. data/spec/fixtures/topic.rb +7 -0
  41. data/spec/rcov.opts +2 -0
  42. data/spec/spec.opts +2 -0
  43. data/spec/spec_helper.rb +74 -0
  44. data/spec/tasks.rake +60 -0
  45. data/spec/view_helpers/action_view_spec.rb +345 -0
  46. data/spec/view_helpers/base_spec.rb +64 -0
  47. data/spec/view_helpers/link_renderer_base_spec.rb +84 -0
  48. data/spec/view_helpers/view_example_group.rb +103 -0
  49. metadata +60 -52
  50. data/README.md +0 -53
  51. data/lib/will_paginate/finder.rb +0 -264
  52. data/lib/will_paginate/i18n.rb +0 -29
  53. data/lib/will_paginate/locale/en.yml +0 -33
  54. data/lib/will_paginate/named_scope.rb +0 -170
  55. data/lib/will_paginate/named_scope_patch.rb +0 -37
  56. data/lib/will_paginate/per_page.rb +0 -27
  57. data/test/ci.rb +0 -56
  58. data/test/collection_test.rb +0 -160
  59. data/test/console +0 -8
  60. data/test/database.yml +0 -16
  61. data/test/finder_test.rb +0 -520
  62. data/test/fixtures/topic.rb +0 -12
  63. data/test/gemfiles/Gemfile.1.2 +0 -13
  64. data/test/gemfiles/Gemfile.1.2.lock +0 -39
  65. data/test/gemfiles/Gemfile.2.0 +0 -16
  66. data/test/gemfiles/Gemfile.2.0.lock +0 -28
  67. data/test/gemfiles/Gemfile.2.1 +0 -16
  68. data/test/gemfiles/Gemfile.2.1.lock +0 -28
  69. data/test/gemfiles/Gemfile.2.2 +0 -16
  70. data/test/gemfiles/Gemfile.2.2.lock +0 -28
  71. data/test/helper.rb +0 -34
  72. data/test/lib/activerecord_test_case.rb +0 -38
  73. data/test/lib/activerecord_test_connector.rb +0 -86
  74. data/test/lib/load_fixtures.rb +0 -12
  75. data/test/lib/view_test_process.rb +0 -186
  76. data/test/view_test.rb +0 -373
  77. /data/{test → spec}/fixtures/admin.rb +0 -0
  78. /data/{test → spec}/fixtures/developers_projects.yml +0 -0
  79. /data/{test → spec}/fixtures/replies.yml +0 -0
  80. /data/{test → spec}/fixtures/schema.rb +0 -0
  81. /data/{test → spec}/fixtures/topics.yml +0 -0
  82. /data/{test → spec}/fixtures/user.rb +0 -0
  83. /data/{test → spec}/fixtures/users.yml +0 -0
@@ -0,0 +1,158 @@
1
+ require 'will_paginate/finders/base'
2
+ require 'active_record'
3
+
4
+ module WillPaginate::Finders
5
+ # = Paginating finders for ActiveRecord models
6
+ #
7
+ # WillPaginate adds +paginate+, +per_page+ and other methods to
8
+ # ActiveRecord::Base class methods and associations. It also hooks into
9
+ # +method_missing+ to intercept pagination calls to dynamic finders such as
10
+ # +paginate_by_user_id+ and translate them to ordinary finders
11
+ # (+find_all_by_user_id+ in this case).
12
+ #
13
+ # In short, paginating finders are equivalent to ActiveRecord finders; the
14
+ # only difference is that we start with "paginate" instead of "find" and
15
+ # that <tt>:page</tt> is required parameter:
16
+ #
17
+ # @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC'
18
+ #
19
+ # In paginating finders, "all" is implicit. There is no sense in paginating
20
+ # a single record, right? So, you can drop the <tt>:all</tt> argument:
21
+ #
22
+ # Post.paginate(...) => Post.find :all
23
+ # Post.paginate_all_by_something => Post.find_all_by_something
24
+ # Post.paginate_by_something => Post.find_all_by_something
25
+ #
26
+ module ActiveRecord
27
+ include WillPaginate::Finders::Base
28
+
29
+ # In Rails, this is automatically called to mix-in pagination functionality to ActiveRecord.
30
+ def self.enable!
31
+ ::ActiveRecord::Base.class_eval do
32
+ extend ActiveRecord
33
+ end
34
+
35
+ # support pagination on associations and scopes
36
+ [::ActiveRecord::Relation, ::ActiveRecord::Associations::AssociationCollection].each do |klass|
37
+ klass.send(:include, ActiveRecord)
38
+ end
39
+ end
40
+
41
+ # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
42
+ # based on the params otherwise used by paginating finds: +page+ and
43
+ # +per_page+.
44
+ #
45
+ # Example:
46
+ #
47
+ # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000],
48
+ # :page => params[:page], :per_page => 3
49
+ #
50
+ # A query for counting rows will automatically be generated if you don't
51
+ # supply <tt>:total_entries</tt>. If you experience problems with this
52
+ # generated SQL, you might want to perform the count manually in your
53
+ # application.
54
+ #
55
+ def paginate_by_sql(sql, options)
56
+ WillPaginate::Collection.create(*wp_parse_options(options)) do |pager|
57
+ query = sanitize_sql(sql.dup)
58
+ original_query = query.dup
59
+ # add limit, offset
60
+ query << " LIMIT #{pager.per_page} OFFSET #{pager.offset}"
61
+ # perfom the find
62
+ pager.replace find_by_sql(query)
63
+
64
+ unless pager.total_entries
65
+ count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s]+$/mi, ''
66
+ count_query = "SELECT COUNT(*) FROM (#{count_query})"
67
+
68
+ unless ['oracle', 'oci'].include?(self.connection.adapter_name.downcase)
69
+ count_query << ' AS count_table'
70
+ end
71
+ # perform the count query
72
+ pager.total_entries = count_by_sql(count_query)
73
+ end
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ def wp_query(options, pager, args, &block) #:nodoc:
80
+ finder = (options.delete(:finder) || 'find').to_s
81
+ find_options = options.except(:count).update(:offset => pager.offset, :limit => pager.per_page)
82
+
83
+ if finder == 'find'
84
+ if Array === args.first and !pager.total_entries
85
+ pager.total_entries = args.first.size
86
+ end
87
+ args << :all if args.empty?
88
+ end
89
+
90
+ args << find_options
91
+ pager.replace send(finder, *args, &block)
92
+
93
+ unless pager.total_entries
94
+ # magic counting
95
+ pager.total_entries = wp_count(options, args, finder)
96
+ end
97
+ end
98
+
99
+ # Does the not-so-trivial job of finding out the total number of entries
100
+ # in the database. It relies on the ActiveRecord +count+ method.
101
+ def wp_count(options, args, finder) #:nodoc:
102
+ # find out if we are in a model or an association proxy
103
+ klass = (@owner and @reflection) ? @reflection.klass : self
104
+ count_options = wp_parse_count_options(options, klass)
105
+
106
+ # we may have to scope ...
107
+ counter = Proc.new { count(count_options) }
108
+
109
+ count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with'))
110
+ # scope_out adds a 'with_finder' method which acts like with_scope, if it's present
111
+ # then execute the count with the scoping provided by the with_finder
112
+ send(scoper, &counter)
113
+ elsif finder =~ /^find_(all_by|by)_([_a-zA-Z]\w*)$/
114
+ # extract conditions from calls like "paginate_by_foo_and_bar"
115
+ attribute_names = $2.split('_and_')
116
+ conditions = construct_attributes_from_arguments(attribute_names, args)
117
+ with_scope(:find => { :conditions => conditions }, &counter)
118
+ else
119
+ counter.call
120
+ end
121
+
122
+ count.respond_to?(:length) ? count.length : count
123
+ end
124
+
125
+ def wp_parse_count_options(options, klass) #:nodoc:
126
+ excludees = [:count, :order, :limit, :offset, :readonly]
127
+
128
+ # Use :select from scope if it isn't already present.
129
+ # FIXME: this triggers extra queries when going through associations
130
+ # if options[:select].blank? && current_scoped_methods && current_scoped_methods.select_values.present?
131
+ # options[:select] = current_scoped_methods.select_values.join(", ")
132
+ # end
133
+
134
+ if options[:select] and options[:select] =~ /^\s*DISTINCT\b/i
135
+ # Remove quoting and check for table_name.*-like statement.
136
+ if options[:select].gsub('`', '') =~ /\w+\.\*/
137
+ options[:select] = "DISTINCT #{klass.table_name}.#{klass.primary_key}"
138
+ end
139
+ else
140
+ excludees << :select
141
+ end
142
+
143
+ # count expects (almost) the same options as find
144
+ count_options = options.except *excludees
145
+
146
+ # merge the hash found in :count
147
+ # this allows you to specify :select, :order, or anything else just for the count query
148
+ count_options.update options[:count] if options[:count]
149
+
150
+ # forget about includes if they are irrelevant when counting
151
+ if count_options[:include] and count_options[:conditions].blank? and count_options[:group].blank?
152
+ count_options.delete :include
153
+ end
154
+
155
+ count_options
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,51 @@
1
+ require 'will_paginate/finders/base'
2
+ require 'active_resource'
3
+
4
+ module WillPaginate::Finders
5
+ # Paginate your ActiveResource models.
6
+ #
7
+ # @posts = Post.paginate :all, :params => {
8
+ # :page => params[:page], :order => 'created_at DESC'
9
+ # }
10
+ #
11
+ module ActiveResource
12
+ include WillPaginate::Finders::Base
13
+
14
+ protected
15
+
16
+ def wp_query(options, pager, args, &block) #:nodoc:
17
+ unless args.empty? or args.first == :all
18
+ raise ArgumentError, "finder arguments other than :all are not supported for pagination (#{args.inspect} given)"
19
+ end
20
+ params = (options[:params] ||= {})
21
+ params[:page] = pager.current_page
22
+ params[:per_page] = pager.per_page
23
+
24
+ pager.replace find_every(options, &block)
25
+ end
26
+
27
+ # Takes the format that Hash.from_xml produces out of an unknown type
28
+ # (produced by WillPaginate::Collection#to_xml_with_collection_type),
29
+ # parses it into a WillPaginate::Collection,
30
+ # and forwards the result to the former +instantiate_collection+ method.
31
+ # It only does this for hashes that have a :type => "collection".
32
+ def instantiate_collection_with_collection(collection, prefix_options = {}) #:nodoc:
33
+ if collection.is_a?(Hash) && collection["type"] == "collection"
34
+ collectables = collection.values.find{ |c| c.is_a?(Hash) || c.is_a?(Array) }
35
+ collectables = [collectables].compact unless collectables.kind_of?(Array)
36
+ instantiated_collection = WillPaginate::Collection.create(collection["current_page"], collection["per_page"], collection["total_entries"]) do |pager|
37
+ pager.replace instantiate_collection_without_collection(collectables, prefix_options)
38
+ end
39
+ else
40
+ instantiate_collection_without_collection(collection, prefix_options)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ ActiveResource::Base.class_eval do
47
+ extend WillPaginate::Finders::ActiveResource
48
+ class << self
49
+ # alias_method_chain :instantiate_collection, :collection
50
+ end
51
+ end
@@ -0,0 +1,112 @@
1
+ require 'will_paginate/core_ext'
2
+
3
+ module WillPaginate
4
+ module Finders
5
+ # = Database-agnostic finder module
6
+ #
7
+ # Out of the box, will_paginate supports hooking in several ORMs to
8
+ # provide paginating finders based on their API. As of this writing, the
9
+ # supported libraries are:
10
+ #
11
+ # * ActiveRecord
12
+ # * DataMapper
13
+ # * Sequel
14
+ #
15
+ # It's easy to write your own adapter for anything that can load data with
16
+ # explicit limit and offset settings. DataMapper adapter is a nice and
17
+ # compact example of writing an adapter to bring the +paginate+ method to
18
+ # DataMapper models.
19
+ #
20
+ # == The importance of SQL's <tt>ORDER BY</tt>
21
+ #
22
+ # In most ORMs, <tt>:order</tt> parameter specifies columns for the
23
+ # <tt>ORDER BY</tt> clause in SQL. It is important to have it, since
24
+ # pagination only makes sense with ordered sets. Without the order clause,
25
+ # databases aren't required to do consistent ordering when performing
26
+ # <tt>SELECT</tt> queries.
27
+ #
28
+ # Ordering by a field for which many records share the same value (e.g.
29
+ # "status") can still result in incorrect ordering with some databases (MS
30
+ # SQL and Postgres for instance). With these databases it's recommend that
31
+ # you order by primary key as well. That is, instead of ordering by
32
+ # "status DESC", use the alternative "status DESC, id DESC" and this will
33
+ # yield consistent results.
34
+ #
35
+ # Therefore, make sure you are doing ordering on a column that makes the
36
+ # most sense in the current context. Make that obvious to the user, also.
37
+ # For perfomance reasons you will also want to add an index to that column.
38
+ module Base
39
+ def per_page
40
+ @per_page ||= 30
41
+ end
42
+
43
+ def per_page=(limit)
44
+ @per_page = limit.to_i
45
+ end
46
+
47
+ # This is the main paginating finder.
48
+ #
49
+ # == Special parameters for paginating finders
50
+ # * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil
51
+ # * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (which is 30 if not overridden)
52
+ # * <tt>:total_entries</tt> -- use only if you manually count total entries
53
+ # * <tt>:count</tt> -- additional options that are passed on to +count+
54
+ # * <tt>:finder</tt> -- name of the finder method to use (default: "find")
55
+ #
56
+ # All other options (+conditions+, +order+, ...) are forwarded to +find+
57
+ # and +count+ calls.
58
+ def paginate(*args, &block)
59
+ options = args.pop
60
+ page, per_page, total_entries = wp_parse_options(options)
61
+
62
+ WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
63
+ query_options = options.except :page, :per_page, :total_entries
64
+ wp_query(query_options, pager, args, &block)
65
+ end
66
+ end
67
+
68
+ # Iterates through all records by loading one page at a time. This is useful
69
+ # for migrations or any other use case where you don't want to load all the
70
+ # records in memory at once.
71
+ #
72
+ # It uses +paginate+ internally; therefore it accepts all of its options.
73
+ # You can specify a starting page with <tt>:page</tt> (default is 1). Default
74
+ # <tt>:order</tt> is <tt>"id"</tt>, override if necessary.
75
+ #
76
+ # {Jamis Buck describes this}[http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord]
77
+ # and also uses a more efficient way for MySQL.
78
+ def paginated_each(options = {}, &block)
79
+ options = { :order => 'id', :page => 1 }.merge options
80
+ options[:page] = options[:page].to_i
81
+ options[:total_entries] = 0 # skip the individual count queries
82
+ total = 0
83
+
84
+ begin
85
+ collection = paginate(options)
86
+ total += collection.each(&block).size
87
+ options[:page] += 1
88
+ end until collection.size < collection.per_page
89
+
90
+ total
91
+ end
92
+
93
+ protected
94
+
95
+ def wp_parse_options(options) #:nodoc:
96
+ raise ArgumentError, 'parameter hash expected' unless Hash === options
97
+ raise ArgumentError, ':page parameter required' unless options.key? :page
98
+
99
+ if options[:count] and options[:total_entries]
100
+ raise ArgumentError, ':count and :total_entries are mutually exclusive'
101
+ end
102
+
103
+ page = options[:page] || 1
104
+ per_page = options[:per_page] || self.per_page
105
+ total = options[:total_entries]
106
+
107
+ return [page, per_page, total]
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,30 @@
1
+ require 'will_paginate/finders/base'
2
+ require 'dm-core'
3
+
4
+ module WillPaginate::Finders
5
+ module DataMapper
6
+ include WillPaginate::Finders::Base
7
+
8
+ protected
9
+
10
+ def wp_query(options, pager, args, &block) #:nodoc
11
+ find_options = options.except(:count).update(:offset => pager.offset, :limit => pager.per_page)
12
+
13
+ pager.replace all(find_options, &block)
14
+
15
+ unless pager.total_entries
16
+ pager.total_entries = wp_count(options)
17
+ end
18
+ end
19
+
20
+ def wp_count(options) #:nodoc
21
+ count_options = options.except(:count, :order)
22
+ # merge the hash found in :count
23
+ count_options.update options[:count] if options[:count]
24
+
25
+ count_options.empty?? count() : count(count_options)
26
+ end
27
+ end
28
+ end
29
+
30
+ DataMapper::Model.send(:include, WillPaginate::Finders::DataMapper)
@@ -0,0 +1,23 @@
1
+ require 'will_paginate/core_ext'
2
+ require 'sequel'
3
+ require 'sequel/extensions/pagination'
4
+
5
+ existing_methods = Sequel::Dataset::Pagination.instance_methods
6
+
7
+ Sequel::Dataset::Pagination.module_eval do
8
+ # it should quack like a WillPaginate::Collection
9
+
10
+ alias :total_pages :page_count unless existing_methods.include_method? :total_pages
11
+ alias :per_page :page_size unless existing_methods.include_method? :per_page
12
+ alias :previous_page :prev_page unless existing_methods.include_method? :previous_page
13
+ alias :total_entries :pagination_record_count unless existing_methods.include_method? :total_entries
14
+
15
+ def out_of_bounds?
16
+ current_page > total_pages
17
+ end
18
+
19
+ # Current offset of the paginated collection
20
+ def offset
21
+ (current_page - 1) * per_page
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'will_paginate/core_ext'
2
+
3
+ module WillPaginate
4
+ # Database logic for different ORMs
5
+ #
6
+ # See WillPaginate::Finders::Base
7
+ module Finders
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ require 'will_paginate'
2
+ require 'will_paginate/collection'
3
+
4
+ module WillPaginate
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :will_paginate
7
+
8
+ initializer "will_paginate.active_record" do |app|
9
+ if defined? ::ActiveRecord
10
+ require 'will_paginate/finders/active_record'
11
+ WillPaginate::Finders::ActiveRecord.enable!
12
+ end
13
+ end
14
+
15
+ initializer "will_paginate.action_dispatch" do |app|
16
+ if defined? ::ActionDispatch::ShowExceptions
17
+ ActionDispatch::ShowExceptions.rescue_responses['WillPaginate::InvalidPage'] = :not_found
18
+ end
19
+ end
20
+
21
+ initializer "will_paginate.action_view" do |app|
22
+ require 'will_paginate/view_helpers/action_view'
23
+ ActionView::Base.send(:include, WillPaginate::ViewHelpers::ActionView)
24
+ end
25
+ end
26
+ end
@@ -1,8 +1,8 @@
1
- module WillPaginate
2
- module VERSION
3
- MAJOR = 2
4
- MINOR = 3
5
- TINY = 16
1
+ module WillPaginate #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 3
4
+ MINOR = 0
5
+ TINY = 'pre'
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -0,0 +1,142 @@
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).try(:html_safe)
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).try(:html_safe)
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
+ # :stopdoc:
92
+
93
+ WillPaginate::ViewHelpers::LinkRenderer.class_eval do
94
+ protected
95
+
96
+ def default_url_params
97
+ { :escape => false }
98
+ end
99
+
100
+ def url(page)
101
+ @base_url_params ||= begin
102
+ url_params = base_url_params
103
+ merge_optional_params(url_params)
104
+ url_params
105
+ end
106
+
107
+ url_params = @base_url_params.dup
108
+ add_current_page_param(url_params, page)
109
+
110
+ @template.url_for(url_params)
111
+ end
112
+
113
+ def base_url_params
114
+ url_params = default_url_params
115
+ # page links should preserve GET parameters
116
+ symbolized_update(url_params, @template.params) if get_request?
117
+ url_params
118
+ end
119
+
120
+ def merge_optional_params(url_params)
121
+ symbolized_update(url_params, @options[:params]) if @options[:params]
122
+ end
123
+
124
+ def add_current_page_param(url_params, page)
125
+ unless param_name.index(/[^\w-]/)
126
+ url_params[param_name.to_sym] = page
127
+ else
128
+ page_param = parse_query_parameters("#{param_name}=#{page}")
129
+ symbolized_update(url_params, page_param)
130
+ end
131
+ end
132
+
133
+ def get_request?
134
+ @template.request.get?
135
+ end
136
+
137
+ private
138
+
139
+ def parse_query_parameters(params)
140
+ Rack::Utils.parse_nested_query(params)
141
+ end
142
+ end