paged_scopes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Matthew Hollingworth
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,471 @@
1
+ h1. Paged Scopes: A Will_paginate Alternative
2
+
3
+ The first time I needed to paginate data in a Rails site, I went straight for the de-facto standard, which, since Rails 2.0, has undoubtedly been "will_paginate":http://wiki.github.com/mislav/will_paginate. However, it didn't take me long to discover it couldn't do all that I wanted it to.
4
+
5
+ Most importantly, I wanted to be able to redirect from a resource member action (the update action, say) back to the index action, with the page set so that the edited resource would be part of the paged list. I couldn't see a way to do that with will_paginate. I found the will_paginate helper a bit messy - ever heard of block helpers? And finally, I wanted my pages to be objects, not just numbers. This would let me load them in controllers and pass them to named routes and have them just work. Will_paginate didn't seem to fit the bill.
6
+
7
+ Now don't get me wrong; will_paginate must be pretty great - it's the "third most watched repo on GitHub":http://github.com/popular/watched as I write this. But choice is always good, and to me, will_paginate seems a bit bloated and ill-fitting to the way I like to structure my code.
8
+
9
+ So, naturally, I rolled my own pagination solution. I've finally packaged it up and released it as a new ActiveRecord pagination gem, _PagedScopes_. It's everything I need in Rails pagination and nothing I don't. It's also lightweight and pretty solid. Check it out!
10
+
11
+ h2. Features
12
+
13
+ The bullet-point summary of the PagedScopes gem goes something like this:
14
+
15
+ * Pages are instances of a class which belongs to the collection it's paginating;
16
+ * Pages can be found by number or by contained object;
17
+ * Each page has its own paged collection, which is a scope on the underlying collection; and
18
+ * Flexible, Digg-style pagination links are achieved using a block helper.
19
+
20
+ h2. A Console Session Is Worth a Thousand Words
21
+
22
+ Let's take a look at how pagination works with PagedScopes. Consider a collection of articles obtained using a <code>published</code> named scope.
23
+
24
+ <pre>
25
+ @articles = Article.published
26
+ => [#<Article id: 1, title: "Article #1">, ..., #<Article id: 5, title: "Article #5">]
27
+ @articles.count
28
+ => 5
29
+ </pre>
30
+
31
+ The PagedScopes gem adds a <code>per_page</code> attribute directly to <code>named_scope</code> collections (and to association collections, too). This value determines how many objects each page contains, and needs to be set before we can paginate the collection:
32
+
33
+ <pre>
34
+ @articles.per_page = 2
35
+ => 2
36
+ </pre>
37
+
38
+ Paginating this collection will now give us three pages.
39
+
40
+ How do we access these pages? By calling <code>pages</code>, the other main method added to <code>ActiveRecord</code> collections. It returns an enumerated class, the instances of which represent the pages of the collection. We can interact with the pages class in some familiar ways:
41
+
42
+ <pre>
43
+ @articles.pages
44
+ => #<Class:0x24ea99c>
45
+ @articles.pages.count
46
+ => 3
47
+ @articles.pages.first
48
+ => #<Page, for: Article, number: 1>
49
+ @articles.pages.find(1)
50
+ => #<Page, for: Article, number: 1>
51
+ @articles.pages.last
52
+ => #<Page, for: Article, number: 3>
53
+ @articles.pages.find(4)
54
+ => # PagedScopes::PageNotFound: couldn't find page number 4
55
+ @articles.pages.all
56
+ => [#<Page, for: Article, number: 1>, #<Page, for: Article, number: 2>, #<Page, for: Article, number: 3>]
57
+ @articles.first.to_param
58
+ => "1"
59
+ </pre>
60
+
61
+ Looks just like any other model - each page is its own self-contained object, as it should be. We can access the collection objects in the page using the same name as the underlying model. In our example, our collection contains <code>Article</code> instances, so the articles in the page are accessed using an <code>articles</code> method:
62
+
63
+ <pre>
64
+ @articles.pages.first.articles
65
+ => [#<Article id: 1, title: "Article #1">, #<Article id: 2, title: "Article #2">]
66
+ @articles.pages.last.articles
67
+ => [#<Article id: 5, title: "Article #5">]
68
+ @articles.pages.map(&:articles).map(&:size)
69
+ => [2, 2, 1]
70
+ @articles.pages.map { |page| page.articles.map(&:title) }
71
+ => [["Article #1", "Article #2"], ["Article #3", "Article #4"], ["Article #5"]]
72
+ </pre>
73
+
74
+ So far, so good. Bu what, exactly, is return by the <code>articles</code> method? Let's see:
75
+
76
+ <pre>
77
+ @articles.pages.first.articles.class
78
+ => ActiveRecord::NamedScope::Scope
79
+ @articles.pages.first.articles.send(:scope, :find)
80
+ => {:conditions=>"published_at IS NOT NULL", :offset=>0, :limit=>2}
81
+ @articles.pages.last.articles.send(:scope, :find)
82
+ => {:conditions=>"published_at IS NOT NULL", :offset=>4, :limit=>2}
83
+ @articles.send(:scope, :find)
84
+ => {:conditions=>"published_at IS NOT NULL"}
85
+ </pre>
86
+
87
+ Yep, it's just a scope on the parent collection, with <code>:limit</code> and <code>:offset</code> added according to the page number. This is kinda important. It means that the objects in the paged collection will not load from the database until they are referenced. We can pass around page objects in view helpers and named routes and so on, without worrying about inadvertently loading the paged data.
88
+
89
+ h2. Finding a Page By Its Contents
90
+
91
+ One particularly nice feature of the library is that we can find a page by identifying an object the page contains.
92
+
93
+ <pre>
94
+ article = Article.find(3)
95
+ => #<Article id: 3, title: "Article #3">
96
+ @articles.pages.find_by_article(article)
97
+ => #<Page, for: Article, number: 2>
98
+
99
+ article = articles.find(8)
100
+ => #<Article id: 8, title: "Article #8">
101
+ @articles.pages.find_by_article(article)
102
+ => nil
103
+ @articles.pages.find_by_article!(article)
104
+ => # PagedScopes::PageNotFound: #<Article id: 8, title: "Article #8"> not found in scope
105
+ </pre>
106
+
107
+ This is really handy if you want to redirect from a resource member action to the paged of the index containing the edited object. (More on this later.)
108
+
109
+ This is implemented using the code I described in my "previous post":http://code.matthewhollingworth.net/articles/2009-06-22-indexing-activerecord-objects-in-an-ordered-collection. As a result you get a couple of freebies on your ActiveRecord objects:
110
+
111
+ <pre>
112
+ article = Article.scoped(:order => "title ASC").find(3)
113
+ => #<Article id: 3, title: "Article #3">
114
+ article.next
115
+ => #<Article id: 4, title: "Article #4">
116
+
117
+ article = Article.scoped(:order => "title DESC").find(3)
118
+ => #<Article id: 3, title: "Article #3">
119
+ article.next
120
+ => #<Article id: 2, title: "Article #2">
121
+ article.previous
122
+ => #<Article id: 4, title: "Article #4">
123
+ </pre>
124
+
125
+ In other words, you can find the <code>next</code> and <code>previous</code> objects for any object in a collection. This provides an easy way to link to neighbouring objects (e.g. older and newer posts in a blog).
126
+
127
+ h2. A Caveat
128
+
129
+ It's important to store the paged scope or association collection in a variable, rather than refer to it directly. In other words:
130
+
131
+ <pre>
132
+ # Do this:
133
+ @articles = @user.articles.published # or whatever
134
+ => [#<Article ...>, ..., #<Article ...>]
135
+ @articles.per_page = 5
136
+ => 5
137
+ @articles.per_page
138
+ => 5
139
+
140
+ # Don't do this:
141
+ @user.articles.published.per_page = 5
142
+ => 5
143
+ @user.articles.published.per_page
144
+ => nil
145
+ </pre>
146
+
147
+ This is because paged scopes and association collections return new instances each time they're called. You need to hang onto them to set the <code>per_page</code> and then get the pages.
148
+
149
+ h2. Page Routing
150
+
151
+ The most common way to represent a paginated collection in an URL is to tack on the page number as a query paramater: <code>http://www.example.com/articles?page=3</code>, for example.
152
+
153
+ I'm not a fan of this approach at all. For starters, it's a bit ugly. More importantly, it won't work with standard Rails page caching, which ignores query parameters.
154
+
155
+ I prefer to think of pagination as just another scoping of the collection. Just as we have paths like <code>/users/9/articles</code>, I prefer a paged collection to have paths like <code>/pages/2/articles</code> (or <code>/users/9/pages/2/articles</code>, for that matter).
156
+
157
+ To this end, the Paged Scopes gem adds a <code>:paged</code> option to the Rails <code>resources</code> mapper. We'll use this option to define the routes for our articles:
158
+
159
+ <pre>
160
+ ActionController::Routing::Routes.draw do |map|
161
+ map.resources :articles, :paged => true
162
+ end
163
+ </pre>
164
+
165
+ Checking our routes using <code>rake routes</code>:
166
+
167
+ <pre>
168
+ articles GET /articles(.:format) {:controller=>"articles", :action=>"index"}
169
+ POST /articles(.:format) {:controller=>"articles", :action=>"create"}
170
+ new_article GET /articles/new(.:format) {:controller=>"articles", :action=>"new"}
171
+ edit_article GET /articles/:id/edit(.:format) {:controller=>"articles", :action=>"edit"}
172
+ article GET /articles/:id(.:format) {:controller=>"articles", :action=>"show"}
173
+ PUT /articles/:id(.:format) {:controller=>"articles", :action=>"update"}
174
+ DELETE /articles/:id(.:format) {:controller=>"articles", :action=>"destroy"}
175
+ page_articles GET /pages/:page_id/articles(.:format) {:controller=>"articles", :action=>"index"}
176
+ </pre>
177
+
178
+ Just your standard set of resource routes, with one extra - the paged articles index route, last in the list. Specifying the <code>:paged</code> option in the mapping yields this extra route for use in our index actions. (Everything else remains the same.)
179
+
180
+ Want a bit more flexibility? We can pass <code>:as</code> or <code>:name</code> options to the paged option if needed:
181
+
182
+ <pre>
183
+ map.resources :articles, :paged => { :as => :pagina }
184
+ map.resources :users, :paged => { :name => :group }
185
+ </pre>
186
+
187
+ Which would produce these routes:
188
+
189
+ <pre>
190
+ page_articles GET /pagina/:page_id/articles(.:format) {:controller=>"articles", :action=>"index"}
191
+ group_users GET /groups/:group_id/users(.:format) {:controller=>"users", :action=>"index"}
192
+ </pre>
193
+
194
+ (This is likely only to be useful in rare situations. One example would be paginating more than one collection in a single view.)
195
+
196
+ h2. Controller Methods
197
+
198
+ OK, so we have our pages represented in our article index route. Let's turn to the articles controller next.
199
+
200
+ I believe there is diverging practice on this, but in controllers I always prefer to load the collection and object in before filters, typically along the lines of:
201
+
202
+ <pre>
203
+ class ArticlesController < ApplicationController
204
+ before_filter :get_articles
205
+ before_filter :get_article, :only => [ :show, :edit, :update, :destroy ]
206
+ before_filter :new_article, :only => [ :new, :create ]
207
+
208
+ # actions here ...
209
+
210
+ protected
211
+
212
+ def get_articles
213
+ @articles = @user.articles.scoped(:order => "created_at DESC") # or whatever
214
+ end
215
+
216
+ def get_article
217
+ @article = @articles.find_from_param(params[:id])
218
+ end
219
+
220
+ def new_article
221
+ @article = @articles.new(params[:article])
222
+ end
223
+ end
224
+ </pre>
225
+
226
+ It's a very consistent way to write RESTful controllers. The <code>@articles</code> collection is _always_ created, which is OK, since it's just a scope or an association and no records are actually loaded. For the member actions, the collection instance is either loaded from the collection or built from it, depending on whether the action is creating a new record (new, create) or modifying an existing once (show, edit, update, destroy).
227
+
228
+ Using this pattern, paginating the collection fits naturally as another before filter once the collection is set. To this end, Paged Scopes provides a tailored <code>paginate</code> class method to do just that:
229
+
230
+ <pre>
231
+ class ArticlesController < ApplicationController
232
+ before_filter :get_articles
233
+ before_filter :get_article, :only => [ :show, :edit, :update, :destroy ]
234
+ before_filter :new_article, :only => [ :new, :create ]
235
+
236
+ paginate :articles, :per_page => 3, :path => :page_articles_path
237
+
238
+ ...
239
+
240
+ </pre>
241
+
242
+ This <code>paginate</code> method basically adds another <code>before_filter</code> which loads the current page from the collection. As arguments, it takes an optional collection name and an options hash. If omitted, the collection name is inferred from the controller name. (Hence, in the above example, we could have omitted the <code>:artices</code> arguments and <code>@articles</code> would then be inferred from the <code>ArticlesController</code> name. Hurrah for naming conventions!)
243
+
244
+ You can pass a few options to the <code>paginate</code> method:
245
+
246
+ * A <code>:per_page</code> option sets the page size on the collection if you specify it. (This option can be omitted if <code>per_page</code> has already been set on the collection.)
247
+ * A <code>:path</code> option will set the path proc for the paginator to be the controller method you specify. In the above example we've set it to a named route (<code>page_articles_path</code>), but it could equally well be a method you've defined later in the controller. (This could be useful if you want to use a polymorphic path, for example.)
248
+ * a <code>:name</code> option is available if you want to refer to your pages by a different class name (unlikely).
249
+
250
+ Any other options will be passed through to the filter definition. So you can use filter options, such as <code>:if</code>, <code>:only</code> and <code>:except</code>, just as you would for any other filter.
251
+
252
+ Aside from setting the options you specify, the main job of the <code>paginate</code> filter is to set the page as an instance variable. Controller actions will then have a <code>@page</code> variable available to be used for pagination. The page number is determined from three locations in order of priority.
253
+
254
+ # If an object of the collection is present (an <code>@article</code>, in our example), the page containing that object is loaded (unless the object is a new record).
255
+ # Failing that, the request params are examine for a <code>:page_id</code>. If present, that page number is loaded. (This fits with the paged resource routes described earlier.)
256
+ # Failing that, the first page is loaded by default.
257
+
258
+ Loading the page for a member action (show, edit, update) might not seem useful at first. Its utility becomes apparent when we're redirecting though:
259
+
260
+ <pre>
261
+ def update
262
+ if @article.save
263
+ flash[:notice] = "Success!"
264
+ redirect_to page_articles_path(@page)
265
+ else
266
+ ...
267
+ end
268
+ end
269
+ </pre>
270
+
271
+ The page is used to redirect to the index at the page containing the edited object. Very polite to users! (Views can also link back to the paged index in a similar manner.)
272
+
273
+ h2. Pagination Links
274
+
275
+ The basic idea is to render a row of numbered links for a few pages either side of the one being viewed. This is referred to as the _inner window_. An _outer window_ is often also included - this shows links for the first and last few pages at the start and end of the list. Usually, _next page_ and _previous page_ links are also sandwiched around the numbered links.
276
+
277
+ The "will_paginate rdoc":http://gitrdoc.com/mislav/will_paginate/tree/master/ has some good links to articles on pagination UI design:
278
+
279
+ * a "Yahoo Design Pattern Library article":http://developer.yahoo.com/ypatterns/parent.php?pattern=pagination describing two styles of pagination;
280
+ * a "Smashing Magazine article":http://www.smashingmagazine.com/2007/11/16/pagination-gallery-examples-and-good-practices/ with good practices and examples; and
281
+ * "another article":http://kurafire.net/log/archive/2007/06/22/pagination-101 with heaps of examples, both good and bad.
282
+
283
+ In the "will_paginate":http://wiki.github.com/mislav/will_paginate gem, the eponymous <code>will_paginate</code> view helper is provided to render these links in your view. It seems to work well, but one look at the method's options gives you an idea what you'll be up for if you want to customize the HTML structure of your pagination links. Want to render your pagination links as a list? You'll have to write your own <code>LinkRenderer</code> subclass. (Have fun with that.)
284
+
285
+ There has to be a better way. There is of course, and it comes from a less-is-more approach.
286
+
287
+ h2. Using the Window Helper
288
+
289
+ With the PagedScopes gem, each page has an associated <code>paginator</code> which provides some simple methods for generating page links. First, we need to call <code>set_path</code> to tell the paginator how to generate links for a pages:
290
+
291
+ <pre>
292
+ @page.paginator.set_path { |page| page_articles_path(page) }
293
+ </pre>
294
+
295
+ The block we supply will be used by the paginator to generate a paged URL whenever one is needed.
296
+
297
+ (Note that the controller <code>paginate</code> method I presented in the last article can also be used to set the path proc by using the <code>:path</code> option.)
298
+
299
+ Next, we use the <code>window</code> method to render the page links. We supply a block which the paginator will call for each page in the window, allowing us to render the link exactly as we want tp. Let's render that list we were talking about:
300
+
301
+ <pre>
302
+ <ul>
303
+ <% @page.paginator.window(:inner => 2, :outer => 1) do |page, path, classes| %>
304
+ <% content_tag_for :li, page, :class => classes.join(" ") do %>
305
+ <%= link_to_if path, page.number, path %>
306
+ <% end %>
307
+ <% end %>
308
+ </ul>
309
+ </pre>
310
+
311
+ Here we've specified an inner window of size 2 (meaning we want links for two pages either side of the current page) and an outer window of size 1 (meaning we want links for just the first and last pages).
312
+
313
+ The <code>window</code> helper passes a succession of pages to our block for us to render. The block arguments are:
314
+
315
+ # The page itself, from which we can get the page number.
316
+ # The path for the page, produced using the <code>set_path</code> proc we've already specified. If the page is the current page, then nil is passed as the path - this is because we shouldn't render a link for the current page. (Hence our use of <code>link_to_if</code>.)
317
+ # An optional array of classes describing the link. Possible values for the classes are <code>:selected</code> if the page is the current page, <code>:gap_before</code> if there's a gap in the numbering before the page, and <code>:gap_after</code> if there's a gap after. You can use these as you see fit, but they're intended to be passed through to your link container as classes for styling. (We've done this above with the <code>:class => classes.join(" ")</code> option.)
318
+
319
+ Within the block, the page link can be rendered as we please. In our example we're putting it inside an <code><li></code> element. For page 7, the <code>window</code> function would produce the following markup:
320
+
321
+ <pre>
322
+ <ul>
323
+ <li class="page gap_after" id="page_1">
324
+ <span><a href="/pages/1/articles">1</a></span>
325
+ </li>
326
+ <li class="page gap_before" id="page_5">
327
+ <span><a href="/pages/5/articles">5</a></span>
328
+ </li>
329
+ <li class="page" id="page_6">
330
+ <span><a href="/pages/6/articles">6</a></span>
331
+ </li>
332
+ <li class="page selected" id="page_7">
333
+ <span>7</span>
334
+ </li>
335
+ <li class="page" id="page_8">
336
+ <span><a href="/pages/8/articles">8</a></span>
337
+ </li>
338
+ <li class="page gap_after" id="page_9">
339
+ <span><a href="/pages/9/articles">9</a></span>
340
+ </li>
341
+ <li class="page gap_before" id="page_12">
342
+ <span><a href="/pages/12/articles">12</a></span>
343
+ </li>
344
+ </ul>
345
+ </pre>
346
+
347
+ h2. Styling the Output
348
+
349
+ Add some styling, using our classes to distinguish the currently selected pages and to add a separator where there are numbering gaps:
350
+
351
+ <pre>
352
+ li.page { display: inline }
353
+ li.page a { text-decoration: none }
354
+ li.page span {
355
+ border: 1px solid gray;
356
+ padding: 0.2em 0.5em }
357
+ li.page.selected span, li.page span:hover {
358
+ background: gray;
359
+ color: white }
360
+ li.page.gap_before:before { content: "..." }
361
+ </pre>
362
+
363
+ The result: a nice-looking set of page links.
364
+
365
+ [Refer to the original article at "code.matthewhollingworth.net":http://code.matthewhollingworth.net/articles/11 for correctly rendered examples!]
366
+
367
+ <notextile>
368
+ <ul class="pgex">
369
+ <li class="page gap_after" id="page_1">
370
+ <span><a href="#">1</a></span>
371
+ </li>
372
+ <li class="page gap_before" id="page_5">
373
+ <span><a href="#">5</a></span>
374
+ </li>
375
+ <li class="page" id="page_6">
376
+ <span><a href="#">6</a></span>
377
+ </li>
378
+ <li class="page selected" id="page_7">
379
+ <span>7</span>
380
+ </li>
381
+ <li class="page" id="page_8">
382
+ <span><a href="#">8</a></span>
383
+ </li>
384
+ <li class="page gap_after" id="page_9">
385
+ <span><a href="#">9</a></span>
386
+ </li>
387
+ <li class="page gap_before" id="page_12">
388
+ <span><a href="#">12</a></span>
389
+ </li>
390
+ </ul>
391
+ </notextile>
392
+
393
+ Too easy!
394
+
395
+ h2. Adding Extra Controls
396
+
397
+ How do we get add _previous_ and _next_ links? This is pretty easy, too - just specify the <code>:extras</code> we want as an option. (Choose from <code>:first</code>, <code>:previous</code>, <code>:next</code> and <code>:last</code>.) Those symbols will be passed to our block as the page when they need to be rendered.
398
+
399
+ We'll move our pagination links to a helper for clarity:
400
+
401
+ <pre>
402
+ module ArticlesHelper
403
+ MARKER = { :previous => "&lt; newer", :next => "older &gt;" }
404
+ def article_page_links
405
+ @page.paginator.window(:inner => 2, :outer => 1, :extras => [ :previous, :next ]) do |page, path, classes|
406
+ content_tag :li, :class => (classes << :page).join(" ") do
407
+ content_tag :li, link_to_if(path, MARKER[page] || page.number, path)
408
+ end
409
+ end
410
+ end
411
+ end
412
+ </pre>
413
+
414
+ Which renders as follows (for page 4 this time):
415
+
416
+ [Refer to the original article at "code.matthewhollingworth.net":http://code.matthewhollingworth.net/articles/11 for correctly rendered examples!]
417
+
418
+ <notextile>
419
+ <ul class="pgex">
420
+ <li class="page">
421
+ <span><a href="#">&lt; newer</a></span>
422
+ </li>
423
+ <li class="page">
424
+ <span><a href="#">1</a></span>
425
+ </li>
426
+ <li class="page">
427
+ <span><a href="#">2</a></span>
428
+ </li>
429
+ <li class="page">
430
+ <span><a href="#">3</a></span>
431
+ </li>
432
+ <li class="page selected">
433
+ <span>4</span>
434
+ </li>
435
+ <li class="page">
436
+ <span><a href="#">5</a></span>
437
+ </li>
438
+ <li class="page gap_after">
439
+ <span><a href="#">6</a></span>
440
+ </li>
441
+ <li class="page gap_before">
442
+ <span><a href="#">12</a></span>
443
+ </li>
444
+ <li class="page">
445
+ <span><a href="#">older &gt;</a></span>
446
+ </li>
447
+ </ul>
448
+ </notextile>
449
+
450
+ Just what we want!
451
+
452
+ Links for the <code>:first</code> and <code>:last</code> pages can also be specified as extras; these will appear outside the _previous_ and _next_ links. (If you use these extras, you'll want to omit the <code>:outer</code> window option.)
453
+
454
+ h2. Get It!
455
+
456
+ You can install the PagedScopes gem as follows:
457
+
458
+ <pre>
459
+ gem sources -a http://gems.github.com # just once
460
+ sudo gem install mholling-paged_scopes
461
+ </pre>
462
+
463
+ And in your <code>config/environment.rb</code>, if you're on Rails:
464
+
465
+ <pre>
466
+ config.gem "mholling-paged_scopes", :lib => "paged_scopes", :source => "http://gems.github.com"
467
+ </pre>
468
+
469
+ Peruse the code at "GitHub":http://github.com/mholling/paged_scopes.
470
+
471
+ Copyright (c) 2009 Matthew Hollingworth. See LICENSE for details.