artemv-diff_to_html 1.0.2 → 1.0.3

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.
@@ -5,7 +5,7 @@ changeset viewer of a tool like Trac, GitHub or Codenotifier,
5
5
  e.g. http://codenotifier.com/projects/49/commits/47#F0.
6
6
 
7
7
  It's based on code from http://gurge.com/blog/2006/10/03/subversion-diff-viewer-cgi-in-ruby (thanks Adam
8
- Doppelt!), adopted lightly to support multifile diffs and have more familiar output. It definitely have
8
+ Doppelt!), adopted lightly to support multifile diffs and to have more familiar output. It definitely have
9
9
  things to improve, so contribution/patches are very welcome.
10
10
 
11
11
  * install the gem:
@@ -17,20 +17,17 @@ things to improve, so contribution/patches are very welcome.
17
17
  to copy it to your project's dir to use it.
18
18
  * To use in Rails project:
19
19
 
20
- require 'diff_to_html'
21
- ...
22
- converter = DiffToHtml.new
23
- ...
20
+ require 'diff_to_html'
21
+ diff = `cat #{File.join(File.dirname(__FILE__), 'diff.svn')}`
22
+ converter = SvnDiffToHtml.new #there's also GitDiffToHtml
23
+ puts converter.composite_to_html(diff)
24
24
 
25
25
  * to use in any Ruby program:
26
26
 
27
- require 'rubygems'
28
- gem 'artemv-diff_to_html'
29
- require 'diff_to_html'
30
- ...
31
-
32
- (just like in test.rb)
33
-
27
+ require 'rubygems'
28
+ require 'diff_to_html'
29
+ ...
30
+
34
31
  == License
35
32
 
36
33
  diff_to_html is released under the MIT license.
@@ -1,68 +1,75 @@
1
- ul.diff {
2
- padding:0;
3
- }
4
-
5
- .diff table col.lineno {
6
- width:4em;
7
- }
8
-
9
- .diff h2, .diff h2 a {
10
- color:#333333;
11
- font-size:14px;
12
- letter-spacing:normal;
13
- margin:0pt auto;
14
- }
15
-
16
- .diff h2 {
17
- padding:0.1em 0pt 0.25em 0.5em;
18
- }
19
-
20
- table.diff {
21
- font-size : 9pt;
22
- font-family : "lucida console", "courier new", monospace;
23
- white-space : pre;
24
- border : 1px solid #D7D7D7;
25
- border-collapse : collapse;
26
- line-height : 110%;
27
- width: 100%;
28
- }
29
-
30
- .diff tr {
31
- background: white;
32
- }
33
-
34
- .diff td {
35
- border : none;
36
- padding : 0px 10px;
37
- margin : 0px;
38
- }
39
-
40
- .diff td a {
41
- text-decoration: none;
42
- }
43
-
44
- tr.a { background : #ddffdd; }
45
-
46
- tr.r { background : #ffdddd; }
47
-
48
- tr.range { background : #EAF2F5; color : #999; }
49
-
50
- td.ln {
51
- background : #ECECEC;
52
- color : #aaa;
53
- border-top:1px solid #999988;
54
- border-bottom:1px solid #999988;
55
- border-right:1px solid #D7D7D7;
56
- }
57
-
58
- .diff li {
59
- background:#F7F7F7 none repeat scroll 0%;
60
- border:1px solid #D7D7D7;
61
- list-style-type:none;
62
- margin:0pt 0pt 2em;
63
- padding:2px;
64
- }
65
-
66
- .ln a {
67
- color: #aaa;
1
+ body, p, ol, ul, td {
2
+ font-family:verdana,arial,helvetica,sans-serif;
3
+ font-size:13px;
4
+ }
5
+
6
+ a, a:link, a:visited {
7
+ color:#507EC0;
8
+ text-decoration:none;
9
+ }
10
+
11
+ ul.diff {
12
+ padding:0;
13
+ }
14
+
15
+ .diff table col.lineno {
16
+ width:4em;
17
+ }
18
+
19
+ .diff h2 {
20
+ color:#333333;
21
+ font-size:14px;
22
+ letter-spacing:normal;
23
+ margin:0pt auto;
24
+ padding:0.1em 0pt 0.25em 0.5em;
25
+ }
26
+
27
+ table.diff {
28
+ font-size : 9pt;
29
+ font-family : "lucida console", "courier new", monospace;
30
+ white-space : pre;
31
+ border : 1px solid #D7D7D7;
32
+ border-collapse : collapse;
33
+ line-height : 110%;
34
+ width: 100%;
35
+ }
36
+
37
+ .diff tr {
38
+ background: white;
39
+ }
40
+
41
+ .diff td {
42
+ border : none;
43
+ padding : 0px 10px;
44
+ margin : 0px;
45
+ }
46
+
47
+ .diff td a {
48
+ text-decoration: none;
49
+ }
50
+
51
+ tr.a { background : #ddffdd; }
52
+
53
+ tr.r { background : #ffdddd; }
54
+
55
+ tr.range { background : #EAF2F5; color : #999; }
56
+
57
+ td.ln {
58
+ background : #ECECEC;
59
+ color : #aaa;
60
+ border-top:1px solid #999988;
61
+ border-bottom:1px solid #999988;
62
+ border-right:1px solid #D7D7D7;
63
+ }
64
+
65
+ .diff li {
66
+ background:#F7F7F7 none repeat scroll 0%;
67
+ border:1px solid #D7D7D7;
68
+ list-style-type:none;
69
+ margin:0pt 0pt 2em;
70
+ padding:2px;
71
+ }
72
+
73
+ .ln a {
74
+ color: #aaa;
68
75
  }
@@ -0,0 +1,3721 @@
1
+ diff --git a/.gitignore b/.gitignore
2
+ new file mode 100644
3
+ index 0000000..57f183a
4
+ --- /dev/null
5
+ +++ b/.gitignore
6
+ @@ -0,0 +1,4 @@
7
+ +/doc
8
+ +/rails
9
+ +*.gem
10
+ +/coverage
11
+ diff --git a/.manifest b/.manifest
12
+ new file mode 100644
13
+ index 0000000..be0c772
14
+ --- /dev/null
15
+ +++ b/.manifest
16
+ @@ -0,0 +1,49 @@
17
+ +CHANGELOG
18
+ +LICENSE
19
+ +README.rdoc
20
+ +Rakefile
21
+ +examples
22
+ +examples/apple-circle.gif
23
+ +examples/index.haml
24
+ +examples/index.html
25
+ +examples/pagination.css
26
+ +examples/pagination.sass
27
+ +init.rb
28
+ +lib
29
+ +lib/will_paginate
30
+ +lib/will_paginate.rb
31
+ +lib/will_paginate/array.rb
32
+ +lib/will_paginate/collection.rb
33
+ +lib/will_paginate/core_ext.rb
34
+ +lib/will_paginate/finder.rb
35
+ +lib/will_paginate/named_scope.rb
36
+ +lib/will_paginate/named_scope_patch.rb
37
+ +lib/will_paginate/version.rb
38
+ +lib/will_paginate/view_helpers.rb
39
+ +test
40
+ +test/boot.rb
41
+ +test/collection_test.rb
42
+ +test/console
43
+ +test/database.yml
44
+ +test/finder_test.rb
45
+ +test/fixtures
46
+ +test/fixtures/admin.rb
47
+ +test/fixtures/developer.rb
48
+ +test/fixtures/developers_projects.yml
49
+ +test/fixtures/project.rb
50
+ +test/fixtures/projects.yml
51
+ +test/fixtures/replies.yml
52
+ +test/fixtures/reply.rb
53
+ +test/fixtures/schema.rb
54
+ +test/fixtures/topic.rb
55
+ +test/fixtures/topics.yml
56
+ +test/fixtures/user.rb
57
+ +test/fixtures/users.yml
58
+ +test/helper.rb
59
+ +test/lib
60
+ +test/lib/activerecord_test_case.rb
61
+ +test/lib/activerecord_test_connector.rb
62
+ +test/lib/load_fixtures.rb
63
+ +test/lib/view_test_process.rb
64
+ +test/tasks.rake
65
+ +test/view_test.rb
66
+
67
+ diff --git a/CHANGELOG b/CHANGELOG
68
+ new file mode 100644
69
+ index 0000000..a75de89
70
+ --- /dev/null
71
+ +++ b/CHANGELOG
72
+ @@ -0,0 +1,92 @@
73
+ +== master
74
+ +
75
+ +* ActiveRecord 2.1: remove :include from count query when tables are not
76
+ + referenced in :conditions
77
+ +
78
+ +== 2.3.2, released 2008-05-16
79
+ +
80
+ +* Fixed LinkRenderer#stringified_merge by removing "return" from iterator block
81
+ +* Ensure that 'href' values in pagination links are escaped URLs
82
+ +
83
+ +== 2.3.1, released 2008-05-04
84
+ +
85
+ +* Fixed page numbers not showing with custom routes and implicit first page
86
+ +* Try to use Hanna for documentation (falls back to default RDoc template if not)
87
+ +
88
+ +== 2.3.0, released 2008-04-29
89
+ +
90
+ +* Changed LinkRenderer to receive collection, options and reference to view template NOT in
91
+ + constructor, but with the #prepare method. This is a step towards supporting passing of
92
+ + LinkRenderer (or subclass) instances that may be preconfigured in some way
93
+ +* LinkRenderer now has #page_link and #page_span methods for easier customization of output in
94
+ + subclasses
95
+ +* Changed page_entries_info() method to adjust its output according to humanized class name of
96
+ + collection items. Override this with :entry_name parameter (singular).
97
+ +
98
+ + page_entries_info(@posts)
99
+ + #-> "Displaying all 12 posts"
100
+ + page_entries_info(@posts, :entry_name => 'item')
101
+ + #-> "Displaying all 12 items"
102
+ +
103
+ +== 2.2.3, released 2008-04-26
104
+ +
105
+ +* will_paginate gem is no longer published on RubyForge, but on
106
+ + gems.github.com:
107
+ +
108
+ + gem sources -a http://gems.github.com/ (you only need to do this once)
109
+ + gem install mislav-will_paginate
110
+ +
111
+ +* extract reusable pagination testing stuff into WillPaginate::View
112
+ +* rethink the page URL construction mechanizm to be more bulletproof when
113
+ + combined with custom routing for page parameter
114
+ +* test that anchor parameter can be used in pagination links
115
+ +
116
+ +== 2.2.2, released 2008-04-21
117
+ +
118
+ +* Add support for page parameter in custom routes like "/foo/page/2"
119
+ +* Change output of "page_entries_info" on single-page collection and erraneous
120
+ + output with empty collection as reported by Tim Chater
121
+ +
122
+ +== 2.2.1, released 2008-04-08
123
+ +
124
+ +* take less risky path when monkeypatching named_scope; fix that it no longer
125
+ + requires ActiveRecord::VERSION
126
+ +* use strings in "respond_to?" calls to work around a bug in acts_as_ferret
127
+ + stable (ugh)
128
+ +* add rake release task
129
+ +
130
+ +
131
+ +== 2.2.0, released 2008-04-07
132
+ +
133
+ +=== API changes
134
+ +* Rename WillPaginate::Collection#page_count to "total_pages" for consistency.
135
+ + If you implemented this interface, change your implementation accordingly.
136
+ +* Remove old, deprecated style of calling Array#paginate as "paginate(page,
137
+ + per_page)". If you want to specify :page, :per_page or :total_entries, use a
138
+ + parameter hash.
139
+ +* Rename LinkRenderer#url_options to "url_for" and drastically optimize it
140
+ +
141
+ +=== View changes
142
+ +* Added "prev_page" and "next_page" CSS classes on previous/next page buttons
143
+ +* Add examples of pagination links styling in "examples/index.html"
144
+ +* Change gap in pagination links from "..." to
145
+ + "<span class="gap">&hellip;</span>".
146
+ +* Add "paginated_section", a block helper that renders pagination both above and
147
+ + below content in the block
148
+ +* Add rel="prev|next|start" to page links
149
+ +
150
+ +=== Other
151
+ +
152
+ +* Add ability to opt-in for Rails 2.1 feature "named_scope" by calling
153
+ + WillPaginate.enable_named_scope (tested in Rails 1.2.6 and 2.0.2)
154
+ +* Support complex page parameters like "developers[page]"
155
+ +* Move Array#paginate definition to will_paginate/array.rb. You can now easily
156
+ + use pagination on arrays outside of Rails:
157
+ +
158
+ + gem 'will_paginate'
159
+ + require 'will_paginate/array'
160
+ +
161
+ +* Add "paginated_each" method for iterating through every record by loading only
162
+ + one page of records at the time
163
+ +* Rails 2: Rescue from WillPaginate::InvalidPage error with 404 Not Found by
164
+ + default
165
+ diff --git a/LICENSE b/LICENSE
166
+ new file mode 100644
167
+ index 0000000..96a48cb
168
+ --- /dev/null
169
+ +++ b/LICENSE
170
+ @@ -0,0 +1,18 @@
171
+ +Copyright (c) 2007 PJ Hyett and Mislav Marohnić
172
+ +
173
+ +Permission is hereby granted, free of charge, to any person obtaining a copy of
174
+ +this software and associated documentation files (the "Software"), to deal in
175
+ +the Software without restriction, including without limitation the rights to
176
+ +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
177
+ +the Software, and to permit persons to whom the Software is furnished to do so,
178
+ +subject to the following conditions:
179
+ +
180
+ +The above copyright notice and this permission notice shall be included in all
181
+ +copies or substantial portions of the Software.
182
+ +
183
+ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
184
+ +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
185
+ +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
186
+ +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
187
+ +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
188
+ +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
189
+ diff --git a/README b/README
190
+ deleted file mode 100644
191
+ index 3594e49..0000000
192
+ --- a/README
193
+ +++ /dev/null
194
+ @@ -1,46 +0,0 @@
195
+ -CanPaginate
196
+ -
197
+ -Ruby port by: PJ Hyett
198
+ -Original php source: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php
199
+ -
200
+ -Example usage:
201
+ -
202
+ -app/controllers/posts_controller.rb
203
+ -
204
+ - @page = params[:page].to_i.zero? ? 1 : params[:page].to_i
205
+ -
206
+ -app/views/posts/index.rhtml
207
+ -
208
+ - <%= will_paginate(Post.count, Post::PER_PAGE) %>
209
+ -
210
+ -Copy the following css into your stylesheet for a good start:
211
+ -
212
+ -.pagination {
213
+ - padding: 3px;
214
+ - margin: 3px;
215
+ -}
216
+ -.pagination a {
217
+ - padding: 2px 5px 2px 5px;
218
+ - margin: 2px;
219
+ - border: 1px solid #aaaadd;
220
+ - text-decoration: none;
221
+ - color: #000099;
222
+ -}
223
+ -.pagination a:hover, div.pagination a:active {
224
+ - border: 1px solid #000099;
225
+ - color: #000;
226
+ -}
227
+ -.pagination span.current {
228
+ - padding: 2px 5px 2px 5px;
229
+ - margin: 2px;
230
+ - border: 1px solid #000099;
231
+ - font-weight: bold;
232
+ - background-color: #000099;
233
+ - color: #FFF;
234
+ -}
235
+ -.pagination span.disabled {
236
+ - padding: 2px 5px 2px 5px;
237
+ - margin: 2px;
238
+ - border: 1px solid #eee;
239
+ - color: #ddd;
240
+ -}
241
+ diff --git a/README.rdoc b/README.rdoc
242
+ new file mode 100644
243
+ index 0000000..fbce96f
244
+ --- /dev/null
245
+ +++ b/README.rdoc
246
+ @@ -0,0 +1,131 @@
247
+ += WillPaginate
248
+ +
249
+ +Pagination is just limiting the number of records displayed. Why should you let
250
+ +it get in your way while developing, then? This plugin makes magic happen. Did
251
+ +you ever want to be able to do just this on a model:
252
+ +
253
+ + Post.paginate :page => 1, :order => 'created_at DESC'
254
+ +
255
+ +... and then render the page links with a single view helper? Well, now you
256
+ +can.
257
+ +
258
+ +Some resources to get you started:
259
+ +
260
+ +* Your mind reels with questions? Join our
261
+ + {Google group}[http://groups.google.com/group/will_paginate].
262
+ +* The will_paginate project page: http://github.com/mislav/will_paginate
263
+ +* How to report bugs: http://github.com/mislav/will_paginate/wikis/report-bugs
264
+ +* Ryan Bates made an awesome screencast[http://railscasts.com/episodes/51],
265
+ + check it out.
266
+ +
267
+ +== Installation
268
+ +
269
+ +The recommended way is that you get the gem:
270
+ +
271
+ + gem install mislav-will_paginate --source http://gems.github.com/
272
+ +
273
+ +After that you don't need the will_paginate <i>plugin</i> in your Rails
274
+ +application anymore. Just add a simple require to the end of
275
+ +"config/environment.rb":
276
+ +
277
+ + gem 'mislav-will_paginate', '~> 2.2'
278
+ + require 'will_paginate'
279
+ +
280
+ +That's it. Remember to install the gem on <b>all</b> machines that you are
281
+ +deploying to.
282
+ +
283
+ +<i>There are extensive
284
+ +{installation instructions}[http://github.com/mislav/will_paginate/wikis/installation]
285
+ +on {the wiki}[http://github.com/mislav/will_paginate/wikis].</i>
286
+ +
287
+ +
288
+ +== Example usage
289
+ +
290
+ +Use a paginate finder in the controller:
291
+ +
292
+ + @posts = Post.paginate_by_board_id @board.id, :page => params[:page], :order => 'updated_at DESC'
293
+ +
294
+ +Yeah, +paginate+ works just like +find+ -- it just doesn't fetch all the
295
+ +records. Don't forget to tell it which page you want, or it will complain!
296
+ +Read more on WillPaginate::Finder::ClassMethods.
297
+ +
298
+ +Render the posts in your view like you would normally do. When you need to render
299
+ +pagination, just stick this in:
300
+ +
301
+ + <%= will_paginate @posts %>
302
+ +
303
+ +You're done. (Copy and paste the example fancy CSS styles from the bottom.) You
304
+ +can find the option list at WillPaginate::ViewHelpers.
305
+ +
306
+ +How does it know how much items to fetch per page? It asks your model by calling
307
+ +its <tt>per_page</tt> class method. You can define it like this:
308
+ +
309
+ + class Post < ActiveRecord::Base
310
+ + cattr_reader :per_page
311
+ + @@per_page = 50
312
+ + end
313
+ +
314
+ +... or like this:
315
+ +
316
+ + class Post < ActiveRecord::Base
317
+ + def self.per_page
318
+ + 50
319
+ + end
320
+ + end
321
+ +
322
+ +... or don't worry about it at all. WillPaginate defines it to be <b>30</b> by default.
323
+ +But you can always specify the count explicitly when calling +paginate+:
324
+ +
325
+ + @posts = Post.paginate :page => params[:page], :per_page => 50
326
+ +
327
+ +The +paginate+ finder wraps the original finder and returns your resultset that now has
328
+ +some new properties. You can use the collection as you would with any ActiveRecord
329
+ +resultset. WillPaginate view helpers also need that object to be able to render pagination:
330
+ +
331
+ + <ol>
332
+ + <% for post in @posts -%>
333
+ + <li>Render `post` in some nice way.</li>
334
+ + <% end -%>
335
+ + </ol>
336
+ +
337
+ + <p>Now let's render us some pagination!</p>
338
+ + <%= will_paginate @posts %>
339
+ +
340
+ +More detailed documentation:
341
+ +
342
+ +* WillPaginate::Finder::ClassMethods for pagination on your models;
343
+ +* WillPaginate::ViewHelpers for your views.
344
+ +
345
+ +
346
+ +== Authors and credits
347
+ +
348
+ +Authors:: Mislav Marohni-�, PJ Hyett
349
+ +Original announcement:: http://errtheblog.com/post/929
350
+ +Original PHP source:: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php
351
+ +
352
+ +All these people helped making will_paginate what it is now with their code
353
+ +contributions or just simply awesome ideas:
354
+ +
355
+ +Chris Wanstrath, Dr. Nic Williams, K. Adam Christensen, Mike Garey, Bence
356
+ +Golda, Matt Aimonetti, Charles Brian Quinn, Desi McAdam, James Coglan, Matijs
357
+ +van Zuijlen, Maria, Brendan Ribera, Todd Willey, Bryan Helmkamp, Jan Berkel,
358
+ +Lourens Naud+�, Rick Olson, Russell Norris, Piotr Usewicz, Chris Eppstein.
359
+ +
360
+ +
361
+ +== Usable pagination in the UI
362
+ +
363
+ +There are some CSS styles to get you started in the "examples/" directory. They
364
+ +are showcased in the <b>"examples/index.html"</b> file.
365
+ +
366
+ +More reading about pagination as design pattern:
367
+ +
368
+ +* Pagination 101:
369
+ + http://kurafire.net/log/archive/2007/06/22/pagination-101
370
+ +* Pagination gallery:
371
+ + http://www.smashingmagazine.com/2007/11/16/pagination-gallery-examples-and-good-practices/
372
+ +* Pagination on Yahoo Design Pattern Library:
373
+ + http://developer.yahoo.com/ypatterns/parent.php?pattern=pagination
374
+ +
375
+ +Want to discuss, request features, ask questions? Join the
376
+ +{Google group}[http://groups.google.com/group/will_paginate].
377
+ +
378
+ diff --git a/Rakefile b/Rakefile
379
+ new file mode 100644
380
+ index 0000000..cb68107
381
+ --- /dev/null
382
+ +++ b/Rakefile
383
+ @@ -0,0 +1,62 @@
384
+ +require 'rubygems'
385
+ +begin
386
+ + hanna_dir = '/home/mislav/projects/hanna/lib'
387
+ + $:.unshift hanna_dir if File.exists? hanna_dir
388
+ + require 'hanna/rdoctask'
389
+ +rescue LoadError
390
+ + require 'rake'
391
+ + require 'rake/rdoctask'
392
+ +end
393
+ +load 'test/tasks.rake'
394
+ +
395
+ +desc 'Default: run unit tests.'
396
+ +task :default => :test
397
+ +
398
+ +desc 'Generate RDoc documentation for the will_paginate plugin.'
399
+ +Rake::RDocTask.new(:rdoc) do |rdoc|
400
+ + rdoc.rdoc_files.include('README.rdoc', 'LICENSE', 'CHANGELOG').
401
+ + include('lib/**/*.rb').
402
+ + exclude('lib/will_paginate/named_scope*').
403
+ + exclude('lib/will_paginate/array.rb').
404
+ + exclude('lib/will_paginate/version.rb')
405
+ +
406
+ + rdoc.main = "README.rdoc" # page to start on
407
+ + rdoc.title = "will_paginate documentation"
408
+ +
409
+ + rdoc.rdoc_dir = 'doc' # rdoc output folder
410
+ + rdoc.options << '--inline-source' << '--charset=UTF-8'
411
+ + rdoc.options << '--webcvs=http://github.com/mislav/will_paginate/tree/master/'
412
+ +end
413
+ +
414
+ +desc %{Update ".manifest" with the latest list of project filenames. Respect\
415
+ +.gitignore by excluding everything that git ignores. Update `files` and\
416
+ +`test_files` arrays in "*.gemspec" file if it's present.}
417
+ +task :manifest do
418
+ + list = Dir['**/*'].sort
419
+ + spec_file = Dir['*.gemspec'].first
420
+ + list -= [spec_file] if spec_file
421
+ +
422
+ + File.read('.gitignore').each_line do |glob|
423
+ + glob = glob.chomp.sub(/^\//, '')
424
+ + list -= Dir[glob]
425
+ + list -= Dir["#{glob}/**/*"] if File.directory?(glob) and !File.symlink?(glob)
426
+ + puts "excluding #{glob}"
427
+ + end
428
+ +
429
+ + if spec_file
430
+ + spec = File.read spec_file
431
+ + spec.gsub! /^(\s* s.(test_)?files \s* = \s* )( \[ [^\]]* \] | %w\( [^)]* \) )/mx do
432
+ + assignment = $1
433
+ + bunch = $2 ? list.grep(/^test\//) : list
434
+ + '%s%%w(%s)' % [assignment, bunch.join(' ')]
435
+ + end
436
+ +
437
+ + File.open(spec_file, 'w') {|f| f << spec }
438
+ + end
439
+ + File.open('.manifest', 'w') {|f| f << list.join("\n") }
440
+ +end
441
+ +
442
+ +task :examples do
443
+ + %x(haml examples/index.haml examples/index.html)
444
+ + %x(sass examples/pagination.sass examples/pagination.css)
445
+ +end
446
+ diff --git a/examples/apple-circle.gif b/examples/apple-circle.gif
447
+ new file mode 100644
448
+ index 0000000..df8cbf7
449
+ Binary files /dev/null and b/examples/apple-circle.gif differ
450
+ diff --git a/examples/index.haml b/examples/index.haml
451
+ new file mode 100644
452
+ index 0000000..e267dd8
453
+ --- /dev/null
454
+ +++ b/examples/index.haml
455
+ @@ -0,0 +1,69 @@
456
+ +!!!
457
+ +%html
458
+ +%head
459
+ + %title Samples of pagination styling for will_paginate
460
+ + %link{ :rel => 'stylesheet', :type => 'text/css', :href => 'pagination.css' }
461
+ + %style{ :type => 'text/css' }
462
+ + :sass
463
+ + html
464
+ + :margin 0
465
+ + :padding 0
466
+ + :background #999
467
+ + :font normal 76% "Lucida Grande", Verdana, Helvetica, sans-serif
468
+ + body
469
+ + :margin 2em
470
+ + :padding 2em
471
+ + :border 2px solid gray
472
+ + :background white
473
+ + :color #222
474
+ + h1
475
+ + :font-size 2em
476
+ + :font-weight normal
477
+ + :margin 0 0 1em 0
478
+ + h2
479
+ + :font-size 1.4em
480
+ + :margin 1em 0 .5em 0
481
+ + pre
482
+ + :font-size 13px
483
+ + :font-family Monaco, "DejaVu Sans Mono", "Bitstream Vera Mono", "Courier New", monospace
484
+ +
485
+ +- pagination = '<span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>'
486
+ +- pagination_no_page_links = '<span class="disabled prev_page">&laquo; Previous</span> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>'
487
+ +
488
+ +%body
489
+ + %h1 Samples of pagination styling for will_paginate
490
+ + %p
491
+ + Find these styles in <b>"examples/pagination.css"</b> of <i>will_paginate</i> library.
492
+ + There is a Sass version of it for all you sassy people.
493
+ + %p
494
+ + Read about good rules for pagination:
495
+ + %a{ :href => 'http://kurafire.net/log/archive/2007/06/22/pagination-101' } Pagination 101
496
+ + %p
497
+ + %em Warning:
498
+ + page links below don't lead anywhere (so don't click on them).
499
+ +
500
+ + %h2 Unstyled pagination <span style="font-weight:normal">(<i>ewww!</i>)</span>
501
+ + %div= pagination
502
+ +
503
+ + %h2 Digg.com
504
+ + .digg_pagination= pagination
505
+ +
506
+ + %h2 Digg-style, no page links
507
+ + .digg_pagination= pagination_no_page_links
508
+ + %p Code that renders this:
509
+ + %pre= '<code>%s</code>' % %[<%= will_paginate @posts, :page_links => false %>].gsub('<', '&lt;').gsub('>', '&gt;')
510
+ +
511
+ + %h2 Digg-style, extra content
512
+ + .digg_pagination
513
+ + .page_info Displaying entries <b>1&nbsp;-&nbsp;6</b> of <b>180</b> in total
514
+ + = pagination
515
+ + %p Code that renders this:
516
+ + %pre= '<code>%s</code>' % %[<div class="digg_pagination">\n <div clas="page_info">\n <%= page_entries_info @posts %>\n </div>\n <%= will_paginate @posts, :container => false %>\n</div>].gsub('<', '&lt;').gsub('>', '&gt;')
517
+ +
518
+ + %h2 Apple.com store
519
+ + .apple_pagination= pagination
520
+ +
521
+ + %h2 Flickr.com
522
+ + .flickr_pagination
523
+ + = pagination
524
+ + .page_info (118 photos)
525
+ diff --git a/examples/index.html b/examples/index.html
526
+ new file mode 100644
527
+ index 0000000..6033abf
528
+ --- /dev/null
529
+ +++ b/examples/index.html
530
+ @@ -0,0 +1,92 @@
531
+ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
532
+ +<html>
533
+ +</html>
534
+ +<head>
535
+ + <title>Samples of pagination styling for will_paginate</title>
536
+ + <link href='pagination.css' rel='stylesheet' type='text/css' />
537
+ + <style type='text/css'>
538
+ + html {
539
+ + margin: 0;
540
+ + padding: 0;
541
+ + background: #999;
542
+ + font: normal 76% "Lucida Grande", Verdana, Helvetica, sans-serif; }
543
+ +
544
+ + body {
545
+ + margin: 2em;
546
+ + padding: 2em;
547
+ + border: 2px solid gray;
548
+ + background: white;
549
+ + color: #222; }
550
+ +
551
+ + h1 {
552
+ + font-size: 2em;
553
+ + font-weight: normal;
554
+ + margin: 0 0 1em 0; }
555
+ +
556
+ + h2 {
557
+ + font-size: 1.4em;
558
+ + margin: 1em 0 .5em 0; }
559
+ +
560
+ + pre {
561
+ + font-size: 13px;
562
+ + font-family: Monaco, "DejaVu Sans Mono", "Bitstream Vera Mono", "Courier New", monospace; }
563
+ + </style>
564
+ +</head>
565
+ +<body>
566
+ + <h1>Samples of pagination styling for will_paginate</h1>
567
+ + <p>
568
+ + Find these styles in <b>"examples/pagination.css"</b> of <i>will_paginate</i> library.
569
+ + There is a Sass version of it for all you sassy people.
570
+ + </p>
571
+ + <p>
572
+ + Read about good rules for pagination:
573
+ + <a href='http://kurafire.net/log/archive/2007/06/22/pagination-101'>Pagination 101</a>
574
+ + </p>
575
+ + <p>
576
+ + <em>Warning:</em>
577
+ + page links below don't lead anywhere (so don't click on them).
578
+ + </p>
579
+ + <h2>
580
+ + Unstyled pagination <span style="font-weight:normal">(<i>ewww!</i>)</span>
581
+ + </h2>
582
+ + <div>
583
+ + <span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
584
+ + </div>
585
+ + <h2>Digg.com</h2>
586
+ + <div class='digg_pagination'>
587
+ + <span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
588
+ + </div>
589
+ + <h2>Digg-style, no page links</h2>
590
+ + <div class='digg_pagination'>
591
+ + <span class="disabled prev_page">&laquo; Previous</span> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
592
+ + </div>
593
+ + <p>Code that renders this:</p>
594
+ + <pre>
595
+ + <code>&lt;%= will_paginate @posts, :page_links =&gt; false %&gt;</code>
596
+ + </pre>
597
+ + <h2>Digg-style, extra content</h2>
598
+ + <div class='digg_pagination'>
599
+ + <div class='page_info'>
600
+ + Displaying entries <b>1&nbsp;-&nbsp;6</b> of <b>180</b> in total
601
+ + </div>
602
+ + <span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
603
+ + </div>
604
+ + <p>Code that renders this:</p>
605
+ + <pre>
606
+ + <code>&lt;div class="digg_pagination"&gt;
607
+ + &lt;div clas="page_info"&gt;
608
+ + &lt;%= page_entries_info @posts %&gt;
609
+ + &lt;/div&gt;
610
+ + &lt;%= will_paginate @posts, :container =&gt; false %&gt;
611
+ + &lt;/div&gt;</code>
612
+ + </pre>
613
+ + <h2>Apple.com store</h2>
614
+ + <div class='apple_pagination'>
615
+ + <span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
616
+ + </div>
617
+ + <h2>Flickr.com</h2>
618
+ + <div class='flickr_pagination'>
619
+ + <span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
620
+ + <div class='page_info'>(118 photos)</div>
621
+ + </div>
622
+ +</body>
623
+ diff --git a/examples/pagination.css b/examples/pagination.css
624
+ new file mode 100644
625
+ index 0000000..e6f0ff6
626
+ --- /dev/null
627
+ +++ b/examples/pagination.css
628
+ @@ -0,0 +1,90 @@
629
+ +.digg_pagination {
630
+ + background: white;
631
+ + /* self-clearing method: */ }
632
+ + .digg_pagination a, .digg_pagination span {
633
+ + padding: .2em .5em;
634
+ + display: block;
635
+ + float: left;
636
+ + margin-right: 1px; }
637
+ + .digg_pagination span.disabled {
638
+ + color: #999;
639
+ + border: 1px solid #DDD; }
640
+ + .digg_pagination span.current {
641
+ + font-weight: bold;
642
+ + background: #2E6AB1;
643
+ + color: white;
644
+ + border: 1px solid #2E6AB1; }
645
+ + .digg_pagination a {
646
+ + text-decoration: none;
647
+ + color: #105CB6;
648
+ + border: 1px solid #9AAFE5; }
649
+ + .digg_pagination a:hover, .digg_pagination a:focus {
650
+ + color: #003;
651
+ + border-color: #003; }
652
+ + .digg_pagination .page_info {
653
+ + background: #2E6AB1;
654
+ + color: white;
655
+ + padding: .4em .6em;
656
+ + width: 22em;
657
+ + margin-bottom: .3em;
658
+ + text-align: center; }
659
+ + .digg_pagination .page_info b {
660
+ + color: #003;
661
+ + background: #6aa6ed;
662
+ + padding: .1em .25em; }
663
+ + .digg_pagination:after {
664
+ + content: ".";
665
+ + display: block;
666
+ + height: 0;
667
+ + clear: both;
668
+ + visibility: hidden; }
669
+ + * html .digg_pagination {
670
+ + height: 1%; }
671
+ + *:first-child+html .digg_pagination {
672
+ + overflow: hidden; }
673
+ +
674
+ +.apple_pagination {
675
+ + background: #F1F1F1;
676
+ + border: 1px solid #E5E5E5;
677
+ + text-align: center;
678
+ + padding: 1em; }
679
+ + .apple_pagination a, .apple_pagination span {
680
+ + padding: .2em .3em; }
681
+ + .apple_pagination span.disabled {
682
+ + color: #AAA; }
683
+ + .apple_pagination span.current {
684
+ + font-weight: bold;
685
+ + background: transparent url(apple-circle.gif) no-repeat 50% 50%; }
686
+ + .apple_pagination a {
687
+ + text-decoration: none;
688
+ + color: black; }
689
+ + .apple_pagination a:hover, .apple_pagination a:focus {
690
+ + text-decoration: underline; }
691
+ +
692
+ +.flickr_pagination {
693
+ + text-align: center;
694
+ + padding: .3em; }
695
+ + .flickr_pagination a, .flickr_pagination span {
696
+ + padding: .2em .5em; }
697
+ + .flickr_pagination span.disabled {
698
+ + color: #AAA; }
699
+ + .flickr_pagination span.current {
700
+ + font-weight: bold;
701
+ + color: #FF0084; }
702
+ + .flickr_pagination a {
703
+ + border: 1px solid #DDDDDD;
704
+ + color: #0063DC;
705
+ + text-decoration: none; }
706
+ + .flickr_pagination a:hover, .flickr_pagination a:focus {
707
+ + border-color: #003366;
708
+ + background: #0063DC;
709
+ + color: white; }
710
+ + .flickr_pagination .page_info {
711
+ + color: #aaa;
712
+ + padding-top: .8em; }
713
+ + .flickr_pagination .prev_page, .flickr_pagination .next_page {
714
+ + border-width: 2px; }
715
+ + .flickr_pagination .prev_page {
716
+ + margin-right: 1em; }
717
+ + .flickr_pagination .next_page {
718
+ + margin-left: 1em; }
719
+ diff --git a/examples/pagination.sass b/examples/pagination.sass
720
+ new file mode 100644
721
+ index 0000000..a623e29
722
+ --- /dev/null
723
+ +++ b/examples/pagination.sass
724
+ @@ -0,0 +1,91 @@
725
+ +.digg_pagination
726
+ + :background white
727
+ + a, span
728
+ + :padding .2em .5em
729
+ + :display block
730
+ + :float left
731
+ + :margin-right 1px
732
+ + span.disabled
733
+ + :color #999
734
+ + :border 1px solid #DDD
735
+ + span.current
736
+ + :font-weight bold
737
+ + :background #2E6AB1
738
+ + :color white
739
+ + :border 1px solid #2E6AB1
740
+ + a
741
+ + :text-decoration none
742
+ + :color #105CB6
743
+ + :border 1px solid #9AAFE5
744
+ + &:hover, &:focus
745
+ + :color #003
746
+ + :border-color #003
747
+ + .page_info
748
+ + :background #2E6AB1
749
+ + :color white
750
+ + :padding .4em .6em
751
+ + :width 22em
752
+ + :margin-bottom .3em
753
+ + :text-align center
754
+ + b
755
+ + :color #003
756
+ + :background = #2E6AB1 + 60
757
+ + :padding .1em .25em
758
+ +
759
+ + /* self-clearing method:
760
+ + &:after
761
+ + :content "."
762
+ + :display block
763
+ + :height 0
764
+ + :clear both
765
+ + :visibility hidden
766
+ + * html &
767
+ + :height 1%
768
+ + *:first-child+html &
769
+ + :overflow hidden
770
+ +
771
+ +.apple_pagination
772
+ + :background #F1F1F1
773
+ + :border 1px solid #E5E5E5
774
+ + :text-align center
775
+ + :padding 1em
776
+ + a, span
777
+ + :padding .2em .3em
778
+ + span.disabled
779
+ + :color #AAA
780
+ + span.current
781
+ + :font-weight bold
782
+ + :background transparent url(apple-circle.gif) no-repeat 50% 50%
783
+ + a
784
+ + :text-decoration none
785
+ + :color black
786
+ + &:hover, &:focus
787
+ + :text-decoration underline
788
+ +
789
+ +.flickr_pagination
790
+ + :text-align center
791
+ + :padding .3em
792
+ + a, span
793
+ + :padding .2em .5em
794
+ + span.disabled
795
+ + :color #AAA
796
+ + span.current
797
+ + :font-weight bold
798
+ + :color #FF0084
799
+ + a
800
+ + :border 1px solid #DDDDDD
801
+ + :color #0063DC
802
+ + :text-decoration none
803
+ + &:hover, &:focus
804
+ + :border-color #003366
805
+ + :background #0063DC
806
+ + :color white
807
+ + .page_info
808
+ + :color #aaa
809
+ + :padding-top .8em
810
+ + .prev_page, .next_page
811
+ + :border-width 2px
812
+ + .prev_page
813
+ + :margin-right 1em
814
+ + .next_page
815
+ + :margin-left 1em
816
+ diff --git a/init.rb b/init.rb
817
+ index 15f7499..6d93aab 100644
818
+ --- a/init.rb
819
+ +++ b/init.rb
820
+ @@ -1,2 +1 @@
821
+ -require 'will_paginate'
822
+ -ActionView::Base.send(:include, WillPaginate)
823
+ +require 'will_paginate'
824
+ diff --git a/lib/will_paginate.rb b/lib/will_paginate.rb
825
+ index 8139aab..c27c87b 100644
826
+ --- a/lib/will_paginate.rb
827
+ +++ b/lib/will_paginate.rb
828
+ @@ -1,43 +1,86 @@
829
+ -module WillPaginate
830
+ - def will_paginate(total_count, per_page, page = @page)
831
+ - adjacents = 2
832
+ - prev_page = page - 1
833
+ - next_page = page + 1
834
+ - last_page = (total_count / per_page.to_f).ceil
835
+ - lpm1 = last_page - 1
836
+ -
837
+ - returning '' do |pgn|
838
+ - if last_page > 1
839
+ - pgn << %{<div class="pagination">}
840
+ -
841
+ - # not enough pages to bother breaking
842
+ - if last_page < 7 + (adjacents * 2)
843
+ - 1.upto(last_page) { |ctr| pgn << (ctr == page ? content_tag(:span, ctr, :class => 'current') : link_to(ctr, :page => ctr)) }
844
+ -
845
+ - # enough pages to hide some
846
+ - elsif last_page > 5 + (adjacents * 2)
847
+ -
848
+ - # close to beginning, only hide later pages
849
+ - if page < 1 + (adjacents * 2)
850
+ - 1.upto(3 + (adjacents * 2)) { |ctr| pgn << (ctr == page ? content_tag(:span, ctr, :class => 'current') : link_to(ctr, :page => ctr)) }
851
+ - pgn << "..." + link_to(lpm1, :page => lpm1) + link_to(last_page, :page => last_page)
852
+ -
853
+ - # in middle, hide some from both sides
854
+ - elsif last_page - (adjacents * 2) > page && page > (adjacents * 2)
855
+ - pgn << link_to('1', :page => 1) + link_to('2', :page => 2) + "..."
856
+ - (page - adjacents).upto(page + adjacents) { |ctr| pgn << (ctr == page ? content_tag(:span, ctr, :class => 'current') : link_to(ctr, :page => ctr)) }
857
+ - pgn << "..." + link_to(lpm1, :page => lpm1) + link_to(last_page, :page => last_page)
858
+ -
859
+ - # close to end, only hide early pages
860
+ - else
861
+ - pgn << link_to('1', :page => 1) + link_to('2', :page => 2) + "..."
862
+ - (last_page - (2 + (adjacents * 2))).upto(last_page) { |ctr| pgn << (ctr == page ? content_tag(:span, ctr, :class => 'current') : link_to(ctr, :page => ctr)) }
863
+ - end
864
+ - end
865
+ - pgn << (page > 1 ? link_to("&laquo; Previous", :page => prev_page) : content_tag(:span, "&laquo; Previous", :class => 'disabled'))
866
+ - pgn << (page < last_page ? link_to("Next &raquo;", :page => next_page) : content_tag(:span, "Next &raquo;", :class => 'disabled'))
867
+ - pgn << '</div>'
868
+ - end
869
+ - end
870
+ - end
871
+ -end
872
+ +require 'active_support'
873
+ +
874
+ +# = You *will* paginate!
875
+ +#
876
+ +# First read about WillPaginate::Finder::ClassMethods, then see
877
+ +# WillPaginate::ViewHelpers. The magical array you're handling in-between is
878
+ +# WillPaginate::Collection.
879
+ +#
880
+ +# Happy paginating!
881
+ +module WillPaginate
882
+ + class << self
883
+ + # shortcut for <tt>enable_actionpack; enable_activerecord</tt>
884
+ + def enable
885
+ + enable_actionpack
886
+ + enable_activerecord
887
+ + end
888
+ +
889
+ + # mixes in WillPaginate::ViewHelpers in ActionView::Base
890
+ + def enable_actionpack
891
+ + return if ActionView::Base.instance_methods.include? 'will_paginate'
892
+ + require 'will_paginate/view_helpers'
893
+ + ActionView::Base.class_eval { include ViewHelpers }
894
+ +
895
+ + if defined?(ActionController::Base) and ActionController::Base.respond_to? :rescue_responses
896
+ + ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found
897
+ + end
898
+ + end
899
+ +
900
+ + # mixes in WillPaginate::Finder in ActiveRecord::Base and classes that deal
901
+ + # with associations
902
+ + def enable_activerecord
903
+ + return if ActiveRecord::Base.respond_to? :paginate
904
+ + require 'will_paginate/finder'
905
+ + ActiveRecord::Base.class_eval { include Finder }
906
+ +
907
+ + # support pagination on associations
908
+ + a = ActiveRecord::Associations
909
+ + returning([ a::AssociationCollection ]) { |classes|
910
+ + # detect http://dev.rubyonrails.org/changeset/9230
911
+ + unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation
912
+ + classes << a::HasManyThroughAssociation
913
+ + end
914
+ + }.each do |klass|
915
+ + klass.class_eval do
916
+ + include Finder::ClassMethods
917
+ + alias_method_chain :method_missing, :paginate
918
+ + end
919
+ + end
920
+ + end
921
+ +
922
+ + # Enable named_scope, a feature of Rails 2.1, even if you have older Rails
923
+ + # (tested on Rails 2.0.2 and 1.2.6).
924
+ + #
925
+ + # You can pass +false+ for +patch+ parameter to skip monkeypatching
926
+ + # *associations*. Use this if you feel that <tt>named_scope</tt> broke
927
+ + # has_many, has_many :through or has_and_belongs_to_many associations in
928
+ + # your app. By passing +false+, you can still use <tt>named_scope</tt> in
929
+ + # your models, but not through associations.
930
+ + def enable_named_scope(patch = true)
931
+ + return if defined? ActiveRecord::NamedScope
932
+ + require 'will_paginate/named_scope'
933
+ + require 'will_paginate/named_scope_patch' if patch
934
+ +
935
+ + ActiveRecord::Base.class_eval do
936
+ + include WillPaginate::NamedScope
937
+ + end
938
+ + end
939
+ + end
940
+ +
941
+ + module Deprecation #:nodoc:
942
+ + extend ActiveSupport::Deprecation
943
+ +
944
+ + def self.warn(message, callstack = caller)
945
+ + message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ')
946
+ + behavior.call(message, callstack) if behavior && !silenced?
947
+ + end
948
+ +
949
+ + def self.silenced?
950
+ + ActiveSupport::Deprecation.silenced?
951
+ + end
952
+ + end
953
+ +end
954
+ +
955
+ +if defined?(Rails) and defined?(ActiveRecord) and defined?(ActionController)
956
+ + WillPaginate.enable
957
+ +end
958
+ diff --git a/lib/will_paginate/array.rb b/lib/will_paginate/array.rb
959
+ new file mode 100644
960
+ index 0000000..e92e149
961
+ --- /dev/null
962
+ +++ b/lib/will_paginate/array.rb
963
+ @@ -0,0 +1,16 @@
964
+ +require 'will_paginate/collection'
965
+ +
966
+ +# http://www.desimcadam.com/archives/8
967
+ +Array.class_eval do
968
+ + def paginate(options = {})
969
+ + raise ArgumentError, "parameter hash expected (got #{options.inspect})" unless Hash === options
970
+ +
971
+ + WillPaginate::Collection.create(
972
+ + options[:page] || 1,
973
+ + options[:per_page] || 30,
974
+ + options[:total_entries] || self.length
975
+ + ) { |pager|
976
+ + pager.replace self[pager.offset, pager.per_page].to_a
977
+ + }
978
+ + end
979
+ +end
980
+ diff --git a/lib/will_paginate/collection.rb b/lib/will_paginate/collection.rb
981
+ new file mode 100644
982
+ index 0000000..bc2b73c
983
+ --- /dev/null
984
+ +++ b/lib/will_paginate/collection.rb
985
+ @@ -0,0 +1,145 @@
986
+ +module WillPaginate
987
+ + # = Invalid page number error
988
+ + # This is an ArgumentError raised in case a page was requested that is either
989
+ + # zero or negative number. You should decide how do deal with such errors in
990
+ + # the controller.
991
+ + #
992
+ + # If you're using Rails 2, then this error will automatically get handled like
993
+ + # 404 Not Found. The hook is in "will_paginate.rb":
994
+ + #
995
+ + # ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found
996
+ + #
997
+ + # If you don't like this, use your preffered method of rescuing exceptions in
998
+ + # public from your controllers to handle this differently. The +rescue_from+
999
+ + # method is a nice addition to Rails 2.
1000
+ + #
1001
+ + # This error is *not* raised when a page further than the last page is
1002
+ + # requested. Use <tt>WillPaginate::Collection#out_of_bounds?</tt> method to
1003
+ + # check for those cases and manually deal with them as you see fit.
1004
+ + class InvalidPage < ArgumentError
1005
+ + def initialize(page, page_num)
1006
+ + super "#{page.inspect} given as value, which translates to '#{page_num}' as page number"
1007
+ + end
1008
+ + end
1009
+ +
1010
+ + # = The key to pagination
1011
+ + # Arrays returned from paginating finds are, in fact, instances of this little
1012
+ + # class. You may think of WillPaginate::Collection as an ordinary array with
1013
+ + # some extra properties. Those properties are used by view helpers to generate
1014
+ + # correct page links.
1015
+ + #
1016
+ + # WillPaginate::Collection also assists in rolling out your own pagination
1017
+ + # solutions: see +create+.
1018
+ + #
1019
+ + # If you are writing a library that provides a collection which you would like
1020
+ + # to conform to this API, you don't have to copy these methods over; simply
1021
+ + # make your plugin/gem dependant on the "will_paginate" gem:
1022
+ + #
1023
+ + # gem 'will_paginate'
1024
+ + # require 'will_paginate/collection'
1025
+ + #
1026
+ + # # now use WillPaginate::Collection directly or subclass it
1027
+ + class Collection < Array
1028
+ + attr_reader :current_page, :per_page, :total_entries, :total_pages
1029
+ +
1030
+ + # Arguments to the constructor are the current page number, per-page limit
1031
+ + # and the total number of entries. The last argument is optional because it
1032
+ + # is best to do lazy counting; in other words, count *conditionally* after
1033
+ + # populating the collection using the +replace+ method.
1034
+ + def initialize(page, per_page, total = nil)
1035
+ + @current_page = page.to_i
1036
+ + raise InvalidPage.new(page, @current_page) if @current_page < 1
1037
+ + @per_page = per_page.to_i
1038
+ + raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1
1039
+ +
1040
+ + self.total_entries = total if total
1041
+ + end
1042
+ +
1043
+ + # Just like +new+, but yields the object after instantiation and returns it
1044
+ + # afterwards. This is very useful for manual pagination:
1045
+ + #
1046
+ + # @entries = WillPaginate::Collection.create(1, 10) do |pager|
1047
+ + # result = Post.find(:all, :limit => pager.per_page, :offset => pager.offset)
1048
+ + # # inject the result array into the paginated collection:
1049
+ + # pager.replace(result)
1050
+ + #
1051
+ + # unless pager.total_entries
1052
+ + # # the pager didn't manage to guess the total count, do it manually
1053
+ + # pager.total_entries = Post.count
1054
+ + # end
1055
+ + # end
1056
+ + #
1057
+ + # The possibilities with this are endless. For another example, here is how
1058
+ + # WillPaginate used to define pagination for Array instances:
1059
+ + #
1060
+ + # Array.class_eval do
1061
+ + # def paginate(page = 1, per_page = 15)
1062
+ + # WillPaginate::Collection.create(page, per_page, size) do |pager|
1063
+ + # pager.replace self[pager.offset, pager.per_page].to_a
1064
+ + # end
1065
+ + # end
1066
+ + # end
1067
+ + #
1068
+ + # The Array#paginate API has since then changed, but this still serves as a
1069
+ + # fine example of WillPaginate::Collection usage.
1070
+ + def self.create(page, per_page, total = nil, &block)
1071
+ + pager = new(page, per_page, total)
1072
+ + yield pager
1073
+ + pager
1074
+ + end
1075
+ +
1076
+ + # Helper method that is true when someone tries to fetch a page with a
1077
+ + # larger number than the last page. Can be used in combination with flashes
1078
+ + # and redirecting.
1079
+ + def out_of_bounds?
1080
+ + current_page > total_pages
1081
+ + end
1082
+ +
1083
+ + # Current offset of the paginated collection. If we're on the first page,
1084
+ + # it is always 0. If we're on the 2nd page and there are 30 entries per page,
1085
+ + # the offset is 30. This property is useful if you want to render ordinals
1086
+ + # besides your records: simply start with offset + 1.
1087
+ + def offset
1088
+ + (current_page - 1) * per_page
1089
+ + end
1090
+ +
1091
+ + # current_page - 1 or nil if there is no previous page
1092
+ + def previous_page
1093
+ + current_page > 1 ? (current_page - 1) : nil
1094
+ + end
1095
+ +
1096
+ + # current_page + 1 or nil if there is no next page
1097
+ + def next_page
1098
+ + current_page < total_pages ? (current_page + 1) : nil
1099
+ + end
1100
+ +
1101
+ + def total_entries=(number)
1102
+ + @total_entries = number.to_i
1103
+ + @total_pages = (@total_entries / per_page.to_f).ceil
1104
+ + end
1105
+ +
1106
+ + # This is a magic wrapper for the original Array#replace method. It serves
1107
+ + # for populating the paginated collection after initialization.
1108
+ + #
1109
+ + # Why magic? Because it tries to guess the total number of entries judging
1110
+ + # by the size of given array. If it is shorter than +per_page+ limit, then we
1111
+ + # know we're on the last page. This trick is very useful for avoiding
1112
+ + # unnecessary hits to the database to do the counting after we fetched the
1113
+ + # data for the current page.
1114
+ + #
1115
+ + # However, after using +replace+ you should always test the value of
1116
+ + # +total_entries+ and set it to a proper value if it's +nil+. See the example
1117
+ + # in +create+.
1118
+ + def replace(array)
1119
+ + result = super
1120
+ +
1121
+ + # The collection is shorter then page limit? Rejoice, because
1122
+ + # then we know that we are on the last page!
1123
+ + if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
1124
+ + self.total_entries = offset + length
1125
+ + end
1126
+ +
1127
+ + result
1128
+ + end
1129
+ + end
1130
+ +end
1131
+ diff --git a/lib/will_paginate/core_ext.rb b/lib/will_paginate/core_ext.rb
1132
+ new file mode 100644
1133
+ index 0000000..d1fe4ef
1134
+ --- /dev/null
1135
+ +++ b/lib/will_paginate/core_ext.rb
1136
+ @@ -0,0 +1,32 @@
1137
+ +require 'set'
1138
+ +require 'will_paginate/array'
1139
+ +
1140
+ +unless Hash.instance_methods.include? 'except'
1141
+ + Hash.class_eval do
1142
+ + # Returns a new hash without the given keys.
1143
+ + def except(*keys)
1144
+ + rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
1145
+ + reject { |key,| rejected.include?(key) }
1146
+ + end
1147
+ +
1148
+ + # Replaces the hash without only the given keys.
1149
+ + def except!(*keys)
1150
+ + replace(except(*keys))
1151
+ + end
1152
+ + end
1153
+ +end
1154
+ +
1155
+ +unless Hash.instance_methods.include? 'slice'
1156
+ + Hash.class_eval do
1157
+ + # Returns a new hash with only the given keys.
1158
+ + def slice(*keys)
1159
+ + allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
1160
+ + reject { |key,| !allowed.include?(key) }
1161
+ + end
1162
+ +
1163
+ + # Replaces the hash with only the given keys.
1164
+ + def slice!(*keys)
1165
+ + replace(slice(*keys))
1166
+ + end
1167
+ + end
1168
+ +end
1169
+ diff --git a/lib/will_paginate/finder.rb b/lib/will_paginate/finder.rb
1170
+ new file mode 100644
1171
+ index 0000000..1ceecd5
1172
+ --- /dev/null
1173
+ +++ b/lib/will_paginate/finder.rb
1174
+ @@ -0,0 +1,247 @@
1175
+ +require 'will_paginate/core_ext'
1176
+ +
1177
+ +module WillPaginate
1178
+ + # A mixin for ActiveRecord::Base. Provides +per_page+ class method
1179
+ + # and hooks things up to provide paginating finders.
1180
+ + #
1181
+ + # Find out more in WillPaginate::Finder::ClassMethods
1182
+ + #
1183
+ + module Finder
1184
+ + def self.included(base)
1185
+ + base.extend ClassMethods
1186
+ + class << base
1187
+ + alias_method_chain :method_missing, :paginate
1188
+ + # alias_method_chain :find_every, :paginate
1189
+ + define_method(:per_page) { 30 } unless respond_to?(:per_page)
1190
+ + end
1191
+ + end
1192
+ +
1193
+ + # = Paginating finders for ActiveRecord models
1194
+ + #
1195
+ + # WillPaginate adds +paginate+, +per_page+ and other methods to
1196
+ + # ActiveRecord::Base class methods and associations. It also hooks into
1197
+ + # +method_missing+ to intercept pagination calls to dynamic finders such as
1198
+ + # +paginate_by_user_id+ and translate them to ordinary finders
1199
+ + # (+find_all_by_user_id+ in this case).
1200
+ + #
1201
+ + # In short, paginating finders are equivalent to ActiveRecord finders; the
1202
+ + # only difference is that we start with "paginate" instead of "find" and
1203
+ + # that <tt>:page</tt> is required parameter:
1204
+ + #
1205
+ + # @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC'
1206
+ + #
1207
+ + # In paginating finders, "all" is implicit. There is no sense in paginating
1208
+ + # a single record, right? So, you can drop the <tt>:all</tt> argument:
1209
+ + #
1210
+ + # Post.paginate(...) => Post.find :all
1211
+ + # Post.paginate_all_by_something => Post.find_all_by_something
1212
+ + # Post.paginate_by_something => Post.find_all_by_something
1213
+ + #
1214
+ + # == The importance of the <tt>:order</tt> parameter
1215
+ + #
1216
+ + # In ActiveRecord finders, <tt>:order</tt> parameter specifies columns for
1217
+ + # the <tt>ORDER BY</tt> clause in SQL. It is important to have it, since
1218
+ + # pagination only makes sense with ordered sets. Without the <tt>ORDER
1219
+ + # BY</tt> clause, databases aren't required to do consistent ordering when
1220
+ + # performing <tt>SELECT</tt> queries; this is especially true for
1221
+ + # PostgreSQL.
1222
+ + #
1223
+ + # Therefore, make sure you are doing ordering on a column that makes the
1224
+ + # most sense in the current context. Make that obvious to the user, also.
1225
+ + # For perfomance reasons you will also want to add an index to that column.
1226
+ + module ClassMethods
1227
+ + # This is the main paginating finder.
1228
+ + #
1229
+ + # == Special parameters for paginating finders
1230
+ + # * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil
1231
+ + # * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (which is 30 if not overridden)
1232
+ + # * <tt>:total_entries</tt> -- use only if you manually count total entries
1233
+ + # * <tt>:count</tt> -- additional options that are passed on to +count+
1234
+ + # * <tt>:finder</tt> -- name of the ActiveRecord finder used (default: "find")
1235
+ + #
1236
+ + # All other options (+conditions+, +order+, ...) are forwarded to +find+
1237
+ + # and +count+ calls.
1238
+ + def paginate(*args, &block)
1239
+ + options = args.pop
1240
+ + page, per_page, total_entries = wp_parse_options(options)
1241
+ + finder = (options[:finder] || 'find').to_s
1242
+ +
1243
+ + if finder == 'find'
1244
+ + # an array of IDs may have been given:
1245
+ + total_entries ||= (Array === args.first and args.first.size)
1246
+ + # :all is implicit
1247
+ + args.unshift(:all) if args.empty?
1248
+ + end
1249
+ +
1250
+ + WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
1251
+ + count_options = options.except :page, :per_page, :total_entries, :finder
1252
+ + find_options = count_options.except(:count).update(:offset => pager.offset, :limit => pager.per_page)
1253
+ +
1254
+ + args << find_options
1255
+ + # @options_from_last_find = nil
1256
+ + pager.replace send(finder, *args, &block)
1257
+ +
1258
+ + # magic counting for user convenience:
1259
+ + pager.total_entries = wp_count(count_options, args, finder) unless pager.total_entries
1260
+ + end
1261
+ + end
1262
+ +
1263
+ + # Iterates through all records by loading one page at a time. This is useful
1264
+ + # for migrations or any other use case where you don't want to load all the
1265
+ + # records in memory at once.
1266
+ + #
1267
+ + # It uses +paginate+ internally; therefore it accepts all of its options.
1268
+ + # You can specify a starting page with <tt>:page</tt> (default is 1). Default
1269
+ + # <tt>:order</tt> is <tt>"id"</tt>, override if necessary.
1270
+ + #
1271
+ + # See http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord where
1272
+ + # Jamis Buck describes this and also uses a more efficient way for MySQL.
1273
+ + def paginated_each(options = {}, &block)
1274
+ + options = { :order => 'id', :page => 1 }.merge options
1275
+ + options[:page] = options[:page].to_i
1276
+ + options[:total_entries] = 0 # skip the individual count queries
1277
+ + total = 0
1278
+ +
1279
+ + begin
1280
+ + collection = paginate(options)
1281
+ + total += collection.each(&block).size
1282
+ + options[:page] += 1
1283
+ + end until collection.size < collection.per_page
1284
+ +
1285
+ + total
1286
+ + end
1287
+ +
1288
+ + # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
1289
+ + # based on the params otherwise used by paginating finds: +page+ and
1290
+ + # +per_page+.
1291
+ + #
1292
+ + # Example:
1293
+ + #
1294
+ + # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000],
1295
+ + # :page => params[:page], :per_page => 3
1296
+ + #
1297
+ + # A query for counting rows will automatically be generated if you don't
1298
+ + # supply <tt>:total_entries</tt>. If you experience problems with this
1299
+ + # generated SQL, you might want to perform the count manually in your
1300
+ + # application.
1301
+ + #
1302
+ + def paginate_by_sql(sql, options)
1303
+ + WillPaginate::Collection.create(*wp_parse_options(options)) do |pager|
1304
+ + query = sanitize_sql(sql)
1305
+ + original_query = query.dup
1306
+ + # add limit, offset
1307
+ + add_limit! query, :offset => pager.offset, :limit => pager.per_page
1308
+ + # perfom the find
1309
+ + pager.replace find_by_sql(query)
1310
+ +
1311
+ + unless pager.total_entries
1312
+ + count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s]+$/mi, ''
1313
+ + count_query = "SELECT COUNT(*) FROM (#{count_query})"
1314
+ +
1315
+ + unless ['oracle', 'oci'].include?(self.connection.adapter_name.downcase)
1316
+ + count_query << ' AS count_table'
1317
+ + end
1318
+ + # perform the count query
1319
+ + pager.total_entries = count_by_sql(count_query)
1320
+ + end
1321
+ + end
1322
+ + end
1323
+ +
1324
+ + def respond_to?(method, include_priv = false) #:nodoc:
1325
+ + case method.to_sym
1326
+ + when :paginate, :paginate_by_sql
1327
+ + true
1328
+ + else
1329
+ + super(method.to_s.sub(/^paginate/, 'find'), include_priv)
1330
+ + end
1331
+ + end
1332
+ +
1333
+ + protected
1334
+ +
1335
+ + def method_missing_with_paginate(method, *args, &block) #:nodoc:
1336
+ + # did somebody tried to paginate? if not, let them be
1337
+ + unless method.to_s.index('paginate') == 0
1338
+ + return method_missing_without_paginate(method, *args, &block)
1339
+ + end
1340
+ +
1341
+ + # paginate finders are really just find_* with limit and offset
1342
+ + finder = method.to_s.sub('paginate', 'find')
1343
+ + finder.sub!('find', 'find_all') if finder.index('find_by_') == 0
1344
+ +
1345
+ + options = args.pop
1346
+ + raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys
1347
+ + options = options.dup
1348
+ + options[:finder] = finder
1349
+ + args << options
1350
+ +
1351
+ + paginate(*args, &block)
1352
+ + end
1353
+ +
1354
+ + # Does the not-so-trivial job of finding out the total number of entries
1355
+ + # in the database. It relies on the ActiveRecord +count+ method.
1356
+ + def wp_count(options, args, finder)
1357
+ + excludees = [:count, :order, :limit, :offset, :readonly]
1358
+ + unless options[:select] and options[:select] =~ /^\s*DISTINCT\b/i
1359
+ + excludees << :select # only exclude the select param if it doesn't begin with DISTINCT
1360
+ + end
1361
+ +
1362
+ + # count expects (almost) the same options as find
1363
+ + count_options = options.except *excludees
1364
+ +
1365
+ + # merge the hash found in :count
1366
+ + # this allows you to specify :select, :order, or anything else just for the count query
1367
+ + count_options.update options[:count] if options[:count]
1368
+ +
1369
+ + # we may be in a model or an association proxy
1370
+ + klass = (@owner and @reflection) ? @reflection.klass : self
1371
+ +
1372
+ + # forget about includes if they are irrelevant (Rails 2.1)
1373
+ + if count_options[:include] and
1374
+ + klass.private_methods.include?('references_eager_loaded_tables?') and
1375
+ + !klass.send(:references_eager_loaded_tables?, count_options)
1376
+ + count_options.delete :include
1377
+ + end
1378
+ +
1379
+ + # we may have to scope ...
1380
+ + counter = Proc.new { count(count_options) }
1381
+ +
1382
+ + count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with'))
1383
+ + # scope_out adds a 'with_finder' method which acts like with_scope, if it's present
1384
+ + # then execute the count with the scoping provided by the with_finder
1385
+ + send(scoper, &counter)
1386
+ + elsif match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(finder)
1387
+ + # extract conditions from calls like "paginate_by_foo_and_bar"
1388
+ + attribute_names = extract_attribute_names_from_match(match)
1389
+ + conditions = construct_attributes_from_arguments(attribute_names, args)
1390
+ + with_scope(:find => { :conditions => conditions }, &counter)
1391
+ + else
1392
+ + counter.call
1393
+ + end
1394
+ +
1395
+ + count.respond_to?(:length) ? count.length : count
1396
+ + end
1397
+ +
1398
+ + def wp_parse_options(options) #:nodoc:
1399
+ + raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys
1400
+ + options = options.symbolize_keys
1401
+ + raise ArgumentError, ':page parameter required' unless options.key? :page
1402
+ +
1403
+ + if options[:count] and options[:total_entries]
1404
+ + raise ArgumentError, ':count and :total_entries are mutually exclusive'
1405
+ + end
1406
+ +
1407
+ + page = options[:page] || 1
1408
+ + per_page = options[:per_page] || self.per_page
1409
+ + total = options[:total_entries]
1410
+ + [page, per_page, total]
1411
+ + end
1412
+ +
1413
+ + private
1414
+ +
1415
+ + # def find_every_with_paginate(options)
1416
+ + # @options_from_last_find = options
1417
+ + # find_every_without_paginate(options)
1418
+ + # end
1419
+ + end
1420
+ + end
1421
+ +end
1422
+ diff --git a/lib/will_paginate/named_scope.rb b/lib/will_paginate/named_scope.rb
1423
+ new file mode 100644
1424
+ index 0000000..8318b13
1425
+ --- /dev/null
1426
+ +++ b/lib/will_paginate/named_scope.rb
1427
+ @@ -0,0 +1,132 @@
1428
+ +## stolen from: http://dev.rubyonrails.org/browser/trunk/activerecord/lib/active_record/named_scope.rb?rev=9084
1429
+ +
1430
+ +module WillPaginate
1431
+ + # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate,
1432
+ + # but in other aspects when managing complex conditions that you want to be reusable.
1433
+ + module NamedScope
1434
+ + # All subclasses of ActiveRecord::Base have two named_scopes:
1435
+ + # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
1436
+ + # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
1437
+ + #
1438
+ + # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
1439
+ + #
1440
+ + # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
1441
+ + # intermediate values (scopes) around as first-class objects is convenient.
1442
+ + def self.included(base)
1443
+ + base.class_eval do
1444
+ + extend ClassMethods
1445
+ + named_scope :all
1446
+ + named_scope :scoped, lambda { |scope| scope }
1447
+ + end
1448
+ + end
1449
+ +
1450
+ + module ClassMethods
1451
+ + def scopes #:nodoc:
1452
+ + read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
1453
+ + end
1454
+ +
1455
+ + # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
1456
+ + # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
1457
+ + #
1458
+ + # class Shirt < ActiveRecord::Base
1459
+ + # named_scope :red, :conditions => {:color => 'red'}
1460
+ + # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
1461
+ + # end
1462
+ + #
1463
+ + # 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>,
1464
+ + # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
1465
+ + #
1466
+ + # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
1467
+ + # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
1468
+ + # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
1469
+ + # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
1470
+ + # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
1471
+ + #
1472
+ + # 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.
1473
+ + # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
1474
+ + # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
1475
+ + #
1476
+ + # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
1477
+ + # <tt>has_many</tt> associations. If,
1478
+ + #
1479
+ + # class Person < ActiveRecord::Base
1480
+ + # has_many :shirts
1481
+ + # end
1482
+ + #
1483
+ + # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
1484
+ + # only shirts.
1485
+ + #
1486
+ + # Named scopes can also be procedural.
1487
+ + #
1488
+ + # class Shirt < ActiveRecord::Base
1489
+ + # named_scope :colored, lambda { |color|
1490
+ + # { :conditions => { :color => color } }
1491
+ + # }
1492
+ + # end
1493
+ + #
1494
+ + # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
1495
+ + #
1496
+ + # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
1497
+ + #
1498
+ + # class Shirt < ActiveRecord::Base
1499
+ + # named_scope :red, :conditions => {:color => 'red'} do
1500
+ + # def dom_id
1501
+ + # 'red_shirts'
1502
+ + # end
1503
+ + # end
1504
+ + # end
1505
+ + #
1506
+ + def named_scope(name, options = {}, &block)
1507
+ + scopes[name] = lambda do |parent_scope, *args|
1508
+ + Scope.new(parent_scope, case options
1509
+ + when Hash
1510
+ + options
1511
+ + when Proc
1512
+ + options.call(*args)
1513
+ + end, &block)
1514
+ + end
1515
+ + (class << self; self end).instance_eval do
1516
+ + define_method name do |*args|
1517
+ + scopes[name].call(self, *args)
1518
+ + end
1519
+ + end
1520
+ + end
1521
+ + end
1522
+ +
1523
+ + class Scope #:nodoc:
1524
+ + attr_reader :proxy_scope, :proxy_options
1525
+ + [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
1526
+ + delegate :scopes, :with_scope, :to => :proxy_scope
1527
+ +
1528
+ + def initialize(proxy_scope, options, &block)
1529
+ + [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
1530
+ + extend Module.new(&block) if block_given?
1531
+ + @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
1532
+ + end
1533
+ +
1534
+ + def reload
1535
+ + load_found; self
1536
+ + end
1537
+ +
1538
+ + protected
1539
+ + def proxy_found
1540
+ + @found || load_found
1541
+ + end
1542
+ +
1543
+ + private
1544
+ + def method_missing(method, *args, &block)
1545
+ + if scopes.include?(method)
1546
+ + scopes[method].call(self, *args)
1547
+ + else
1548
+ + with_scope :find => proxy_options do
1549
+ + proxy_scope.send(method, *args, &block)
1550
+ + end
1551
+ + end
1552
+ + end
1553
+ +
1554
+ + def load_found
1555
+ + @found = find(:all)
1556
+ + end
1557
+ + end
1558
+ + end
1559
+ +end
1560
+ diff --git a/lib/will_paginate/named_scope_patch.rb b/lib/will_paginate/named_scope_patch.rb
1561
+ new file mode 100644
1562
+ index 0000000..f4ed1c2
1563
+ --- /dev/null
1564
+ +++ b/lib/will_paginate/named_scope_patch.rb
1565
+ @@ -0,0 +1,39 @@
1566
+ +## based on http://dev.rubyonrails.org/changeset/9084
1567
+ +
1568
+ +ActiveRecord::Associations::AssociationProxy.class_eval do
1569
+ + protected
1570
+ + def with_scope(*args, &block)
1571
+ + @reflection.klass.send :with_scope, *args, &block
1572
+ + end
1573
+ +end
1574
+ +
1575
+ +[ ActiveRecord::Associations::AssociationCollection,
1576
+ + ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass|
1577
+ + klass.class_eval do
1578
+ + protected
1579
+ + alias :method_missing_without_scopes :method_missing_without_paginate
1580
+ + def method_missing_without_paginate(method, *args, &block)
1581
+ + if @reflection.klass.scopes.include?(method)
1582
+ + @reflection.klass.scopes[method].call(self, *args, &block)
1583
+ + else
1584
+ + method_missing_without_scopes(method, *args, &block)
1585
+ + end
1586
+ + end
1587
+ + end
1588
+ +end
1589
+ +
1590
+ +# Rails 1.2.6
1591
+ +ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
1592
+ + protected
1593
+ + def method_missing(method, *args, &block)
1594
+ + if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
1595
+ + super
1596
+ + elsif @reflection.klass.scopes.include?(method)
1597
+ + @reflection.klass.scopes[method].call(self, *args)
1598
+ + else
1599
+ + @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
1600
+ + @reflection.klass.send(method, *args, &block)
1601
+ + end
1602
+ + end
1603
+ + end
1604
+ +end if ActiveRecord::Base.respond_to? :find_first
1605
+ diff --git a/lib/will_paginate/version.rb b/lib/will_paginate/version.rb
1606
+ new file mode 100644
1607
+ index 0000000..eaa26cd
1608
+ --- /dev/null
1609
+ +++ b/lib/will_paginate/version.rb
1610
+ @@ -0,0 +1,9 @@
1611
+ +module WillPaginate
1612
+ + module VERSION
1613
+ + MAJOR = 2
1614
+ + MINOR = 3
1615
+ + TINY = 3
1616
+ +
1617
+ + STRING = [MAJOR, MINOR, TINY].join('.')
1618
+ + end
1619
+ +end
1620
+ diff --git a/lib/will_paginate/view_helpers.rb b/lib/will_paginate/view_helpers.rb
1621
+ new file mode 100644
1622
+ index 0000000..01dbd5e
1623
+ --- /dev/null
1624
+ +++ b/lib/will_paginate/view_helpers.rb
1625
+ @@ -0,0 +1,373 @@
1626
+ +require 'will_paginate/core_ext'
1627
+ +
1628
+ +module WillPaginate
1629
+ + # = Will Paginate view helpers
1630
+ + #
1631
+ + # Currently there is only one view helper: +will_paginate+. It renders the
1632
+ + # pagination links for the given collection. The helper itself is lightweight
1633
+ + # and serves only as a wrapper around link renderer instantiation; the
1634
+ + # renderer then does all the hard work of generating the HTML.
1635
+ + #
1636
+ + # == Global options for helpers
1637
+ + #
1638
+ + # Options for pagination helpers are optional and get their default values from the
1639
+ + # WillPaginate::ViewHelpers.pagination_options hash. You can write to this hash to
1640
+ + # override default options on the global level:
1641
+ + #
1642
+ + # WillPaginate::ViewHelpers.pagination_options[:prev_label] = 'Previous page'
1643
+ + #
1644
+ + # By putting this into your environment.rb you can easily translate link texts to previous
1645
+ + # and next pages, as well as override some other defaults to your liking.
1646
+ + module ViewHelpers
1647
+ + # default options that can be overridden on the global level
1648
+ + @@pagination_options = {
1649
+ + :class => 'pagination',
1650
+ + :prev_label => '&laquo; Previous',
1651
+ + :next_label => 'Next &raquo;',
1652
+ + :inner_window => 4, # links around the current page
1653
+ + :outer_window => 1, # links around beginning and end
1654
+ + :separator => ' ', # single space is friendly to spiders and non-graphic browsers
1655
+ + :param_name => :page,
1656
+ + :params => nil,
1657
+ + :renderer => 'WillPaginate::LinkRenderer',
1658
+ + :page_links => true,
1659
+ + :container => true
1660
+ + }
1661
+ + mattr_reader :pagination_options
1662
+ +
1663
+ + # Renders Digg/Flickr-style pagination for a WillPaginate::Collection
1664
+ + # object. Nil is returned if there is only one page in total; no point in
1665
+ + # rendering the pagination in that case...
1666
+ + #
1667
+ + # ==== Options
1668
+ + # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination")
1669
+ + # * <tt>:prev_label</tt> -- default: "T� Previous"
1670
+ + # * <tt>:next_label</tt> -- default: "Next T�"
1671
+ + # * <tt>:inner_window</tt> -- how many links are shown around the current page (default: 4)
1672
+ + # * <tt>:outer_window</tt> -- how many links are around the first and the last page (default: 1)
1673
+ + # * <tt>:separator</tt> -- string separator for page HTML elements (default: single space)
1674
+ + # * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
1675
+ + # * <tt>:params</tt> -- additional parameters when generating pagination links
1676
+ + # (eg. <tt>:controller => "foo", :action => nil</tt>)
1677
+ + # * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default:
1678
+ + # <tt>WillPaginate::LinkRenderer</tt>)
1679
+ + # * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
1680
+ + # * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
1681
+ + # false only when you are rendering your own pagination markup (default: true)
1682
+ + # * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID
1683
+ + # automatically generated from the class name of objects in collection: for example, paginating
1684
+ + # ArticleComment models would yield an ID of "article_comments_pagination".
1685
+ + #
1686
+ + # All options beside listed ones are passed as HTML attributes to the container
1687
+ + # element for pagination links (the DIV). For example:
1688
+ + #
1689
+ + # <%= will_paginate @posts, :id => 'wp_posts' %>
1690
+ + #
1691
+ + # ... will result in:
1692
+ + #
1693
+ + # <div class="pagination" id="wp_posts"> ... </div>
1694
+ + #
1695
+ + # ==== Using the helper without arguments
1696
+ + # If the helper is called without passing in the collection object, it will
1697
+ + # try to read from the instance variable inferred by the controller name.
1698
+ + # For example, calling +will_paginate+ while the current controller is
1699
+ + # PostsController will result in trying to read from the <tt>@posts</tt>
1700
+ + # variable. Example:
1701
+ + #
1702
+ + # <%= will_paginate :id => true %>
1703
+ + #
1704
+ + # ... will result in <tt>@post</tt> collection getting paginated:
1705
+ + #
1706
+ + # <div class="pagination" id="posts_pagination"> ... </div>
1707
+ + #
1708
+ + def will_paginate(collection = nil, options = {})
1709
+ + options, collection = collection, nil if collection.is_a? Hash
1710
+ + unless collection or !controller
1711
+ + collection_name = "@#{controller.controller_name}"
1712
+ + collection = instance_variable_get(collection_name)
1713
+ + raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " +
1714
+ + "forget to pass the collection object for will_paginate?" unless collection
1715
+ + end
1716
+ + # early exit if there is nothing to render
1717
+ + return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(collection) > 1
1718
+ +
1719
+ + options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options
1720
+ +
1721
+ + # get the renderer instance
1722
+ + renderer = case options[:renderer]
1723
+ + when String
1724
+ + options[:renderer].to_s.constantize.new
1725
+ + when Class
1726
+ + options[:renderer].new
1727
+ + else
1728
+ + options[:renderer]
1729
+ + end
1730
+ + # render HTML for pagination
1731
+ + renderer.prepare collection, options, self
1732
+ + renderer.to_html
1733
+ + end
1734
+ +
1735
+ + # Wrapper for rendering pagination links at both top and bottom of a block
1736
+ + # of content.
1737
+ + #
1738
+ + # <% paginated_section @posts do %>
1739
+ + # <ol id="posts">
1740
+ + # <% for post in @posts %>
1741
+ + # <li> ... </li>
1742
+ + # <% end %>
1743
+ + # </ol>
1744
+ + # <% end %>
1745
+ + #
1746
+ + # will result in:
1747
+ + #
1748
+ + # <div class="pagination"> ... </div>
1749
+ + # <ol id="posts">
1750
+ + # ...
1751
+ + # </ol>
1752
+ + # <div class="pagination"> ... </div>
1753
+ + #
1754
+ + # Arguments are passed to a <tt>will_paginate</tt> call, so the same options
1755
+ + # apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two
1756
+ + # blocks of pagination links sharing the same ID (which is invalid HTML).
1757
+ + def paginated_section(*args, &block)
1758
+ + pagination = will_paginate(*args).to_s
1759
+ + content = pagination + capture(&block) + pagination
1760
+ + concat content, block.binding
1761
+ + end
1762
+ +
1763
+ + # Renders a helpful message with numbers of displayed vs. total entries.
1764
+ + # You can use this as a blueprint for your own, similar helpers.
1765
+ + #
1766
+ + # <%= page_entries_info @posts %>
1767
+ + # #-> Displaying posts 6 - 10 of 26 in total
1768
+ + #
1769
+ + # By default, the message will use the humanized class name of objects
1770
+ + # in collection: for instance, "project types" for ProjectType models.
1771
+ + # Override this to your liking with the <tt>:entry_name</tt> parameter:
1772
+ + #
1773
+ + # <%= page_entries_info @posts, :entry_name => 'item' %>
1774
+ + # #-> Displaying items 6 - 10 of 26 in total
1775
+ + def page_entries_info(collection, options = {})
1776
+ + entry_name = options[:entry_name] ||
1777
+ + (collection.empty?? 'entry' : collection.first.class.name.underscore.sub('_', ' '))
1778
+ +
1779
+ + if collection.total_pages < 2
1780
+ + case collection.size
1781
+ + when 0; "No #{entry_name.pluralize} found"
1782
+ + when 1; "Displaying <b>1</b> #{entry_name}"
1783
+ + else; "Displaying <b>all #{collection.size}</b> #{entry_name.pluralize}"
1784
+ + end
1785
+ + else
1786
+ + %{Displaying #{entry_name.pluralize} <b>%d&nbsp;-&nbsp;%d</b> of <b>%d</b> in total} % [
1787
+ + collection.offset + 1,
1788
+ + collection.offset + collection.length,
1789
+ + collection.total_entries
1790
+ + ]
1791
+ + end
1792
+ + end
1793
+ +
1794
+ + def self.total_pages_for_collection(collection) #:nodoc:
1795
+ + if collection.respond_to?('page_count') and !collection.respond_to?('total_pages')
1796
+ + WillPaginate::Deprecation.warn <<-MSG
1797
+ + You are using a paginated collection of class #{collection.class.name}
1798
+ + which conforms to the old API of WillPaginate::Collection by using
1799
+ + `page_count`, while the current method name is `total_pages`. Please
1800
+ + upgrade yours or 3rd-party code that provides the paginated collection.
1801
+ + MSG
1802
+ + class << collection
1803
+ + def total_pages; page_count; end
1804
+ + end
1805
+ + end
1806
+ + collection.total_pages
1807
+ + end
1808
+ + end
1809
+ +
1810
+ + # This class does the heavy lifting of actually building the pagination
1811
+ + # links. It is used by +will_paginate+ helper internally.
1812
+ + class LinkRenderer
1813
+ +
1814
+ + # The gap in page links is represented by:
1815
+ + #
1816
+ + # <span class="gap">&hellip;</span>
1817
+ + attr_accessor :gap_marker
1818
+ +
1819
+ + def initialize
1820
+ + @gap_marker = '<span class="gap">&hellip;</span>'
1821
+ + end
1822
+ +
1823
+ + # * +collection+ is a WillPaginate::Collection instance or any other object
1824
+ + # that conforms to that API
1825
+ + # * +options+ are forwarded from +will_paginate+ view helper
1826
+ + # * +template+ is the reference to the template being rendered
1827
+ + def prepare(collection, options, template)
1828
+ + @collection = collection
1829
+ + @options = options
1830
+ + @template = template
1831
+ +
1832
+ + # reset values in case we're re-using this instance
1833
+ + @total_pages = @param_name = @url_string = nil
1834
+ + end
1835
+ +
1836
+ + # Process it! This method returns the complete HTML string which contains
1837
+ + # pagination links. Feel free to subclass LinkRenderer and change this
1838
+ + # method as you see fit.
1839
+ + def to_html
1840
+ + links = @options[:page_links] ? windowed_links : []
1841
+ + # previous/next buttons
1842
+ + links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:prev_label])
1843
+ + links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label])
1844
+ +
1845
+ + html = links.join(@options[:separator])
1846
+ + @options[:container] ? @template.content_tag(:div, html, html_attributes) : html
1847
+ + end
1848
+ +
1849
+ + # Returns the subset of +options+ this instance was initialized with that
1850
+ + # represent HTML attributes for the container element of pagination links.
1851
+ + def html_attributes
1852
+ + return @html_attributes if @html_attributes
1853
+ + @html_attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class])
1854
+ + # pagination of Post models will have the ID of "posts_pagination"
1855
+ + if @options[:container] and @options[:id] === true
1856
+ + @html_attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination'
1857
+ + end
1858
+ + @html_attributes
1859
+ + end
1860
+ +
1861
+ + protected
1862
+ +
1863
+ + # Collects link items for visible page numbers.
1864
+ + def windowed_links
1865
+ + prev = nil
1866
+ +
1867
+ + visible_page_numbers.inject [] do |links, n|
1868
+ + # detect gaps:
1869
+ + links << gap_marker if prev and n > prev + 1
1870
+ + links << page_link_or_span(n, 'current')
1871
+ + prev = n
1872
+ + links
1873
+ + end
1874
+ + end
1875
+ +
1876
+ + # Calculates visible page numbers using the <tt>:inner_window</tt> and
1877
+ + # <tt>:outer_window</tt> options.
1878
+ + def visible_page_numbers
1879
+ + inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
1880
+ + window_from = current_page - inner_window
1881
+ + window_to = current_page + inner_window
1882
+ +
1883
+ + # adjust lower or upper limit if other is out of bounds
1884
+ + if window_to > total_pages
1885
+ + window_from -= window_to - total_pages
1886
+ + window_to = total_pages
1887
+ + end
1888
+ + if window_from < 1
1889
+ + window_to += 1 - window_from
1890
+ + window_from = 1
1891
+ + window_to = total_pages if window_to > total_pages
1892
+ + end
1893
+ +
1894
+ + visible = (1..total_pages).to_a
1895
+ + left_gap = (2 + outer_window)...window_from
1896
+ + right_gap = (window_to + 1)...(total_pages - outer_window)
1897
+ + visible -= left_gap.to_a if left_gap.last - left_gap.first > 1
1898
+ + visible -= right_gap.to_a if right_gap.last - right_gap.first > 1
1899
+ +
1900
+ + visible
1901
+ + end
1902
+ +
1903
+ + def page_link_or_span(page, span_class, text = nil)
1904
+ + text ||= page.to_s
1905
+ +
1906
+ + if page and page != current_page
1907
+ + classnames = span_class && span_class.index(' ') && span_class.split(' ', 2).last
1908
+ + page_link page, text, :rel => rel_value(page), :class => classnames
1909
+ + else
1910
+ + page_span page, text, :class => span_class
1911
+ + end
1912
+ + end
1913
+ +
1914
+ + def page_link(page, text, attributes = {})
1915
+ + @template.link_to text, url_for(page), attributes
1916
+ + end
1917
+ +
1918
+ + def page_span(page, text, attributes = {})
1919
+ + @template.content_tag :span, text, attributes
1920
+ + end
1921
+ +
1922
+ + # Returns URL params for +page_link_or_span+, taking the current GET params
1923
+ + # and <tt>:params</tt> option into account.
1924
+ + def url_for(page)
1925
+ + page_one = page == 1
1926
+ + unless @url_string and !page_one
1927
+ + @url_params = {}
1928
+ + # page links should preserve GET parameters
1929
+ + stringified_merge @url_params, @template.params if @template.request.get?
1930
+ + stringified_merge @url_params, @options[:params] if @options[:params]
1931
+ +
1932
+ + if complex = param_name.index(/[^\w-]/)
1933
+ + page_param = (defined?(CGIMethods) ? CGIMethods : ActionController::AbstractRequest).
1934
+ + parse_query_parameters("#{param_name}=#{page}")
1935
+ +
1936
+ + stringified_merge @url_params, page_param
1937
+ + else
1938
+ + @url_params[param_name] = page_one ? 1 : 2
1939
+ + end
1940
+ +
1941
+ + url = @template.url_for(@url_params)
1942
+ + return url if page_one
1943
+ +
1944
+ + if complex
1945
+ + @url_string = url.sub(%r!((?:\?|&amp;)#{CGI.escape param_name}=)#{page}!, '\1@')
1946
+ + return url
1947
+ + else
1948
+ + @url_string = url
1949
+ + @url_params[param_name] = 3
1950
+ + @template.url_for(@url_params).split(//).each_with_index do |char, i|
1951
+ + if char == '3' and url[i, 1] == '2'
1952
+ + @url_string[i] = '@'
1953
+ + break
1954
+ + end
1955
+ + end
1956
+ + end
1957
+ + end
1958
+ + # finally!
1959
+ + @url_string.sub '@', page.to_s
1960
+ + end
1961
+ +
1962
+ + private
1963
+ +
1964
+ + def rel_value(page)
1965
+ + case page
1966
+ + when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
1967
+ + when @collection.next_page; 'next'
1968
+ + when 1; 'start'
1969
+ + end
1970
+ + end
1971
+ +
1972
+ + def current_page
1973
+ + @collection.current_page
1974
+ + end
1975
+ +
1976
+ + def total_pages
1977
+ + @total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@collection)
1978
+ + end
1979
+ +
1980
+ + def param_name
1981
+ + @param_name ||= @options[:param_name].to_s
1982
+ + end
1983
+ +
1984
+ + # Recursively merge into target hash by using stringified keys from the other one
1985
+ + def stringified_merge(target, other)
1986
+ + other.each do |key, value|
1987
+ + key = key.to_s # this line is what it's all about!
1988
+ + existing = target[key]
1989
+ +
1990
+ + if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
1991
+ + stringified_merge(existing || (target[key] = {}), value)
1992
+ + else
1993
+ + target[key] = value
1994
+ + end
1995
+ + end
1996
+ + end
1997
+ + end
1998
+ +end
1999
+ diff --git a/test/boot.rb b/test/boot.rb
2000
+ new file mode 100644
2001
+ index 0000000..4df47ec
2002
+ --- /dev/null
2003
+ +++ b/test/boot.rb
2004
+ @@ -0,0 +1,21 @@
2005
+ +plugin_root = File.join(File.dirname(__FILE__), '..')
2006
+ +version = ENV['RAILS_VERSION']
2007
+ +version = nil if version and version == ""
2008
+ +
2009
+ +# first look for a symlink to a copy of the framework
2010
+ +if !version and framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p }
2011
+ + puts "found framework root: #{framework_root}"
2012
+ + # this allows for a plugin to be tested outside of an app and without Rails gems
2013
+ + $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib"
2014
+ +else
2015
+ + # simply use installed gems if available
2016
+ + puts "using Rails#{version ? ' ' + version : nil} gems"
2017
+ + require 'rubygems'
2018
+ +
2019
+ + if version
2020
+ + gem 'rails', version
2021
+ + else
2022
+ + gem 'actionpack'
2023
+ + gem 'activerecord'
2024
+ + end
2025
+ +end
2026
+ diff --git a/test/collection_test.rb b/test/collection_test.rb
2027
+ new file mode 100644
2028
+ index 0000000..de39206
2029
+ --- /dev/null
2030
+ +++ b/test/collection_test.rb
2031
+ @@ -0,0 +1,140 @@
2032
+ +require 'helper'
2033
+ +require 'will_paginate/array'
2034
+ +
2035
+ +class ArrayPaginationTest < Test::Unit::TestCase
2036
+ + def test_simple
2037
+ + collection = ('a'..'e').to_a
2038
+ +
2039
+ + [{ :page => 1, :per_page => 3, :expected => %w( a b c ) },
2040
+ + { :page => 2, :per_page => 3, :expected => %w( d e ) },
2041
+ + { :page => 1, :per_page => 5, :expected => %w( a b c d e ) },
2042
+ + { :page => 3, :per_page => 5, :expected => [] },
2043
+ + ].
2044
+ + each do |conditions|
2045
+ + expected = conditions.delete :expected
2046
+ + assert_equal expected, collection.paginate(conditions)
2047
+ + end
2048
+ + end
2049
+ +
2050
+ + def test_defaults
2051
+ + result = (1..50).to_a.paginate
2052
+ + assert_equal 1, result.current_page
2053
+ + assert_equal 30, result.size
2054
+ + end
2055
+ +
2056
+ + def test_deprecated_api
2057
+ + assert_raise(ArgumentError) { [].paginate(2) }
2058
+ + assert_raise(ArgumentError) { [].paginate(2, 10) }
2059
+ + end
2060
+ +
2061
+ + def test_total_entries_has_precedence
2062
+ + result = %w(a b c).paginate :total_entries => 5
2063
+ + assert_equal 5, result.total_entries
2064
+ + end
2065
+ +
2066
+ + def test_argument_error_with_params_and_another_argument
2067
+ + assert_raise ArgumentError do
2068
+ + [].paginate({}, 5)
2069
+ + end
2070
+ + end
2071
+ +
2072
+ + def test_paginated_collection
2073
+ + entries = %w(a b c)
2074
+ + collection = create(2, 3, 10) do |pager|
2075
+ + assert_equal entries, pager.replace(entries)
2076
+ + end
2077
+ +
2078
+ + assert_equal entries, collection
2079
+ + assert_respond_to_all collection, %w(total_pages each offset size current_page per_page total_entries)
2080
+ + assert_kind_of Array, collection
2081
+ + assert_instance_of Array, collection.entries
2082
+ + assert_equal 3, collection.offset
2083
+ + assert_equal 4, collection.total_pages
2084
+ + assert !collection.out_of_bounds?
2085
+ + end
2086
+ +
2087
+ + def test_previous_next_pages
2088
+ + collection = create(1, 1, 3)
2089
+ + assert_nil collection.previous_page
2090
+ + assert_equal 2, collection.next_page
2091
+ +
2092
+ + collection = create(2, 1, 3)
2093
+ + assert_equal 1, collection.previous_page
2094
+ + assert_equal 3, collection.next_page
2095
+ +
2096
+ + collection = create(3, 1, 3)
2097
+ + assert_equal 2, collection.previous_page
2098
+ + assert_nil collection.next_page
2099
+ + end
2100
+ +
2101
+ + def test_out_of_bounds
2102
+ + entries = create(2, 3, 2){}
2103
+ + assert entries.out_of_bounds?
2104
+ +
2105
+ + entries = create(1, 3, 2){}
2106
+ + assert !entries.out_of_bounds?
2107
+ + end
2108
+ +
2109
+ + def test_guessing_total_count
2110
+ + entries = create do |pager|
2111
+ + # collection is shorter than limit
2112
+ + pager.replace array
2113
+ + end
2114
+ + assert_equal 8, entries.total_entries
2115
+ +
2116
+ + entries = create(2, 5, 10) do |pager|
2117
+ + # collection is shorter than limit, but we have an explicit count
2118
+ + pager.replace array
2119
+ + end
2120
+ + assert_equal 10, entries.total_entries
2121
+ +
2122
+ + entries = create do |pager|
2123
+ + # collection is the same as limit; we can't guess
2124
+ + pager.replace array(5)
2125
+ + end
2126
+ + assert_equal nil, entries.total_entries
2127
+ +
2128
+ + entries = create do |pager|
2129
+ + # collection is empty; we can't guess
2130
+ + pager.replace array(0)
2131
+ + end
2132
+ + assert_equal nil, entries.total_entries
2133
+ +
2134
+ + entries = create(1) do |pager|
2135
+ + # collection is empty and we're on page 1,
2136
+ + # so the whole thing must be empty, too
2137
+ + pager.replace array(0)
2138
+ + end
2139
+ + assert_equal 0, entries.total_entries
2140
+ + end
2141
+ +
2142
+ + def test_invalid_page
2143
+ + bad_inputs = [0, -1, nil, '', 'Schnitzel']
2144
+ +
2145
+ + bad_inputs.each do |bad|
2146
+ + assert_raise(WillPaginate::InvalidPage) { create bad }
2147
+ + end
2148
+ + end
2149
+ +
2150
+ + def test_invalid_per_page_setting
2151
+ + assert_raise(ArgumentError) { create(1, -1) }
2152
+ + end
2153
+ +
2154
+ + def test_page_count_was_removed
2155
+ + assert_raise(NoMethodError) { create.page_count }
2156
+ + # It's `total_pages` now.
2157
+ + end
2158
+ +
2159
+ + private
2160
+ + def create(page = 2, limit = 5, total = nil, &block)
2161
+ + if block_given?
2162
+ + WillPaginate::Collection.create(page, limit, total, &block)
2163
+ + else
2164
+ + WillPaginate::Collection.new(page, limit, total)
2165
+ + end
2166
+ + end
2167
+ +
2168
+ + def array(size = 3)
2169
+ + Array.new(size)
2170
+ + end
2171
+ +end
2172
+ diff --git a/test/console b/test/console
2173
+ new file mode 100755
2174
+ index 0000000..ca74c60
2175
+ --- /dev/null
2176
+ +++ b/test/console
2177
+ @@ -0,0 +1,8 @@
2178
+ +#!/usr/bin/env ruby
2179
+ +irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
2180
+ +libs = []
2181
+ +
2182
+ +libs << 'irb/completion'
2183
+ +libs << File.join('lib', 'load_fixtures')
2184
+ +
2185
+ +exec "#{irb} -Ilib:test#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt"
2186
+ diff --git a/test/database.yml b/test/database.yml
2187
+ new file mode 100644
2188
+ index 0000000..7ef1e73
2189
+ --- /dev/null
2190
+ +++ b/test/database.yml
2191
+ @@ -0,0 +1,22 @@
2192
+ +sqlite3:
2193
+ + database: ":memory:"
2194
+ + adapter: sqlite3
2195
+ + timeout: 500
2196
+ +
2197
+ +sqlite2:
2198
+ + database: ":memory:"
2199
+ + adapter: sqlite2
2200
+ +
2201
+ +mysql:
2202
+ + adapter: mysql
2203
+ + username: rails
2204
+ + password: mislav
2205
+ + encoding: utf8
2206
+ + database: will_paginate_unittest
2207
+ +
2208
+ +postgres:
2209
+ + adapter: postgresql
2210
+ + username: mislav
2211
+ + password: mislav
2212
+ + database: will_paginate_unittest
2213
+ + min_messages: warning
2214
+ diff --git a/test/finder_test.rb b/test/finder_test.rb
2215
+ new file mode 100644
2216
+ index 0000000..23fe269
2217
+ --- /dev/null
2218
+ +++ b/test/finder_test.rb
2219
+ @@ -0,0 +1,434 @@
2220
+ +require 'helper'
2221
+ +require 'lib/activerecord_test_case'
2222
+ +
2223
+ +require 'will_paginate'
2224
+ +WillPaginate.enable_activerecord
2225
+ +WillPaginate.enable_named_scope
2226
+ +
2227
+ +class FinderTest < ActiveRecordTestCase
2228
+ + fixtures :topics, :replies, :users, :projects, :developers_projects
2229
+ +
2230
+ + def test_new_methods_presence
2231
+ + assert_respond_to_all Topic, %w(per_page paginate paginate_by_sql)
2232
+ + end
2233
+ +
2234
+ + def test_simple_paginate
2235
+ + assert_queries(1) do
2236
+ + entries = Topic.paginate :page => nil
2237
+ + assert_equal 1, entries.current_page
2238
+ + assert_equal 1, entries.total_pages
2239
+ + assert_equal 4, entries.size
2240
+ + end
2241
+ +
2242
+ + assert_queries(2) do
2243
+ + entries = Topic.paginate :page => 2
2244
+ + assert_equal 1, entries.total_pages
2245
+ + assert entries.empty?
2246
+ + end
2247
+ + end
2248
+ +
2249
+ + def test_parameter_api
2250
+ + # :page parameter in options is required!
2251
+ + assert_raise(ArgumentError){ Topic.paginate }
2252
+ + assert_raise(ArgumentError){ Topic.paginate({}) }
2253
+ +
2254
+ + # explicit :all should not break anything
2255
+ + assert_equal Topic.paginate(:page => nil), Topic.paginate(:all, :page => 1)
2256
+ +
2257
+ + # :count could be nil and we should still not cry
2258
+ + assert_nothing_raised { Topic.paginate :page => 1, :count => nil }
2259
+ + end
2260
+ +
2261
+ + def test_paginate_with_per_page
2262
+ + entries = Topic.paginate :page => 1, :per_page => 1
2263
+ + assert_equal 1, entries.size
2264
+ + assert_equal 4, entries.total_pages
2265
+ +
2266
+ + # Developer class has explicit per_page at 10
2267
+ + entries = Developer.paginate :page => 1
2268
+ + assert_equal 10, entries.size
2269
+ + assert_equal 2, entries.total_pages
2270
+ +
2271
+ + entries = Developer.paginate :page => 1, :per_page => 5
2272
+ + assert_equal 11, entries.total_entries
2273
+ + assert_equal 5, entries.size
2274
+ + assert_equal 3, entries.total_pages
2275
+ + end
2276
+ +
2277
+ + def test_paginate_with_order
2278
+ + entries = Topic.paginate :page => 1, :order => 'created_at desc'
2279
+ + expected = [topics(:futurama), topics(:harvey_birdman), topics(:rails), topics(:ar)].reverse
2280
+ + assert_equal expected, entries.to_a
2281
+ + assert_equal 1, entries.total_pages
2282
+ + end
2283
+ +
2284
+ + def test_paginate_with_conditions
2285
+ + entries = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago]
2286
+ + expected = [topics(:rails), topics(:ar)]
2287
+ + assert_equal expected, entries.to_a
2288
+ + assert_equal 1, entries.total_pages
2289
+ + end
2290
+ +
2291
+ + def test_paginate_with_include_and_conditions
2292
+ + entries = Topic.paginate \
2293
+ + :page => 1,
2294
+ + :include => :replies,
2295
+ + :conditions => "replies.content LIKE 'Bird%' ",
2296
+ + :per_page => 10
2297
+ +
2298
+ + expected = Topic.find :all,
2299
+ + :include => 'replies',
2300
+ + :conditions => "replies.content LIKE 'Bird%' ",
2301
+ + :limit => 10
2302
+ +
2303
+ + assert_equal expected, entries.to_a
2304
+ + assert_equal 1, entries.total_entries
2305
+ + end
2306
+ +
2307
+ + def test_paginate_with_include_and_order
2308
+ + entries = nil
2309
+ + assert_queries(2) do
2310
+ + entries = Topic.paginate \
2311
+ + :page => 1,
2312
+ + :include => :replies,
2313
+ + :order => 'replies.created_at asc, topics.created_at asc',
2314
+ + :per_page => 10
2315
+ + end
2316
+ +
2317
+ + expected = Topic.find :all,
2318
+ + :include => 'replies',
2319
+ + :order => 'replies.created_at asc, topics.created_at asc',
2320
+ + :limit => 10
2321
+ +
2322
+ + assert_equal expected, entries.to_a
2323
+ + assert_equal 4, entries.total_entries
2324
+ + end
2325
+ +
2326
+ + def test_paginate_associations_with_include
2327
+ + entries, project = nil, projects(:active_record)
2328
+ +
2329
+ + assert_nothing_raised "THIS IS A BUG in Rails 1.2.3 that was fixed in [7326]. " +
2330
+ + "Please upgrade to a newer version of Rails." do
2331
+ + entries = project.topics.paginate \
2332
+ + :page => 1,
2333
+ + :include => :replies,
2334
+ + :conditions => "replies.content LIKE 'Nice%' ",
2335
+ + :per_page => 10
2336
+ + end
2337
+ +
2338
+ + expected = Topic.find :all,
2339
+ + :include => 'replies',
2340
+ + :conditions => "project_id = #{project.id} AND replies.content LIKE 'Nice%' ",
2341
+ + :limit => 10
2342
+ +
2343
+ + assert_equal expected, entries.to_a
2344
+ + end
2345
+ +
2346
+ + def test_paginate_associations
2347
+ + dhh = users :david
2348
+ + expected_name_ordered = [projects(:action_controller), projects(:active_record)]
2349
+ + expected_id_ordered = [projects(:active_record), projects(:action_controller)]
2350
+ +
2351
+ + assert_queries(2) do
2352
+ + # with association-specified order
2353
+ + entries = dhh.projects.paginate(:page => 1)
2354
+ + assert_equal expected_name_ordered, entries
2355
+ + assert_equal 2, entries.total_entries
2356
+ + end
2357
+ +
2358
+ + # with explicit order
2359
+ + entries = dhh.projects.paginate(:page => 1, :order => 'projects.id')
2360
+ + assert_equal expected_id_ordered, entries
2361
+ + assert_equal 2, entries.total_entries
2362
+ +
2363
+ + assert_nothing_raised { dhh.projects.find(:all, :order => 'projects.id', :limit => 4) }
2364
+ + entries = dhh.projects.paginate(:page => 1, :order => 'projects.id', :per_page => 4)
2365
+ + assert_equal expected_id_ordered, entries
2366
+ +
2367
+ + # has_many with implicit order
2368
+ + topic = Topic.find(1)
2369
+ + expected = [replies(:spam), replies(:witty_retort)]
2370
+ + assert_equal expected.map(&:id).sort, topic.replies.paginate(:page => 1).map(&:id).sort
2371
+ + assert_equal expected.reverse, topic.replies.paginate(:page => 1, :order => 'replies.id ASC')
2372
+ + end
2373
+ +
2374
+ + def test_paginate_association_extension
2375
+ + project = Project.find(:first)
2376
+ +
2377
+ + assert_queries(2) do
2378
+ + entries = project.replies.paginate_recent :page => 1
2379
+ + assert_equal [replies(:brave)], entries
2380
+ + end
2381
+ + end
2382
+ +
2383
+ + def test_paginate_with_joins
2384
+ + entries = nil
2385
+ +
2386
+ + assert_queries(1) do
2387
+ + entries = Developer.paginate :page => 1,
2388
+ + :joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id',
2389
+ + :conditions => 'project_id = 1'
2390
+ + assert_equal 2, entries.size
2391
+ + developer_names = entries.map &:name
2392
+ + assert developer_names.include?('David')
2393
+ + assert developer_names.include?('Jamis')
2394
+ + end
2395
+ +
2396
+ + assert_queries(1) do
2397
+ + expected = entries.to_a
2398
+ + entries = Developer.paginate :page => 1,
2399
+ + :joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id',
2400
+ + :conditions => 'project_id = 1', :count => { :select => "users.id" }
2401
+ + assert_equal expected, entries.to_a
2402
+ + assert_equal 2, entries.total_entries
2403
+ + end
2404
+ + end
2405
+ +
2406
+ + def test_paginate_with_group
2407
+ + entries = nil
2408
+ + assert_queries(1) do
2409
+ + entries = Developer.paginate :page => 1, :per_page => 10,
2410
+ + :group => 'salary', :select => 'salary', :order => 'salary'
2411
+ + end
2412
+ +
2413
+ + expected = [ users(:david), users(:jamis), users(:dev_10), users(:poor_jamis) ].map(&:salary).sort
2414
+ + assert_equal expected, entries.map(&:salary)
2415
+ + end
2416
+ +
2417
+ + def test_paginate_with_dynamic_finder
2418
+ + expected = [replies(:witty_retort), replies(:spam)]
2419
+ + assert_equal expected, Reply.paginate_by_topic_id(1, :page => 1)
2420
+ +
2421
+ + entries = Developer.paginate :conditions => { :salary => 100000 }, :page => 1, :per_page => 5
2422
+ + assert_equal 8, entries.total_entries
2423
+ + assert_equal entries, Developer.paginate_by_salary(100000, :page => 1, :per_page => 5)
2424
+ +
2425
+ + # dynamic finder + conditions
2426
+ + entries = Developer.paginate_by_salary(100000, :page => 1,
2427
+ + :conditions => ['id > ?', 6])
2428
+ + assert_equal 4, entries.total_entries
2429
+ + assert_equal (7..10).to_a, entries.map(&:id)
2430
+ +
2431
+ + assert_raises NoMethodError do
2432
+ + Developer.paginate_by_inexistent_attribute 100000, :page => 1
2433
+ + end
2434
+ + end
2435
+ +
2436
+ + def test_scoped_paginate
2437
+ + entries = Developer.with_poor_ones { Developer.paginate :page => 1 }
2438
+ +
2439
+ + assert_equal 2, entries.size
2440
+ + assert_equal 2, entries.total_entries
2441
+ + end
2442
+ +
2443
+ + ## named_scope ##
2444
+ +
2445
+ + def test_paginate_in_named_scope
2446
+ + entries = Developer.poor.paginate :page => 1, :per_page => 1
2447
+ +
2448
+ + assert_equal 1, entries.size
2449
+ + assert_equal 2, entries.total_entries
2450
+ + end
2451
+ +
2452
+ + def test_paginate_in_named_scope_on_habtm_association
2453
+ + project = projects(:active_record)
2454
+ + assert_queries(2) do
2455
+ + entries = project.developers.poor.paginate :page => 1, :per_page => 1
2456
+ +
2457
+ + assert_equal 1, entries.size, 'one developer should be found'
2458
+ + assert_equal 1, entries.total_entries, 'only one developer should be found'
2459
+ + end
2460
+ + end
2461
+ +
2462
+ + def test_paginate_in_named_scope_on_hmt_association
2463
+ + project = projects(:active_record)
2464
+ + expected = [replies(:brave)]
2465
+ +
2466
+ + assert_queries(2) do
2467
+ + entries = project.replies.recent.paginate :page => 1, :per_page => 1
2468
+ + assert_equal expected, entries
2469
+ + assert_equal 1, entries.total_entries, 'only one reply should be found'
2470
+ + end
2471
+ + end
2472
+ +
2473
+ + def test_paginate_in_named_scope_on_has_many_association
2474
+ + project = projects(:active_record)
2475
+ + expected = [topics(:ar)]
2476
+ +
2477
+ + assert_queries(2) do
2478
+ + entries = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1
2479
+ + assert_equal expected, entries
2480
+ + assert_equal 1, entries.total_entries, 'only one topic should be found'
2481
+ + end
2482
+ + end
2483
+ +
2484
+ + ## misc ##
2485
+ +
2486
+ + def test_count_and_total_entries_options_are_mutually_exclusive
2487
+ + e = assert_raise ArgumentError do
2488
+ + Developer.paginate :page => 1, :count => {}, :total_entries => 1
2489
+ + end
2490
+ + assert_match /exclusive/, e.to_s
2491
+ + end
2492
+ +
2493
+ + def test_readonly
2494
+ + assert_nothing_raised { Developer.paginate :readonly => true, :page => 1 }
2495
+ + end
2496
+ +
2497
+ + # this functionality is temporarily removed
2498
+ + def xtest_pagination_defines_method
2499
+ + pager = "paginate_by_created_at"
2500
+ + assert !User.methods.include?(pager), "User methods should not include `#{pager}` method"
2501
+ + # paginate!
2502
+ + assert 0, User.send(pager, nil, :page => 1).total_entries
2503
+ + # the paging finder should now be defined
2504
+ + assert User.methods.include?(pager), "`#{pager}` method should be defined on User"
2505
+ + end
2506
+ +
2507
+ + # Is this Rails 2.0? Find out by testing find_all which was removed in [6998]
2508
+ + unless ActiveRecord::Base.respond_to? :find_all
2509
+ + def test_paginate_array_of_ids
2510
+ + # AR finders also accept arrays of IDs
2511
+ + # (this was broken in Rails before [6912])
2512
+ + assert_queries(1) do
2513
+ + entries = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id')
2514
+ + assert_equal (4..6).to_a, entries.map(&:id)
2515
+ + assert_equal 8, entries.total_entries
2516
+ + end
2517
+ + end
2518
+ + end
2519
+ +
2520
+ + uses_mocha 'internals' do
2521
+ + def test_implicit_all_with_dynamic_finders
2522
+ + Topic.expects(:find_all_by_foo).returns([])
2523
+ + Topic.expects(:count).returns(0)
2524
+ + Topic.paginate_by_foo :page => 2
2525
+ + end
2526
+ +
2527
+ + def test_guessing_the_total_count
2528
+ + Topic.expects(:find).returns(Array.new(2))
2529
+ + Topic.expects(:count).never
2530
+ +
2531
+ + entries = Topic.paginate :page => 2, :per_page => 4
2532
+ + assert_equal 6, entries.total_entries
2533
+ + end
2534
+ +
2535
+ + def test_guessing_that_there_are_no_records
2536
+ + Topic.expects(:find).returns([])
2537
+ + Topic.expects(:count).never
2538
+ +
2539
+ + entries = Topic.paginate :page => 1, :per_page => 4
2540
+ + assert_equal 0, entries.total_entries
2541
+ + end
2542
+ +
2543
+ + def test_extra_parameters_stay_untouched
2544
+ + Topic.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => 0 }).returns(Array.new(5))
2545
+ + Topic.expects(:count).with({:foo => 'bar'}).returns(1)
2546
+ +
2547
+ + Topic.paginate :foo => 'bar', :page => 1, :per_page => 4
2548
+ + end
2549
+ +
2550
+ + def test_count_skips_select
2551
+ + Developer.stubs(:find).returns([])
2552
+ + Developer.expects(:count).with({}).returns(0)
2553
+ + Developer.paginate :select => 'salary', :page => 2
2554
+ + end
2555
+ +
2556
+ + def test_count_select_when_distinct
2557
+ + Developer.stubs(:find).returns([])
2558
+ + Developer.expects(:count).with(:select => 'DISTINCT salary').returns(0)
2559
+ + Developer.paginate :select => 'DISTINCT salary', :page => 2
2560
+ + end
2561
+ +
2562
+ + def test_should_use_scoped_finders_if_present
2563
+ + # scope-out compatibility
2564
+ + Topic.expects(:find_best).returns(Array.new(5))
2565
+ + Topic.expects(:with_best).returns(1)
2566
+ +
2567
+ + Topic.paginate_best :page => 1, :per_page => 4
2568
+ + end
2569
+ +
2570
+ + def test_paginate_by_sql
2571
+ + assert_respond_to Developer, :paginate_by_sql
2572
+ + Developer.expects(:find_by_sql).with(regexp_matches(/sql LIMIT 3(,| OFFSET) 3/)).returns([])
2573
+ + Developer.expects(:count_by_sql).with('SELECT COUNT(*) FROM (sql) AS count_table').returns(0)
2574
+ +
2575
+ + entries = Developer.paginate_by_sql 'sql', :page => 2, :per_page => 3
2576
+ + end
2577
+ +
2578
+ + def test_paginate_by_sql_respects_total_entries_setting
2579
+ + Developer.expects(:find_by_sql).returns([])
2580
+ + Developer.expects(:count_by_sql).never
2581
+ +
2582
+ + entries = Developer.paginate_by_sql 'sql', :page => 1, :total_entries => 999
2583
+ + assert_equal 999, entries.total_entries
2584
+ + end
2585
+ +
2586
+ + def test_paginate_by_sql_strips_order_by_when_counting
2587
+ + Developer.expects(:find_by_sql).returns([])
2588
+ + Developer.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS count_table").returns(0)
2589
+ +
2590
+ + Developer.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 2
2591
+ + end
2592
+ +
2593
+ + # TODO: counts are still wrong
2594
+ + def test_ability_to_use_with_custom_finders
2595
+ + # acts_as_taggable defines find_tagged_with(tag, options)
2596
+ + Topic.expects(:find_tagged_with).with('will_paginate', :offset => 5, :limit => 5).returns([])
2597
+ + Topic.expects(:count).with({}).returns(0)
2598
+ +
2599
+ + Topic.paginate_tagged_with 'will_paginate', :page => 2, :per_page => 5
2600
+ + end
2601
+ +
2602
+ + def test_array_argument_doesnt_eliminate_count
2603
+ + ids = (1..8).to_a
2604
+ + Developer.expects(:find_all_by_id).returns([])
2605
+ + Developer.expects(:count).returns(0)
2606
+ +
2607
+ + Developer.paginate_by_id(ids, :per_page => 3, :page => 2, :order => 'id')
2608
+ + end
2609
+ +
2610
+ + def test_paginating_finder_doesnt_mangle_options
2611
+ + Developer.expects(:find).returns([])
2612
+ + options = { :page => 1 }
2613
+ + options.expects(:delete).never
2614
+ + options_before = options.dup
2615
+ +
2616
+ + Developer.paginate(options)
2617
+ + assert_equal options, options_before
2618
+ + end
2619
+ +
2620
+ + def test_paginated_each
2621
+ + collection = stub('collection', :size => 5, :empty? => false, :per_page => 5)
2622
+ + collection.expects(:each).times(2).returns(collection)
2623
+ + last_collection = stub('collection', :size => 4, :empty? => false, :per_page => 5)
2624
+ + last_collection.expects(:each).returns(last_collection)
2625
+ +
2626
+ + params = { :order => 'id', :total_entries => 0 }
2627
+ +
2628
+ + Developer.expects(:paginate).with(params.merge(:page => 2)).returns(collection)
2629
+ + Developer.expects(:paginate).with(params.merge(:page => 3)).returns(collection)
2630
+ + Developer.expects(:paginate).with(params.merge(:page => 4)).returns(last_collection)
2631
+ +
2632
+ + assert_equal 14, Developer.paginated_each(:page => '2') { }
2633
+ + end
2634
+ +
2635
+ + # detect ActiveRecord 2.1
2636
+ + if ActiveRecord::Base.private_methods.include?('references_eager_loaded_tables?')
2637
+ + def test_removes_irrelevant_includes_in_count
2638
+ + Developer.expects(:find).returns([1])
2639
+ + Developer.expects(:count).with({}).returns(0)
2640
+ +
2641
+ + Developer.paginate :page => 1, :per_page => 1, :include => :projects
2642
+ + end
2643
+ +
2644
+ + def test_doesnt_remove_referenced_includes_in_count
2645
+ + Developer.expects(:find).returns([1])
2646
+ + Developer.expects(:count).with({ :include => :projects, :conditions => 'projects.id > 2' }).returns(0)
2647
+ +
2648
+ + Developer.paginate :page => 1, :per_page => 1,
2649
+ + :include => :projects, :conditions => 'projects.id > 2'
2650
+ + end
2651
+ + end
2652
+ + end
2653
+ +end
2654
+ diff --git a/test/fixtures/admin.rb b/test/fixtures/admin.rb
2655
+ new file mode 100644
2656
+ index 0000000..1d5e7f3
2657
+ --- /dev/null
2658
+ +++ b/test/fixtures/admin.rb
2659
+ @@ -0,0 +1,3 @@
2660
+ +class Admin < User
2661
+ + has_many :companies, :finder_sql => 'SELECT * FROM companies'
2662
+ +end
2663
+ diff --git a/test/fixtures/developer.rb b/test/fixtures/developer.rb
2664
+ new file mode 100644
2665
+ index 0000000..6e702e7
2666
+ --- /dev/null
2667
+ +++ b/test/fixtures/developer.rb
2668
+ @@ -0,0 +1,13 @@
2669
+ +class Developer < User
2670
+ + has_and_belongs_to_many :projects, :include => :topics, :order => 'projects.name'
2671
+ +
2672
+ + def self.with_poor_ones(&block)
2673
+ + with_scope :find => { :conditions => ['salary <= ?', 80000], :order => 'salary' } do
2674
+ + yield
2675
+ + end
2676
+ + end
2677
+ +
2678
+ + named_scope :poor, :conditions => ['salary <= ?', 80000], :order => 'salary'
2679
+ +
2680
+ + def self.per_page() 10 end
2681
+ +end
2682
+ diff --git a/test/fixtures/developers_projects.yml b/test/fixtures/developers_projects.yml
2683
+ new file mode 100644
2684
+ index 0000000..cee359c
2685
+ --- /dev/null
2686
+ +++ b/test/fixtures/developers_projects.yml
2687
+ @@ -0,0 +1,13 @@
2688
+ +david_action_controller:
2689
+ + developer_id: 1
2690
+ + project_id: 2
2691
+ + joined_on: 2004-10-10
2692
+ +
2693
+ +david_active_record:
2694
+ + developer_id: 1
2695
+ + project_id: 1
2696
+ + joined_on: 2004-10-10
2697
+ +
2698
+ +jamis_active_record:
2699
+ + developer_id: 2
2700
+ + project_id: 1
2701
+
2702
+ diff --git a/test/fixtures/project.rb b/test/fixtures/project.rb
2703
+ new file mode 100644
2704
+ index 0000000..0f85ef5
2705
+ --- /dev/null
2706
+ +++ b/test/fixtures/project.rb
2707
+ @@ -0,0 +1,15 @@
2708
+ +class Project < ActiveRecord::Base
2709
+ + has_and_belongs_to_many :developers, :uniq => true
2710
+ +
2711
+ + has_many :topics
2712
+ + # :finder_sql => 'SELECT * FROM topics WHERE (topics.project_id = #{id})',
2713
+ + # :counter_sql => 'SELECT COUNT(*) FROM topics WHERE (topics.project_id = #{id})'
2714
+ +
2715
+ + has_many :replies, :through => :topics do
2716
+ + def find_recent(params = {})
2717
+ + with_scope :find => { :conditions => ['replies.created_at > ?', 15.minutes.ago] } do
2718
+ + find :all, params
2719
+ + end
2720
+ + end
2721
+ + end
2722
+ +end
2723
+ diff --git a/test/fixtures/projects.yml b/test/fixtures/projects.yml
2724
+ new file mode 100644
2725
+ index 0000000..44e2508
2726
+ --- /dev/null
2727
+ +++ b/test/fixtures/projects.yml
2728
+ @@ -0,0 +1,6 @@
2729
+ +active_record:
2730
+ + id: 1
2731
+ + name: Active Record
2732
+ +action_controller:
2733
+ + id: 2
2734
+ + name: Active Controller
2735
+ diff --git a/test/fixtures/replies.yml b/test/fixtures/replies.yml
2736
+ new file mode 100644
2737
+ index 0000000..9a83c00
2738
+ --- /dev/null
2739
+ +++ b/test/fixtures/replies.yml
2740
+ @@ -0,0 +1,29 @@
2741
+ +witty_retort:
2742
+ + id: 1
2743
+ + topic_id: 1
2744
+ + content: Birdman is better!
2745
+ + created_at: <%= 6.hours.ago.to_s(:db) %>
2746
+ +
2747
+ +another:
2748
+ + id: 2
2749
+ + topic_id: 2
2750
+ + content: Nuh uh!
2751
+ + created_at: <%= 1.hour.ago.to_s(:db) %>
2752
+ +
2753
+ +spam:
2754
+ + id: 3
2755
+ + topic_id: 1
2756
+ + content: Nice site!
2757
+ + created_at: <%= 1.hour.ago.to_s(:db) %>
2758
+ +
2759
+ +decisive:
2760
+ + id: 4
2761
+ + topic_id: 4
2762
+ + content: "I'm getting to the bottom of this"
2763
+ + created_at: <%= 30.minutes.ago.to_s(:db) %>
2764
+ +
2765
+ +brave:
2766
+ + id: 5
2767
+ + topic_id: 4
2768
+ + content: "AR doesn't scare me a bit"
2769
+ + created_at: <%= 10.minutes.ago.to_s(:db) %>
2770
+ diff --git a/test/fixtures/reply.rb b/test/fixtures/reply.rb
2771
+ new file mode 100644
2772
+ index 0000000..2d58b75
2773
+ --- /dev/null
2774
+ +++ b/test/fixtures/reply.rb
2775
+ @@ -0,0 +1,7 @@
2776
+ +class Reply < ActiveRecord::Base
2777
+ + belongs_to :topic, :include => [:replies]
2778
+ +
2779
+ + named_scope :recent, :conditions => ['replies.created_at > ?', 15.minutes.ago]
2780
+ +
2781
+ + validates_presence_of :content
2782
+ +end
2783
+ diff --git a/test/fixtures/schema.rb b/test/fixtures/schema.rb
2784
+ new file mode 100644
2785
+ index 0000000..8831aad
2786
+ --- /dev/null
2787
+ +++ b/test/fixtures/schema.rb
2788
+ @@ -0,0 +1,38 @@
2789
+ +ActiveRecord::Schema.define do
2790
+ +
2791
+ + create_table "users", :force => true do |t|
2792
+ + t.column "name", :text
2793
+ + t.column "salary", :integer, :default => 70000
2794
+ + t.column "created_at", :datetime
2795
+ + t.column "updated_at", :datetime
2796
+ + t.column "type", :text
2797
+ + end
2798
+ +
2799
+ + create_table "projects", :force => true do |t|
2800
+ + t.column "name", :text
2801
+ + end
2802
+ +
2803
+ + create_table "developers_projects", :id => false, :force => true do |t|
2804
+ + t.column "developer_id", :integer, :null => false
2805
+ + t.column "project_id", :integer, :null => false
2806
+ + t.column "joined_on", :date
2807
+ + t.column "access_level", :integer, :default => 1
2808
+ + end
2809
+ +
2810
+ + create_table "topics", :force => true do |t|
2811
+ + t.column "project_id", :integer
2812
+ + t.column "title", :string
2813
+ + t.column "subtitle", :string
2814
+ + t.column "content", :text
2815
+ + t.column "created_at", :datetime
2816
+ + t.column "updated_at", :datetime
2817
+ + end
2818
+ +
2819
+ + create_table "replies", :force => true do |t|
2820
+ + t.column "content", :text
2821
+ + t.column "created_at", :datetime
2822
+ + t.column "updated_at", :datetime
2823
+ + t.column "topic_id", :integer
2824
+ + end
2825
+ +
2826
+ +end
2827
+ diff --git a/test/fixtures/topic.rb b/test/fixtures/topic.rb
2828
+ new file mode 100644
2829
+ index 0000000..3fddd17
2830
+ --- /dev/null
2831
+ +++ b/test/fixtures/topic.rb
2832
+ @@ -0,0 +1,6 @@
2833
+ +class Topic < ActiveRecord::Base
2834
+ + has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC'
2835
+ + belongs_to :project
2836
+ +
2837
+ + named_scope :mentions_activerecord, :conditions => ['topics.title LIKE ?', '%ActiveRecord%']
2838
+ +end
2839
+ diff --git a/test/fixtures/topics.yml b/test/fixtures/topics.yml
2840
+ new file mode 100644
2841
+ index 0000000..0a26904
2842
+ --- /dev/null
2843
+ +++ b/test/fixtures/topics.yml
2844
+ @@ -0,0 +1,30 @@
2845
+ +futurama:
2846
+ + id: 1
2847
+ + title: Isnt futurama awesome?
2848
+ + subtitle: It really is, isnt it.
2849
+ + content: I like futurama
2850
+ + created_at: <%= 1.day.ago.to_s(:db) %>
2851
+ + updated_at:
2852
+ +
2853
+ +harvey_birdman:
2854
+ + id: 2
2855
+ + title: Harvey Birdman is the king of all men
2856
+ + subtitle: yup
2857
+ + content: He really is
2858
+ + created_at: <%= 2.hours.ago.to_s(:db) %>
2859
+ + updated_at:
2860
+ +
2861
+ +rails:
2862
+ + id: 3
2863
+ + project_id: 1
2864
+ + title: Rails is nice
2865
+ + subtitle: It makes me happy
2866
+ + content: except when I have to hack internals to fix pagination. even then really.
2867
+ + created_at: <%= 20.minutes.ago.to_s(:db) %>
2868
+ +
2869
+ +ar:
2870
+ + id: 4
2871
+ + project_id: 1
2872
+ + title: ActiveRecord sometimes freaks me out
2873
+ + content: "I mean, what's the deal with eager loading?"
2874
+ + created_at: <%= 15.minutes.ago.to_s(:db) %>
2875
+ diff --git a/test/fixtures/user.rb b/test/fixtures/user.rb
2876
+ new file mode 100644
2877
+ index 0000000..4a57cf0
2878
+ --- /dev/null
2879
+ +++ b/test/fixtures/user.rb
2880
+ @@ -0,0 +1,2 @@
2881
+ +class User < ActiveRecord::Base
2882
+ +end
2883
+ diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
2884
+ new file mode 100644
2885
+ index 0000000..ed2c03a
2886
+ --- /dev/null
2887
+ +++ b/test/fixtures/users.yml
2888
+ @@ -0,0 +1,35 @@
2889
+ +david:
2890
+ + id: 1
2891
+ + name: David
2892
+ + salary: 80000
2893
+ + type: Developer
2894
+ +
2895
+ +jamis:
2896
+ + id: 2
2897
+ + name: Jamis
2898
+ + salary: 150000
2899
+ + type: Developer
2900
+ +
2901
+ +<% for digit in 3..10 %>
2902
+ +dev_<%= digit %>:
2903
+ + id: <%= digit %>
2904
+ + name: fixture_<%= digit %>
2905
+ + salary: 100000
2906
+ + type: Developer
2907
+ +<% end %>
2908
+ +
2909
+ +poor_jamis:
2910
+ + id: 11
2911
+ + name: Jamis
2912
+ + salary: 9000
2913
+ + type: Developer
2914
+ +
2915
+ +admin:
2916
+ + id: 12
2917
+ + name: admin
2918
+ + type: Admin
2919
+ +
2920
+ +goofy:
2921
+ + id: 13
2922
+ + name: Goofy
2923
+ + type: Admin
2924
+ diff --git a/test/helper.rb b/test/helper.rb
2925
+ new file mode 100644
2926
+ index 0000000..7ae5f3b
2927
+ --- /dev/null
2928
+ +++ b/test/helper.rb
2929
+ @@ -0,0 +1,37 @@
2930
+ +require 'test/unit'
2931
+ +require 'rubygems'
2932
+ +
2933
+ +# gem install redgreen for colored test output
2934
+ +begin require 'redgreen'; rescue LoadError; end
2935
+ +
2936
+ +require 'boot' unless defined?(ActiveRecord)
2937
+ +
2938
+ +class Test::Unit::TestCase
2939
+ + protected
2940
+ + def assert_respond_to_all object, methods
2941
+ + methods.each do |method|
2942
+ + [method.to_s, method.to_sym].each { |m| assert_respond_to object, m }
2943
+ + end
2944
+ + end
2945
+ +
2946
+ + def collect_deprecations
2947
+ + old_behavior = WillPaginate::Deprecation.behavior
2948
+ + deprecations = []
2949
+ + WillPaginate::Deprecation.behavior = Proc.new do |message, callstack|
2950
+ + deprecations << message
2951
+ + end
2952
+ + result = yield
2953
+ + [result, deprecations]
2954
+ + ensure
2955
+ + WillPaginate::Deprecation.behavior = old_behavior
2956
+ + end
2957
+ +end
2958
+ +
2959
+ +# Wrap tests that use Mocha and skip if unavailable.
2960
+ +def uses_mocha(test_name)
2961
+ + require 'mocha' unless Object.const_defined?(:Mocha)
2962
+ +rescue LoadError => load_error
2963
+ + $stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again."
2964
+ +else
2965
+ + yield
2966
+ +end
2967
+ diff --git a/test/lib/activerecord_test_case.rb b/test/lib/activerecord_test_case.rb
2968
+ new file mode 100644
2969
+ index 0000000..375b86a
2970
+ --- /dev/null
2971
+ +++ b/test/lib/activerecord_test_case.rb
2972
+ @@ -0,0 +1,36 @@
2973
+ +require 'lib/activerecord_test_connector'
2974
+ +
2975
+ +class ActiveRecordTestCase < Test::Unit::TestCase
2976
+ + # Set our fixture path
2977
+ + if ActiveRecordTestConnector.able_to_connect
2978
+ + self.fixture_path = File.join(File.dirname(__FILE__), '..', 'fixtures')
2979
+ + self.use_transactional_fixtures = true
2980
+ + end
2981
+ +
2982
+ + def self.fixtures(*args)
2983
+ + super if ActiveRecordTestConnector.connected
2984
+ + end
2985
+ +
2986
+ + def run(*args)
2987
+ + super if ActiveRecordTestConnector.connected
2988
+ + end
2989
+ +
2990
+ + # Default so Test::Unit::TestCase doesn't complain
2991
+ + def test_truth
2992
+ + end
2993
+ +
2994
+ + protected
2995
+ +
2996
+ + def assert_queries(num = 1)
2997
+ + $query_count = 0
2998
+ + yield
2999
+ + ensure
3000
+ + assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
3001
+ + end
3002
+ +
3003
+ + def assert_no_queries(&block)
3004
+ + assert_queries(0, &block)
3005
+ + end
3006
+ +end
3007
+ +
3008
+ +ActiveRecordTestConnector.setup
3009
+ diff --git a/test/lib/activerecord_test_connector.rb b/test/lib/activerecord_test_connector.rb
3010
+ new file mode 100644
3011
+ index 0000000..d06ceaa
3012
+ --- /dev/null
3013
+ +++ b/test/lib/activerecord_test_connector.rb
3014
+ @@ -0,0 +1,69 @@
3015
+ +require 'active_record'
3016
+ +require 'active_record/version'
3017
+ +require 'active_record/fixtures'
3018
+ +
3019
+ +class ActiveRecordTestConnector
3020
+ + cattr_accessor :able_to_connect
3021
+ + cattr_accessor :connected
3022
+ +
3023
+ + FIXTURES_PATH = File.join(File.dirname(__FILE__), '..', 'fixtures')
3024
+ +
3025
+ + # Set our defaults
3026
+ + self.connected = false
3027
+ + self.able_to_connect = true
3028
+ +
3029
+ + def self.setup
3030
+ + unless self.connected || !self.able_to_connect
3031
+ + setup_connection
3032
+ + load_schema
3033
+ + Dependencies.load_paths.unshift FIXTURES_PATH
3034
+ + self.connected = true
3035
+ + end
3036
+ + rescue Exception => e # errors from ActiveRecord setup
3037
+ + $stderr.puts "\nSkipping ActiveRecord tests: #{e}"
3038
+ + $stderr.puts "Install SQLite3 to run the full test suite for will_paginate.\n\n"
3039
+ + self.able_to_connect = false
3040
+ + end
3041
+ +
3042
+ + private
3043
+ +
3044
+ + def self.setup_connection
3045
+ + db = ENV['DB'].blank?? 'sqlite3' : ENV['DB']
3046
+ +
3047
+ + configurations = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'database.yml'))
3048
+ + raise "no configuration for '#{db}'" unless configurations.key? db
3049
+ + configuration = configurations[db]
3050
+ +
3051
+ + ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb'
3052
+ + puts "using #{configuration['adapter']} adapter" unless ENV['DB'].blank?
3053
+ +
3054
+ + ActiveRecord::Base.establish_connection(configuration)
3055
+ + ActiveRecord::Base.configurations = { db => configuration }
3056
+ + prepare ActiveRecord::Base.connection
3057
+ +
3058
+ + unless Object.const_defined?(:QUOTED_TYPE)
3059
+ + Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')
3060
+ + end
3061
+ + end
3062
+ +
3063
+ + def self.load_schema
3064
+ + ActiveRecord::Base.silence do
3065
+ + ActiveRecord::Migration.verbose = false
3066
+ + load File.join(FIXTURES_PATH, 'schema.rb')
3067
+ + end
3068
+ + end
3069
+ +
3070
+ + def self.prepare(conn)
3071
+ + class << conn
3072
+ + IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SHOW FIELDS /]
3073
+ +
3074
+ + def execute_with_counting(sql, name = nil, &block)
3075
+ + $query_count ||= 0
3076
+ + $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
3077
+ + execute_without_counting(sql, name, &block)
3078
+ + end
3079
+ +
3080
+ + alias_method_chain :execute, :counting
3081
+ + end
3082
+ + end
3083
+ +end
3084
+ diff --git a/test/lib/load_fixtures.rb b/test/lib/load_fixtures.rb
3085
+ new file mode 100644
3086
+ index 0000000..65a3450
3087
+ --- /dev/null
3088
+ +++ b/test/lib/load_fixtures.rb
3089
+ @@ -0,0 +1,11 @@
3090
+ +require 'boot'
3091
+ +require 'lib/activerecord_test_connector'
3092
+ +
3093
+ +# setup the connection
3094
+ +ActiveRecordTestConnector.setup
3095
+ +
3096
+ +# load all fixtures
3097
+ +Fixtures.create_fixtures(ActiveRecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables)
3098
+ +
3099
+ +require 'will_paginate'
3100
+ +WillPaginate.enable_activerecord
3101
+ diff --git a/test/lib/view_test_process.rb b/test/lib/view_test_process.rb
3102
+ new file mode 100644
3103
+ index 0000000..fc5d8ec
3104
+ --- /dev/null
3105
+ +++ b/test/lib/view_test_process.rb
3106
+ @@ -0,0 +1,165 @@
3107
+ +require 'action_controller'
3108
+ +require 'action_controller/test_process'
3109
+ +
3110
+ +require 'will_paginate'
3111
+ +WillPaginate.enable_actionpack
3112
+ +
3113
+ +ActionController::Routing::Routes.draw do |map|
3114
+ + map.connect 'dummy/page/:page', :controller => 'dummy'
3115
+ + map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dots'
3116
+ + map.connect 'ibocorp/:page', :controller => 'ibocorp',
3117
+ + :requirements => { :page => /\d+/ },
3118
+ + :defaults => { :page => 1 }
3119
+ +
3120
+ + map.connect ':controller/:action/:id'
3121
+ +end
3122
+ +
3123
+ +ActionController::Base.perform_caching = false
3124
+ +
3125
+ +class WillPaginate::ViewTestCase < Test::Unit::TestCase
3126
+ + def setup
3127
+ + super
3128
+ + @controller = DummyController.new
3129
+ + @request = @controller.request
3130
+ + @html_result = nil
3131
+ + @template = '<%= will_paginate collection, options %>'
3132
+ +
3133
+ + @view = ActionView::Base.new
3134
+ + @view.assigns['controller'] = @controller
3135
+ + @view.assigns['_request'] = @request
3136
+ + @view.assigns['_params'] = @request.params
3137
+ + end
3138
+ +
3139
+ + def test_no_complain; end
3140
+ +
3141
+ + protected
3142
+ +
3143
+ + def paginate(collection = {}, options = {}, &block)
3144
+ + if collection.instance_of? Hash
3145
+ + page_options = { :page => 1, :total_entries => 11, :per_page => 4 }.merge(collection)
3146
+ + collection = [1].paginate(page_options)
3147
+ + end
3148
+ +
3149
+ + locals = { :collection => collection, :options => options }
3150
+ +
3151
+ + if defined? ActionView::InlineTemplate
3152
+ + # Rails 2.1
3153
+ + args = [ ActionView::InlineTemplate.new(@view, @template, locals) ]
3154
+ + else
3155
+ + # older Rails versions
3156
+ + args = [nil, @template, nil, locals]
3157
+ + end
3158
+ +
3159
+ + @html_result = @view.render_template(*args)
3160
+ + @html_document = HTML::Document.new(@html_result, true, false)
3161
+ +
3162
+ + if block_given?
3163
+ + classname = options[:class] || WillPaginate::ViewHelpers.pagination_options[:class]
3164
+ + assert_select("div.#{classname}", 1, 'no main DIV', &block)
3165
+ + end
3166
+ + end
3167
+ +
3168
+ + def response_from_page_or_rjs
3169
+ + @html_document.root
3170
+ + end
3171
+ +
3172
+ + def validate_page_numbers expected, links, param_name = :page
3173
+ + param_pattern = /\W#{CGI.escape(param_name.to_s)}=([^&]*)/
3174
+ +
3175
+ + assert_equal(expected, links.map { |e|
3176
+ + e['href'] =~ param_pattern
3177
+ + $1 ? $1.to_i : $1
3178
+ + })
3179
+ + end
3180
+ +
3181
+ + def assert_links_match pattern, links = nil, numbers = nil
3182
+ + links ||= assert_select 'div.pagination a[href]' do |elements|
3183
+ + elements
3184
+ + end
3185
+ +
3186
+ + pages = [] if numbers
3187
+ +
3188
+ + links.each do |el|
3189
+ + assert_match pattern, el['href']
3190
+ + if numbers
3191
+ + el['href'] =~ pattern
3192
+ + pages << ($1.nil?? nil : $1.to_i)
3193
+ + end
3194
+ + end
3195
+ +
3196
+ + assert_equal numbers, pages, "page numbers don't match" if numbers
3197
+ + end
3198
+ +
3199
+ + def assert_no_links_match pattern
3200
+ + assert_select 'div.pagination a[href]' do |elements|
3201
+ + elements.each do |el|
3202
+ + assert_no_match pattern, el['href']
3203
+ + end
3204
+ + end
3205
+ + end
3206
+ +end
3207
+ +
3208
+ +class DummyRequest
3209
+ + attr_accessor :symbolized_path_parameters
3210
+ +
3211
+ + def initialize
3212
+ + @get = true
3213
+ + @params = {}
3214
+ + @symbolized_path_parameters = { :controller => 'foo', :action => 'bar' }
3215
+ + end
3216
+ +
3217
+ + def get?
3218
+ + @get
3219
+ + end
3220
+ +
3221
+ + def post
3222
+ + @get = false
3223
+ + end
3224
+ +
3225
+ + def relative_url_root
3226
+ + ''
3227
+ + end
3228
+ +
3229
+ + def params(more = nil)
3230
+ + @params.update(more) if more
3231
+ + @params
3232
+ + end
3233
+ +end
3234
+ +
3235
+ +class DummyController
3236
+ + attr_reader :request
3237
+ + attr_accessor :controller_name
3238
+ +
3239
+ + def initialize
3240
+ + @request = DummyRequest.new
3241
+ + @url = ActionController::UrlRewriter.new(@request, @request.params)
3242
+ + end
3243
+ +
3244
+ + def params
3245
+ + @request.params
3246
+ + end
3247
+ +
3248
+ + def url_for(params)
3249
+ + @url.rewrite(params)
3250
+ + end
3251
+ +end
3252
+ +
3253
+ +module HTML
3254
+ + Node.class_eval do
3255
+ + def inner_text
3256
+ + children.map(&:inner_text).join('')
3257
+ + end
3258
+ + end
3259
+ +
3260
+ + Text.class_eval do
3261
+ + def inner_text
3262
+ + self.to_s
3263
+ + end
3264
+ + end
3265
+ +
3266
+ + Tag.class_eval do
3267
+ + def inner_text
3268
+ + childless?? '' : super
3269
+ + end
3270
+ + end
3271
+ +end
3272
+ diff --git a/test/tasks.rake b/test/tasks.rake
3273
+ new file mode 100644
3274
+ index 0000000..4090340
3275
+ --- /dev/null
3276
+ +++ b/test/tasks.rake
3277
+ @@ -0,0 +1,56 @@
3278
+ +require 'rake/testtask'
3279
+ +
3280
+ +desc 'Test the will_paginate plugin.'
3281
+ +Rake::TestTask.new(:test) do |t|
3282
+ + t.pattern = 'test/**/*_test.rb'
3283
+ + t.verbose = true
3284
+ + t.libs << 'test'
3285
+ +end
3286
+ +
3287
+ +# I want to specify environment variables at call time
3288
+ +class EnvTestTask < Rake::TestTask
3289
+ + attr_accessor :env
3290
+ +
3291
+ + def ruby(*args)
3292
+ + env.each { |key, value| ENV[key] = value } if env
3293
+ + super
3294
+ + env.keys.each { |key| ENV.delete key } if env
3295
+ + end
3296
+ +end
3297
+ +
3298
+ +for configuration in %w( sqlite3 mysql postgres )
3299
+ + EnvTestTask.new("test_#{configuration}") do |t|
3300
+ + t.pattern = 'test/finder_test.rb'
3301
+ + t.verbose = true
3302
+ + t.env = { 'DB' => configuration }
3303
+ + t.libs << 'test'
3304
+ + end
3305
+ +end
3306
+ +
3307
+ +task :test_databases => %w(test_mysql test_sqlite3 test_postgres)
3308
+ +
3309
+ +desc %{Test everything on SQLite3, MySQL and PostgreSQL}
3310
+ +task :test_full => %w(test test_mysql test_postgres)
3311
+ +
3312
+ +desc %{Test everything with Rails 1.2.x and 2.0.x gems}
3313
+ +task :test_all do
3314
+ + all = Rake::Task['test_full']
3315
+ + ENV['RAILS_VERSION'] = '~>1.2.6'
3316
+ + all.invoke
3317
+ + # reset the invoked flag
3318
+ + %w( test_full test test_mysql test_postgres ).each do |name|
3319
+ + Rake::Task[name].instance_variable_set '@already_invoked', false
3320
+ + end
3321
+ + # do it again
3322
+ + ENV['RAILS_VERSION'] = '~>2.0.2'
3323
+ + all.invoke
3324
+ +end
3325
+ +
3326
+ +task :rcov do
3327
+ + excludes = %w( lib/will_paginate/named_scope*
3328
+ + lib/will_paginate/core_ext.rb
3329
+ + lib/will_paginate.rb
3330
+ + rails* )
3331
+ +
3332
+ + system %[rcov -Itest:lib test/*.rb -x #{excludes.join(',')}]
3333
+ +end
3334
+ diff --git a/test/view_test.rb b/test/view_test.rb
3335
+ new file mode 100644
3336
+ index 0000000..2c49b5e
3337
+ --- /dev/null
3338
+ +++ b/test/view_test.rb
3339
+ @@ -0,0 +1,355 @@
3340
+ +require 'helper'
3341
+ +require 'lib/view_test_process'
3342
+ +
3343
+ +class AdditionalLinkAttributesRenderer < WillPaginate::LinkRenderer
3344
+ + def initialize(link_attributes = nil)
3345
+ + super()
3346
+ + @additional_link_attributes = link_attributes || { :default => 'true' }
3347
+ + end
3348
+ +
3349
+ + def page_link(page, text, attributes = {})
3350
+ + @template.link_to text, url_for(page), attributes.merge(@additional_link_attributes)
3351
+ + end
3352
+ +end
3353
+ +
3354
+ +class ViewTest < WillPaginate::ViewTestCase
3355
+ +
3356
+ + ## basic pagination ##
3357
+ +
3358
+ + def test_will_paginate
3359
+ + paginate do |pagination|
3360
+ + assert_select 'a[href]', 3 do |elements|
3361
+ + validate_page_numbers [2,3,2], elements
3362
+ + assert_select elements.last, ':last-child', "Next &raquo;"
3363
+ + end
3364
+ + assert_select 'span', 2
3365
+ + assert_select 'span.disabled:first-child', '&laquo; Previous'
3366
+ + assert_select 'span.current', '1'
3367
+ + assert_equal '&laquo; Previous 1 2 3 Next &raquo;', pagination.first.inner_text
3368
+ + end
3369
+ + end
3370
+ +
3371
+ + def test_no_pagination_when_page_count_is_one
3372
+ + paginate :per_page => 30
3373
+ + assert_equal '', @html_result
3374
+ + end
3375
+ +
3376
+ + def test_will_paginate_with_options
3377
+ + paginate({ :page => 2 },
3378
+ + :class => 'will_paginate', :prev_label => 'Prev', :next_label => 'Next') do
3379
+ + assert_select 'a[href]', 4 do |elements|
3380
+ + validate_page_numbers [1,1,3,3], elements
3381
+ + # test rel attribute values:
3382
+ + assert_select elements[1], 'a', '1' do |link|
3383
+ + assert_equal 'prev start', link.first['rel']
3384
+ + end
3385
+ + assert_select elements.first, 'a', "Prev" do |link|
3386
+ + assert_equal 'prev start', link.first['rel']
3387
+ + end
3388
+ + assert_select elements.last, 'a', "Next" do |link|
3389
+ + assert_equal 'next', link.first['rel']
3390
+ + end
3391
+ + end
3392
+ + assert_select 'span.current', '2'
3393
+ + end
3394
+ + end
3395
+ +
3396
+ + def test_will_paginate_using_renderer_class
3397
+ + paginate({}, :renderer => AdditionalLinkAttributesRenderer) do
3398
+ + assert_select 'a[default=true]', 3
3399
+ + end
3400
+ + end
3401
+ +
3402
+ + def test_will_paginate_using_renderer_instance
3403
+ + renderer = WillPaginate::LinkRenderer.new
3404
+ + renderer.gap_marker = '<span class="my-gap">~~</span>'
3405
+ +
3406
+ + paginate({ :per_page => 2 }, :inner_window => 0, :outer_window => 0, :renderer => renderer) do
3407
+ + assert_select 'span.my-gap', '~~'
3408
+ + end
3409
+ +
3410
+ + renderer = AdditionalLinkAttributesRenderer.new(:title => 'rendered')
3411
+ + paginate({}, :renderer => renderer) do
3412
+ + assert_select 'a[title=rendered]', 3
3413
+ + end
3414
+ + end
3415
+ +
3416
+ + def test_prev_next_links_have_classnames
3417
+ + paginate do |pagination|
3418
+ + assert_select 'span.disabled.prev_page:first-child'
3419
+ + assert_select 'a.next_page[href]:last-child'
3420
+ + end
3421
+ + end
3422
+ +
3423
+ + def test_full_output
3424
+ + paginate
3425
+ + expected = <<-HTML
3426
+ + <div class="pagination"><span class="disabled prev_page">&laquo; Previous</span>
3427
+ + <span class="current">1</span>
3428
+ + <a href="/foo/bar?page=2" rel="next">2</a>
3429
+ + <a href="/foo/bar?page=3">3</a>
3430
+ + <a href="/foo/bar?page=2" class="next_page" rel="next">Next &raquo;</a></div>
3431
+ + HTML
3432
+ + expected.strip!.gsub!(/\s{2,}/, ' ')
3433
+ +
3434
+ + assert_dom_equal expected, @html_result
3435
+ + end
3436
+ +
3437
+ + def test_escaping_of_urls
3438
+ + paginate({:page => 1, :per_page => 1, :total_entries => 2},
3439
+ + :page_links => false, :params => { :tag => '<br>' })
3440
+ +
3441
+ + assert_select 'a[href]', 1 do |links|
3442
+ + query = links.first['href'].split('?', 2)[1]
3443
+ + assert_equal %w(page=2 tag=%3Cbr%3E), query.split('&amp;').sort
3444
+ + end
3445
+ + end
3446
+ +
3447
+ + ## advanced options for pagination ##
3448
+ +
3449
+ + def test_will_paginate_without_container
3450
+ + paginate({}, :container => false)
3451
+ + assert_select 'div.pagination', 0, 'main DIV present when it shouldn\'t'
3452
+ + assert_select 'a[href]', 3
3453
+ + end
3454
+ +
3455
+ + def test_will_paginate_without_page_links
3456
+ + paginate({ :page => 2 }, :page_links => false) do
3457
+ + assert_select 'a[href]', 2 do |elements|
3458
+ + validate_page_numbers [1,3], elements
3459
+ + end
3460
+ + end
3461
+ + end
3462
+ +
3463
+ + def test_will_paginate_windows
3464
+ + paginate({ :page => 6, :per_page => 1 }, :inner_window => 1) do |pagination|
3465
+ + assert_select 'a[href]', 8 do |elements|
3466
+ + validate_page_numbers [5,1,2,5,7,10,11,7], elements
3467
+ + assert_select elements.first, 'a', '&laquo; Previous'
3468
+ + assert_select elements.last, 'a', 'Next &raquo;'
3469
+ + end
3470
+ + assert_select 'span.current', '6'
3471
+ + assert_equal '&laquo; Previous 1 2 &hellip; 5 6 7 &hellip; 10 11 Next &raquo;', pagination.first.inner_text
3472
+ + end
3473
+ + end
3474
+ +
3475
+ + def test_will_paginate_eliminates_small_gaps
3476
+ + paginate({ :page => 6, :per_page => 1 }, :inner_window => 2) do
3477
+ + assert_select 'a[href]', 12 do |elements|
3478
+ + validate_page_numbers [5,1,2,3,4,5,7,8,9,10,11,7], elements
3479
+ + end
3480
+ + end
3481
+ + end
3482
+ +
3483
+ + def test_container_id
3484
+ + paginate do |div|
3485
+ + assert_nil div.first['id']
3486
+ + end
3487
+ +
3488
+ + # magic ID
3489
+ + paginate({}, :id => true) do |div|
3490
+ + assert_equal 'fixnums_pagination', div.first['id']
3491
+ + end
3492
+ +
3493
+ + # explicit ID
3494
+ + paginate({}, :id => 'custom_id') do |div|
3495
+ + assert_equal 'custom_id', div.first['id']
3496
+ + end
3497
+ + end
3498
+ +
3499
+ + ## other helpers ##
3500
+ +
3501
+ + def test_paginated_section
3502
+ + @template = <<-ERB
3503
+ + <% paginated_section collection, options do %>
3504
+ + <%= content_tag :div, '', :id => "developers" %>
3505
+ + <% end %>
3506
+ + ERB
3507
+ +
3508
+ + paginate
3509
+ + assert_select 'div.pagination', 2
3510
+ + assert_select 'div.pagination + div#developers', 1
3511
+ + end
3512
+ +
3513
+ + def test_page_entries_info
3514
+ + @template = '<%= page_entries_info collection %>'
3515
+ + array = ('a'..'z').to_a
3516
+ +
3517
+ + paginate array.paginate(:page => 2, :per_page => 5)
3518
+ + assert_equal %{Displaying strings <b>6&nbsp;-&nbsp;10</b> of <b>26</b> in total},
3519
+ + @html_result
3520
+ +
3521
+ + paginate array.paginate(:page => 7, :per_page => 4)
3522
+ + assert_equal %{Displaying strings <b>25&nbsp;-&nbsp;26</b> of <b>26</b> in total},
3523
+ + @html_result
3524
+ + end
3525
+ +
3526
+ + def test_page_entries_info_with_longer_class_name
3527
+ + @template = '<%= page_entries_info collection %>'
3528
+ + collection = ('a'..'z').to_a.paginate
3529
+ + collection.first.stubs(:class).returns(mock('class', :name => 'ProjectType'))
3530
+ +
3531
+ + paginate collection
3532
+ + assert @html_result.index('project types'), "expected <#{@html_result.inspect}> to mention 'project types'"
3533
+ + end
3534
+ +
3535
+ + def test_page_entries_info_with_single_page_collection
3536
+ + @template = '<%= page_entries_info collection %>'
3537
+ +
3538
+ + paginate(('a'..'d').to_a.paginate(:page => 1, :per_page => 5))
3539
+ + assert_equal %{Displaying <b>all 4</b> strings}, @html_result
3540
+ +
3541
+ + paginate(['a'].paginate(:page => 1, :per_page => 5))
3542
+ + assert_equal %{Displaying <b>1</b> string}, @html_result
3543
+ +
3544
+ + paginate([].paginate(:page => 1, :per_page => 5))
3545
+ + assert_equal %{No entries found}, @html_result
3546
+ + end
3547
+ +
3548
+ + def test_page_entries_info_with_custom_entry_name
3549
+ + @template = '<%= page_entries_info collection, :entry_name => "author" %>'
3550
+ +
3551
+ + entries = (1..20).to_a
3552
+ +
3553
+ + paginate(entries.paginate(:page => 1, :per_page => 5))
3554
+ + assert_equal %{Displaying authors <b>1&nbsp;-&nbsp;5</b> of <b>20</b> in total}, @html_result
3555
+ +
3556
+ + paginate(entries.paginate(:page => 1, :per_page => 20))
3557
+ + assert_equal %{Displaying <b>all 20</b> authors}, @html_result
3558
+ +
3559
+ + paginate(['a'].paginate(:page => 1, :per_page => 5))
3560
+ + assert_equal %{Displaying <b>1</b> author}, @html_result
3561
+ +
3562
+ + paginate([].paginate(:page => 1, :per_page => 5))
3563
+ + assert_equal %{No authors found}, @html_result
3564
+ + end
3565
+ +
3566
+ + ## parameter handling in page links ##
3567
+ +
3568
+ + def test_will_paginate_preserves_parameters_on_get
3569
+ + @request.params :foo => { :bar => 'baz' }
3570
+ + paginate
3571
+ + assert_links_match /foo%5Bbar%5D=baz/
3572
+ + end
3573
+ +
3574
+ + def test_will_paginate_doesnt_preserve_parameters_on_post
3575
+ + @request.post
3576
+ + @request.params :foo => 'bar'
3577
+ + paginate
3578
+ + assert_no_links_match /foo=bar/
3579
+ + end
3580
+ +
3581
+ + def test_adding_additional_parameters
3582
+ + paginate({}, :params => { :foo => 'bar' })
3583
+ + assert_links_match /foo=bar/
3584
+ + end
3585
+ +
3586
+ + def test_adding_anchor_parameter
3587
+ + paginate({}, :params => { :anchor => 'anchor' })
3588
+ + assert_links_match /#anchor$/
3589
+ + end
3590
+ +
3591
+ + def test_removing_arbitrary_parameters
3592
+ + @request.params :foo => 'bar'
3593
+ + paginate({}, :params => { :foo => nil })
3594
+ + assert_no_links_match /foo=bar/
3595
+ + end
3596
+ +
3597
+ + def test_adding_additional_route_parameters
3598
+ + paginate({}, :params => { :controller => 'baz', :action => 'list' })
3599
+ + assert_links_match %r{\Wbaz/list\W}
3600
+ + end
3601
+ +
3602
+ + def test_will_paginate_with_custom_page_param
3603
+ + paginate({ :page => 2 }, :param_name => :developers_page) do
3604
+ + assert_select 'a[href]', 4 do |elements|
3605
+ + validate_page_numbers [1,1,3,3], elements, :developers_page
3606
+ + end
3607
+ + end
3608
+ + end
3609
+ +
3610
+ + def test_complex_custom_page_param
3611
+ + @request.params :developers => { :page => 2 }
3612
+ +
3613
+ + paginate({ :page => 2 }, :param_name => 'developers[page]') do
3614
+ + assert_select 'a[href]', 4 do |links|
3615
+ + assert_links_match /\?developers%5Bpage%5D=\d+$/, links
3616
+ + validate_page_numbers [1,1,3,3], links, 'developers[page]'
3617
+ + end
3618
+ + end
3619
+ + end
3620
+ +
3621
+ + def test_custom_routing_page_param
3622
+ + @request.symbolized_path_parameters.update :controller => 'dummy', :action => nil
3623
+ + paginate :per_page => 2 do
3624
+ + assert_select 'a[href]', 6 do |links|
3625
+ + assert_links_match %r{/page/(\d+)$}, links, [2, 3, 4, 5, 6, 2]
3626
+ + end
3627
+ + end
3628
+ + end
3629
+ +
3630
+ + def test_custom_routing_page_param_with_dot_separator
3631
+ + @request.symbolized_path_parameters.update :controller => 'dummy', :action => 'dots'
3632
+ + paginate :per_page => 2 do
3633
+ + assert_select 'a[href]', 6 do |links|
3634
+ + assert_links_match %r{/page\.(\d+)$}, links, [2, 3, 4, 5, 6, 2]
3635
+ + end
3636
+ + end
3637
+ + end
3638
+ +
3639
+ + def test_custom_routing_with_first_page_hidden
3640
+ + @request.symbolized_path_parameters.update :controller => 'ibocorp', :action => nil
3641
+ + paginate :page => 2, :per_page => 2 do
3642
+ + assert_select 'a[href]', 7 do |links|
3643
+ + assert_links_match %r{/ibocorp(?:/(\d+))?$}, links, [nil, nil, 3, 4, 5, 6, 3]
3644
+ + end
3645
+ + end
3646
+ + end
3647
+ +
3648
+ + ## internal hardcore stuff ##
3649
+ +
3650
+ + class LegacyCollection < WillPaginate::Collection
3651
+ + alias :page_count :total_pages
3652
+ + undef :total_pages
3653
+ + end
3654
+ +
3655
+ + def test_deprecation_notices_with_page_count
3656
+ + collection = LegacyCollection.new(1, 1, 2)
3657
+ +
3658
+ + assert_deprecated collection.class.name do
3659
+ + paginate collection
3660
+ + end
3661
+ + end
3662
+ +
3663
+ + uses_mocha 'view internals' do
3664
+ + def test_collection_name_can_be_guessed
3665
+ + collection = mock
3666
+ + collection.expects(:total_pages).returns(1)
3667
+ +
3668
+ + @template = '<%= will_paginate options %>'
3669
+ + @controller.controller_name = 'developers'
3670
+ + @view.assigns['developers'] = collection
3671
+ +
3672
+ + paginate(nil)
3673
+ + end
3674
+ + end
3675
+ +
3676
+ + def test_inferred_collection_name_raises_error_when_nil
3677
+ + @template = '<%= will_paginate options %>'
3678
+ + @controller.controller_name = 'developers'
3679
+ +
3680
+ + e = assert_raise ArgumentError do
3681
+ + paginate(nil)
3682
+ + end
3683
+ + assert e.message.include?('@developers')
3684
+ + end
3685
+ +
3686
+ + if ActionController::Base.respond_to? :rescue_responses
3687
+ + # only on Rails 2
3688
+ + def test_rescue_response_hook_presence
3689
+ + assert_equal :not_found,
3690
+ + ActionController::Base.rescue_responses['WillPaginate::InvalidPage']
3691
+ + end
3692
+ + end
3693
+ +
3694
+ +end
3695
+ diff --git a/will_paginate.gemspec b/will_paginate.gemspec
3696
+ new file mode 100644
3697
+ index 0000000..74ef32f
3698
+ --- /dev/null
3699
+ +++ b/will_paginate.gemspec
3700
+ @@ -0,0 +1,21 @@
3701
+ +Gem::Specification.new do |s|
3702
+ + s.name = 'will_paginate'
3703
+ + s.version = '2.3.2'
3704
+ + s.date = '2008-05-16'
3705
+ +
3706
+ + s.summary = "Most awesome pagination solution for Rails"
3707
+ + s.description = "The will_paginate library provides a simple, yet powerful and extensible API for ActiveRecord pagination and rendering of pagination links in ActionView templates."
3708
+ +
3709
+ + s.authors = ['Mislav Marohni-�', 'PJ Hyett']
3710
+ + s.email = 'mislav.marohnic@gmail.com'
3711
+ + s.homepage = 'http://github.com/mislav/will_paginate/wikis'
3712
+ +
3713
+ + s.has_rdoc = true
3714
+ + s.rdoc_options = ['--main', 'README.rdoc']
3715
+ + s.rdoc_options << '--inline-source' << '--charset=UTF-8'
3716
+ + s.extra_rdoc_files = ['README.rdoc', 'LICENSE', 'CHANGELOG']
3717
+ + s.add_dependency 'activesupport', ['>= 1.4.4']
3718
+ +
3719
+ + s.files = %w(CHANGELOG LICENSE README.rdoc Rakefile examples examples/apple-circle.gif examples/index.haml examples/index.html examples/pagination.css examples/pagination.sass init.rb lib lib/will_paginate lib/will_paginate.rb lib/will_paginate/array.rb lib/will_paginate/collection.rb lib/will_paginate/core_ext.rb lib/will_paginate/finder.rb lib/will_paginate/named_scope.rb lib/will_paginate/named_scope_patch.rb lib/will_paginate/version.rb lib/will_paginate/view_helpers.rb test test/boot.rb test/collection_test.rb test/console test/database.yml test/finder_test.rb test/fixtures test/fixtures/admin.rb test/fixtures/developer.rb test/fixtures/developers_projects.yml test/fixtures/project.rb test/fixtures/projects.yml test/fixtures/replies.yml test/fixtures/reply.rb test/fixtures/schema.rb test/fixtures/topic.rb test/fixtures/topics.yml test/fixtures/user.rb test/fixtures/users.yml test/helper.rb test/lib test/lib/activerecord_test_case.rb test/lib/activerecord_test_connector.rb test/lib/load_fixtures.rb test/lib/view_test_process.rb test/tasks.rake test/view_test.rb)
3720
+ + s.test_files = %w(test/boot.rb test/collection_test.rb test/console test/database.yml test/finder_test.rb test/fixtures test/fixtures/admin.rb test/fixtures/developer.rb test/fixtures/developers_projects.yml test/fixtures/project.rb test/fixtures/projects.yml test/fixtures/replies.yml test/fixtures/reply.rb test/fixtures/schema.rb test/fixtures/topic.rb test/fixtures/topics.yml test/fixtures/user.rb test/fixtures/users.yml test/helper.rb test/lib test/lib/activerecord_test_case.rb test/lib/activerecord_test_connector.rb test/lib/load_fixtures.rb test/lib/view_test_process.rb test/tasks.rake test/view_test.rb)
3721
+ +end