artemv-diff_to_html 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +9 -12
- data/examples/diff.css +74 -67
- data/examples/diff.git +3721 -0
- data/examples/diff.svn +175 -0
- data/examples/test.rb +94 -18
- data/examples/test_git_diff.rb +5 -0
- data/examples/test_svn_diff.rb +8 -0
- data/lib/diff_to_html.rb +100 -53
- metadata +6 -2
data/README.rdoc
CHANGED
@@ -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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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.
|
data/examples/diff.css
CHANGED
@@ -1,68 +1,75 @@
|
|
1
|
-
ul
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
font-size
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
border:1px solid #
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
}
|
data/examples/diff.git
ADDED
@@ -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">…</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">« 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">…</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next »</a>'
|
486
|
+
+- pagination_no_page_links = '<span class="disabled prev_page">« Previous</span> <a href="./?page=2" rel="next" class="next_page">Next »</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('<', '<').gsub('>', '>')
|
510
|
+
+
|
511
|
+
+ %h2 Digg-style, extra content
|
512
|
+
+ .digg_pagination
|
513
|
+
+ .page_info Displaying entries <b>1 - 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('<', '<').gsub('>', '>')
|
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">« 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">…</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next »</a>
|
584
|
+
+ </div>
|
585
|
+
+ <h2>Digg.com</h2>
|
586
|
+
+ <div class='digg_pagination'>
|
587
|
+
+ <span class="disabled prev_page">« 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">…</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next »</a>
|
588
|
+
+ </div>
|
589
|
+
+ <h2>Digg-style, no page links</h2>
|
590
|
+
+ <div class='digg_pagination'>
|
591
|
+
+ <span class="disabled prev_page">« Previous</span> <a href="./?page=2" rel="next" class="next_page">Next »</a>
|
592
|
+
+ </div>
|
593
|
+
+ <p>Code that renders this:</p>
|
594
|
+
+ <pre>
|
595
|
+
+ <code><%= will_paginate @posts, :page_links => false %></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 - 6</b> of <b>180</b> in total
|
601
|
+
+ </div>
|
602
|
+
+ <span class="disabled prev_page">« 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">…</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next »</a>
|
603
|
+
+ </div>
|
604
|
+
+ <p>Code that renders this:</p>
|
605
|
+
+ <pre>
|
606
|
+
+ <code><div class="digg_pagination">
|
607
|
+
+ <div clas="page_info">
|
608
|
+
+ <%= page_entries_info @posts %>
|
609
|
+
+ </div>
|
610
|
+
+ <%= will_paginate @posts, :container => false %>
|
611
|
+
+ </div></code>
|
612
|
+
+ </pre>
|
613
|
+
+ <h2>Apple.com store</h2>
|
614
|
+
+ <div class='apple_pagination'>
|
615
|
+
+ <span class="disabled prev_page">« 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">…</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next »</a>
|
616
|
+
+ </div>
|
617
|
+
+ <h2>Flickr.com</h2>
|
618
|
+
+ <div class='flickr_pagination'>
|
619
|
+
+ <span class="disabled prev_page">« 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">…</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next »</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("« Previous", :page => prev_page) : content_tag(:span, "« Previous", :class => 'disabled'))
|
866
|
+
- pgn << (page < last_page ? link_to("Next »", :page => next_page) : content_tag(:span, "Next »", :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 => '« Previous',
|
1651
|
+
+ :next_label => 'Next »',
|
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 - %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">…</span>
|
1817
|
+
+ attr_accessor :gap_marker
|
1818
|
+
+
|
1819
|
+
+ def initialize
|
1820
|
+
+ @gap_marker = '<span class="gap">…</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!((?:\?|&)#{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 »"
|
3363
|
+
+ end
|
3364
|
+
+ assert_select 'span', 2
|
3365
|
+
+ assert_select 'span.disabled:first-child', '« Previous'
|
3366
|
+
+ assert_select 'span.current', '1'
|
3367
|
+
+ assert_equal '« Previous 1 2 3 Next »', 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">« 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 »</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('&').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', '« Previous'
|
3468
|
+
+ assert_select elements.last, 'a', 'Next »'
|
3469
|
+
+ end
|
3470
|
+
+ assert_select 'span.current', '6'
|
3471
|
+
+ assert_equal '« Previous 1 2 … 5 6 7 … 10 11 Next »', 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 - 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 - 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 - 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
|