artemv-diff_to_html 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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