Upgraded to Rails 2.1. This can have wide ranging consequences, so please help track down any issues introduced by the upgrade. Requires environment.rb modifications.

Changes you will need to make:

 * In your environment.rb, you will need to update references to a few files per environment.rb.tmpl
 * In your environment.rb, you will need to specify the local time zone of the computer that is running your Tracks install.

Other notes on my changes:

 * Modified our code to take advantage of Rails 2.1's slick time zone support.
 * Upgraded will_paginate for compatibility
 * Hacked the Selenium on Rails plugin, which has not been updated in some time and does not support Rails 2.1
 * Verified that all tests pass on my machine, including Selenium tests -- I'd like confirmation from others, too.
This commit is contained in:
Luke Melia 2008-06-17 01:13:25 -04:00
parent f3bae73868
commit 901a58f8a3
1086 changed files with 51452 additions and 19526 deletions

View file

@ -7,6 +7,8 @@ if envs.include? RAILS_ENV
require 'selenium_controller'
require File.dirname(__FILE__) + '/routes'
SeleniumController.prepend_view_path File.expand_path(File.dirname(__FILE__) + '/lib/views')
else
#erase all traces
$LOAD_PATH.delete lib_path

View file

@ -13,13 +13,8 @@ module SeleniumOnRails
File.expand_path(File.dirname(__FILE__) + '/../views/' + view)
end
# Returns the path to the layout template. The path is relative in relation
# to the app/views/ directory since Rails doesn't support absolute paths
# to layout templates.
def layout_path
rails_root = Pathname.new File.expand_path(File.join(RAILS_ROOT, 'app/views'))
view_path = Pathname.new view_path('layout')
view_path.relative_path_from(rails_root).to_s
'/layout.rhtml'
end
def fixtures_path

View file

@ -8,7 +8,7 @@
# See SeleniumOnRails::TestBuilder for a list of available commands.
class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder
end
ActionView::Base.register_template_handler 'rsel', SeleniumOnRails::RSelenese
ActionView::Template.register_template_handler 'rsel', SeleniumOnRails::RSelenese
class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder
attr_accessor :view
@ -20,16 +20,16 @@ class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder
end
# Render _template_ using _local_assigns_.
def render template, local_assigns
title = (@view.assigns['page_title'] or local_assigns['page_title'])
def render template
title = @view.assigns['page_title']
table(title) do
test = self #to enable test.command
assign_locals_code = ''
local_assigns.each_key {|key| assign_locals_code << "#{key} = local_assigns[#{key.inspect}];"}
eval assign_locals_code + "\n" + template
eval template.source
end
end
def compilable?
false
end
end

View file

@ -1,6 +1,6 @@
class SeleniumOnRails::Selenese
end
ActionView::Base.register_template_handler 'sel', SeleniumOnRails::Selenese
ActionView::Template.register_template_handler 'sel', SeleniumOnRails::Selenese
class SeleniumOnRails::Selenese
@ -8,8 +8,8 @@ class SeleniumOnRails::Selenese
@view = view
end
def render template, local_assigns
name = (@view.assigns['page_title'] or local_assigns['page_title'])
def render template
name = @view.assigns['page_title']
lines = template.strip.split "\n"
html = ''
html << extract_comments(lines)
@ -18,6 +18,10 @@ class SeleniumOnRails::Selenese
raise 'You cannot have comments in the middle of commands!' if next_line lines, :any
html
end
def compilable?
false
end
private
def next_line lines, expects

View file

@ -1,2 +1,4 @@
/pkg
/doc
/rails
*.gem
/coverage

49
vendor/plugins/will_paginate/.manifest vendored Normal file
View file

@ -0,0 +1,49 @@
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

92
vendor/plugins/will_paginate/CHANGELOG vendored Normal file
View file

@ -0,0 +1,92 @@
== master
* ActiveRecord 2.1: remove :include from count query when tables are not
referenced in :conditions
== 2.3.2, released 2008-05-16
* Fixed LinkRenderer#stringified_merge by removing "return" from iterator block
* Ensure that 'href' values in pagination links are escaped URLs
== 2.3.1, released 2008-05-04
* Fixed page numbers not showing with custom routes and implicit first page
* Try to use Hanna for documentation (falls back to default RDoc template if not)
== 2.3.0, released 2008-04-29
* Changed LinkRenderer to receive collection, options and reference to view template NOT in
constructor, but with the #prepare method. This is a step towards supporting passing of
LinkRenderer (or subclass) instances that may be preconfigured in some way
* LinkRenderer now has #page_link and #page_span methods for easier customization of output in
subclasses
* Changed page_entries_info() method to adjust its output according to humanized class name of
collection items. Override this with :entry_name parameter (singular).
page_entries_info(@posts)
#-> "Displaying all 12 posts"
page_entries_info(@posts, :entry_name => 'item')
#-> "Displaying all 12 items"
== 2.2.3, released 2008-04-26
* will_paginate gem is no longer published on RubyForge, but on
gems.github.com:
gem sources -a http://gems.github.com/ (you only need to do this once)
gem install mislav-will_paginate
* extract reusable pagination testing stuff into WillPaginate::View
* rethink the page URL construction mechanizm to be more bulletproof when
combined with custom routing for page parameter
* test that anchor parameter can be used in pagination links
== 2.2.2, released 2008-04-21
* Add support for page parameter in custom routes like "/foo/page/2"
* Change output of "page_entries_info" on single-page collection and erraneous
output with empty collection as reported by Tim Chater
== 2.2.1, released 2008-04-08
* take less risky path when monkeypatching named_scope; fix that it no longer
requires ActiveRecord::VERSION
* use strings in "respond_to?" calls to work around a bug in acts_as_ferret
stable (ugh)
* add rake release task
== 2.2.0, released 2008-04-07
=== API changes
* Rename WillPaginate::Collection#page_count to "total_pages" for consistency.
If you implemented this interface, change your implementation accordingly.
* Remove old, deprecated style of calling Array#paginate as "paginate(page,
per_page)". If you want to specify :page, :per_page or :total_entries, use a
parameter hash.
* Rename LinkRenderer#url_options to "url_for" and drastically optimize it
=== View changes
* Added "prev_page" and "next_page" CSS classes on previous/next page buttons
* Add examples of pagination links styling in "examples/index.html"
* Change gap in pagination links from "..." to
"<span class="gap">&hellip;</span>".
* Add "paginated_section", a block helper that renders pagination both above and
below content in the block
* Add rel="prev|next|start" to page links
=== Other
* Add ability to opt-in for Rails 2.1 feature "named_scope" by calling
WillPaginate.enable_named_scope (tested in Rails 1.2.6 and 2.0.2)
* Support complex page parameters like "developers[page]"
* Move Array#paginate definition to will_paginate/array.rb. You can now easily
use pagination on arrays outside of Rails:
gem 'will_paginate'
require 'will_paginate/array'
* Add "paginated_each" method for iterating through every record by loading only
one page of records at the time
* Rails 2: Rescue from WillPaginate::InvalidPage error with 404 Not Found by
default

View file

@ -1,180 +0,0 @@
= WillPaginate
Pagination is just limiting the number of records displayed. Why should you let
it get in your way while developing, then? This plugin makes magic happen. Did
you ever want to be able to do just this on a model:
Post.paginate :page => 1, :order => 'created_at DESC'
... and then render the page links with a single view helper? Well, now you
can.
Ryan Bates made an awesome screencast[http://railscasts.com/episodes/51],
check it out.
Your mind reels with questions? Join our Google
group[http://groups.google.com/group/will_paginate].
== Installation
Will Paginate officially supports Rails versions 1.2.6 and 2.0.x.
Previously, the plugin was available on the following SVN location:
svn://errtheblog.com/svn/plugins/will_paginate
In February 2008, it moved to GitHub[http://github.com/mislav/will_paginate/tree]
to be tracked with git. The SVN repo continued to have updates, but not
forever. Therefore you should switch to using the gem:
gem install will_paginate --no-ri
After that, you can remove the plugin from your applications and add
a simple require to the end of config/environment.rb:
require 'will_paginate'
That's it, just remember to install the gem on all machines that
you are deploying to.
The second option is to download and extract the tarball from GitHub. Here is the
link for downloading the current state of the master branch:
http://github.com/mislav/will_paginate/tarball/master
Extract it to <tt>vendor/plugins</tt>. The directory will have a default name
like "mislav-will_paginate-master"; you can rename it to "will_paginate" for
simplicity.
== Example usage
Use a paginate finder in the controller:
@posts = Post.paginate_by_board_id @board.id, :page => params[:page], :order => 'updated_at DESC'
Yeah, +paginate+ works just like +find+ -- it just doesn't fetch all the
records. Don't forget to tell it which page you want, or it will complain!
Read more on WillPaginate::Finder::ClassMethods.
Render the posts in your view like you would normally do. When you need to render
pagination, just stick this in:
<%= will_paginate @posts %>
You're done. (Copy and paste the example fancy CSS styles from the bottom.) You
can find the option list at WillPaginate::ViewHelpers.
How does it know how much items to fetch per page? It asks your model by calling
its <tt>per_page</tt> class method. You can define it like this:
class Post < ActiveRecord::Base
cattr_reader :per_page
@@per_page = 50
end
... or like this:
class Post < ActiveRecord::Base
def self.per_page
50
end
end
... or don't worry about it at all. WillPaginate defines it to be <b>30</b> by default.
But you can always specify the count explicitly when calling +paginate+:
@posts = Post.paginate :page => params[:page], :per_page => 50
The +paginate+ finder wraps the original finder and returns your resultset that now has
some new properties. You can use the collection as you would with any ActiveRecord
resultset. WillPaginate view helpers also need that object to be able to render pagination:
<ol>
<% for post in @posts -%>
<li>Render `post` in some nice way.</li>
<% end -%>
</ol>
<p>Now let's render us some pagination!</p>
<%= will_paginate @posts %>
More detailed documentation:
* WillPaginate::Finder::ClassMethods for pagination on your models;
* WillPaginate::ViewHelpers for your views.
== Oh noes, a bug!
Tell us what happened so we can fix it, quick! Issues are filed on the Lighthouse project:
http://err.lighthouseapp.com/projects/466-plugins/tickets?q=tagged:will_paginate
Steps to make an awesome bug report:
1. Run <tt>rake test</tt> in the <i>will_paginate</i> directory. (You will need SQLite3.)
Copy the output if there are failing tests.
2. Register on Lighthouse to create a new ticket.
3. Write a descriptive, short title. Provide as much info as you can in the body.
Assign the ticket to Mislav and tag it with meaningful tags, <tt>"will_paginate"</tt>
being among them.
4. Yay! You will be notified on updates automatically.
Here is an example of a great bug report and patch:
http://err.lighthouseapp.com/projects/466/tickets/172-total_entries-ignored-in-paginate_by_sql
== Authors, credits, contact
Want to discuss, request features, ask questions? Join the Google group:
http://groups.google.com/group/will_paginate
Authors:: Mislav Marohnić, PJ Hyett
Original announcement:: http://errtheblog.com/post/929
Original PHP source:: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php
All these people helped making will_paginate what it is now with their code
contributions or simply awesome ideas:
Chris Wanstrath, Dr. Nic Williams, K. Adam Christensen, Mike Garey, Bence
Golda, Matt Aimonetti, Charles Brian Quinn, Desi McAdam, James Coglan, Matijs
van Zuijlen, Maria, Brendan Ribera, Todd Willey, Bryan Helmkamp, Jan Berkel.
== Usable pagination in the UI
Copy the following CSS into your stylesheet for a good start:
.pagination {
padding: 3px;
margin: 3px;
}
.pagination a {
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #aaaadd;
text-decoration: none;
color: #000099;
}
.pagination a:hover, .pagination a:active {
border: 1px solid #000099;
color: #000;
}
.pagination span.current {
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #000099;
font-weight: bold;
background-color: #000099;
color: #FFF;
}
.pagination span.disabled {
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #eee;
color: #ddd;
}
More reading about pagination as design pattern:
* Pagination 101:
http://kurafire.net/log/archive/2007/06/22/pagination-101
* Pagination gallery:
http://www.smashingmagazine.com/2007/11/16/pagination-gallery-examples-and-good-practices/
* Pagination on Yahoo Design Pattern Library:
http://developer.yahoo.com/ypatterns/parent.php?pattern=pagination

131
vendor/plugins/will_paginate/README.rdoc vendored Normal file
View file

@ -0,0 +1,131 @@
= WillPaginate
Pagination is just limiting the number of records displayed. Why should you let
it get in your way while developing, then? This plugin makes magic happen. Did
you ever want to be able to do just this on a model:
Post.paginate :page => 1, :order => 'created_at DESC'
... and then render the page links with a single view helper? Well, now you
can.
Some resources to get you started:
* Your mind reels with questions? Join our
{Google group}[http://groups.google.com/group/will_paginate].
* The will_paginate project page: http://github.com/mislav/will_paginate
* How to report bugs: http://github.com/mislav/will_paginate/wikis/report-bugs
* Ryan Bates made an awesome screencast[http://railscasts.com/episodes/51],
check it out.
== Installation
The recommended way is that you get the gem:
gem install mislav-will_paginate --source http://gems.github.com/
After that you don't need the will_paginate <i>plugin</i> in your Rails
application anymore. Just add a simple require to the end of
"config/environment.rb":
gem 'mislav-will_paginate', '~> 2.2'
require 'will_paginate'
That's it. Remember to install the gem on <b>all</b> machines that you are
deploying to.
<i>There are extensive
{installation instructions}[http://github.com/mislav/will_paginate/wikis/installation]
on {the wiki}[http://github.com/mislav/will_paginate/wikis].</i>
== Example usage
Use a paginate finder in the controller:
@posts = Post.paginate_by_board_id @board.id, :page => params[:page], :order => 'updated_at DESC'
Yeah, +paginate+ works just like +find+ -- it just doesn't fetch all the
records. Don't forget to tell it which page you want, or it will complain!
Read more on WillPaginate::Finder::ClassMethods.
Render the posts in your view like you would normally do. When you need to render
pagination, just stick this in:
<%= will_paginate @posts %>
You're done. (Copy and paste the example fancy CSS styles from the bottom.) You
can find the option list at WillPaginate::ViewHelpers.
How does it know how much items to fetch per page? It asks your model by calling
its <tt>per_page</tt> class method. You can define it like this:
class Post < ActiveRecord::Base
cattr_reader :per_page
@@per_page = 50
end
... or like this:
class Post < ActiveRecord::Base
def self.per_page
50
end
end
... or don't worry about it at all. WillPaginate defines it to be <b>30</b> by default.
But you can always specify the count explicitly when calling +paginate+:
@posts = Post.paginate :page => params[:page], :per_page => 50
The +paginate+ finder wraps the original finder and returns your resultset that now has
some new properties. You can use the collection as you would with any ActiveRecord
resultset. WillPaginate view helpers also need that object to be able to render pagination:
<ol>
<% for post in @posts -%>
<li>Render `post` in some nice way.</li>
<% end -%>
</ol>
<p>Now let's render us some pagination!</p>
<%= will_paginate @posts %>
More detailed documentation:
* WillPaginate::Finder::ClassMethods for pagination on your models;
* WillPaginate::ViewHelpers for your views.
== Authors and credits
Authors:: Mislav Marohnić, PJ Hyett
Original announcement:: http://errtheblog.com/post/929
Original PHP source:: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php
All these people helped making will_paginate what it is now with their code
contributions or just simply awesome ideas:
Chris Wanstrath, Dr. Nic Williams, K. Adam Christensen, Mike Garey, Bence
Golda, Matt Aimonetti, Charles Brian Quinn, Desi McAdam, James Coglan, Matijs
van Zuijlen, Maria, Brendan Ribera, Todd Willey, Bryan Helmkamp, Jan Berkel,
Lourens Naudé, Rick Olson, Russell Norris, Piotr Usewicz, Chris Eppstein.
== Usable pagination in the UI
There are some CSS styles to get you started in the "examples/" directory. They
are showcased in the <b>"examples/index.html"</b> file.
More reading about pagination as design pattern:
* Pagination 101:
http://kurafire.net/log/archive/2007/06/22/pagination-101
* Pagination gallery:
http://www.smashingmagazine.com/2007/11/16/pagination-gallery-examples-and-good-practices/
* Pagination on Yahoo Design Pattern Library:
http://developer.yahoo.com/ypatterns/parent.php?pattern=pagination
Want to discuss, request features, ask questions? Join the
{Google group}[http://groups.google.com/group/will_paginate].

View file

@ -1,65 +1,62 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rubygems'
begin
hanna_dir = '/home/mislav/projects/hanna/lib'
$:.unshift hanna_dir if File.exists? hanna_dir
require 'hanna/rdoctask'
rescue LoadError
require 'rake'
require 'rake/rdoctask'
end
load 'test/tasks.rake'
desc 'Default: run unit tests.'
task :default => :test
desc 'Test the will_paginate plugin.'
Rake::TestTask.new(:test) do |t|
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
# I want to specify environment variables at call time
class EnvTestTask < Rake::TestTask
attr_accessor :env
def ruby(*args)
env.each { |key, value| ENV[key] = value } if env
super
env.keys.each { |key| ENV.delete key } if env
end
end
for configuration in %w( sqlite3 mysql postgres )
EnvTestTask.new("test_#{configuration}") do |t|
t.pattern = 'test/finder_test.rb'
t.verbose = true
t.env = { 'DB' => configuration }
end
end
task :test_databases => %w(test_mysql test_sqlite3 test_postgres)
desc %{Test everything on SQLite3, MySQL and PostgreSQL}
task :test_full => %w(test test_mysql test_postgres)
desc %{Test everything with Rails 1.2.x and 2.0.x gems}
task :test_all do
all = Rake::Task['test_full']
ENV['RAILS_VERSION'] = '~>1.2.6'
all.invoke
# reset the invoked flag
%w( test_full test test_mysql test_postgres ).each do |name|
Rake::Task[name].instance_variable_set '@already_invoked', false
end
# do it again
ENV['RAILS_VERSION'] = '~>2.0.2'
all.invoke
end
desc 'Generate RDoc documentation for the will_paginate plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
files = ['README', 'LICENSE', 'lib/**/*.rb']
rdoc.rdoc_files.add(files)
rdoc.main = "README" # page to start on
rdoc.title = "will_paginate"
rdoc.rdoc_files.include('README.rdoc', 'LICENSE', 'CHANGELOG').
include('lib/**/*.rb').
exclude('lib/will_paginate/named_scope*').
exclude('lib/will_paginate/array.rb').
exclude('lib/will_paginate/version.rb')
templates = %w[/Users/chris/ruby/projects/err/rock/template.rb /var/www/rock/template.rb]
rdoc.template = templates.find { |t| File.exists? t }
rdoc.main = "README.rdoc" # page to start on
rdoc.title = "will_paginate documentation"
rdoc.rdoc_dir = 'doc' # rdoc output folder
rdoc.options << '--inline-source'
rdoc.options << '--charset=UTF-8'
rdoc.options << '--inline-source' << '--charset=UTF-8'
rdoc.options << '--webcvs=http://github.com/mislav/will_paginate/tree/master/'
end
desc %{Update ".manifest" with the latest list of project filenames. Respect\
.gitignore by excluding everything that git ignores. Update `files` and\
`test_files` arrays in "*.gemspec" file if it's present.}
task :manifest do
list = Dir['**/*'].sort
spec_file = Dir['*.gemspec'].first
list -= [spec_file] if spec_file
File.read('.gitignore').each_line do |glob|
glob = glob.chomp.sub(/^\//, '')
list -= Dir[glob]
list -= Dir["#{glob}/**/*"] if File.directory?(glob) and !File.symlink?(glob)
puts "excluding #{glob}"
end
if spec_file
spec = File.read spec_file
spec.gsub! /^(\s* s.(test_)?files \s* = \s* )( \[ [^\]]* \] | %w\( [^)]* \) )/mx do
assignment = $1
bunch = $2 ? list.grep(/^test\//) : list
'%s%%w(%s)' % [assignment, bunch.join(' ')]
end
File.open(spec_file, 'w') {|f| f << spec }
end
File.open('.manifest', 'w') {|f| f << list.join("\n") }
end
task :examples do
%x(haml examples/index.haml examples/index.html)
%x(sass examples/pagination.sass examples/pagination.css)
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

View file

@ -0,0 +1,69 @@
!!!
%html
%head
%title Samples of pagination styling for will_paginate
%link{ :rel => 'stylesheet', :type => 'text/css', :href => 'pagination.css' }
%style{ :type => 'text/css' }
:sass
html
:margin 0
:padding 0
:background #999
:font normal 76% "Lucida Grande", Verdana, Helvetica, sans-serif
body
:margin 2em
:padding 2em
:border 2px solid gray
:background white
:color #222
h1
:font-size 2em
:font-weight normal
:margin 0 0 1em 0
h2
:font-size 1.4em
:margin 1em 0 .5em 0
pre
:font-size 13px
:font-family Monaco, "DejaVu Sans Mono", "Bitstream Vera Mono", "Courier New", monospace
- pagination = '<span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>'
- pagination_no_page_links = '<span class="disabled prev_page">&laquo; Previous</span> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>'
%body
%h1 Samples of pagination styling for will_paginate
%p
Find these styles in <b>"examples/pagination.css"</b> of <i>will_paginate</i> library.
There is a Sass version of it for all you sassy people.
%p
Read about good rules for pagination:
%a{ :href => 'http://kurafire.net/log/archive/2007/06/22/pagination-101' } Pagination 101
%p
%em Warning:
page links below don't lead anywhere (so don't click on them).
%h2 Unstyled pagination <span style="font-weight:normal">(<i>ewww!</i>)</span>
%div= pagination
%h2 Digg.com
.digg_pagination= pagination
%h2 Digg-style, no page links
.digg_pagination= pagination_no_page_links
%p Code that renders this:
%pre= '<code>%s</code>' % %[<%= will_paginate @posts, :page_links => false %>].gsub('<', '&lt;').gsub('>', '&gt;')
%h2 Digg-style, extra content
.digg_pagination
.page_info Displaying entries <b>1&nbsp;-&nbsp;6</b> of <b>180</b> in total
= pagination
%p Code that renders this:
%pre= '<code>%s</code>' % %[<div class="digg_pagination">\n <div clas="page_info">\n <%= page_entries_info @posts %>\n </div>\n <%= will_paginate @posts, :container => false %>\n</div>].gsub('<', '&lt;').gsub('>', '&gt;')
%h2 Apple.com store
.apple_pagination= pagination
%h2 Flickr.com
.flickr_pagination
= pagination
.page_info (118 photos)

View file

@ -0,0 +1,92 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
</html>
<head>
<title>Samples of pagination styling for will_paginate</title>
<link href='pagination.css' rel='stylesheet' type='text/css' />
<style type='text/css'>
html {
margin: 0;
padding: 0;
background: #999;
font: normal 76% "Lucida Grande", Verdana, Helvetica, sans-serif; }
body {
margin: 2em;
padding: 2em;
border: 2px solid gray;
background: white;
color: #222; }
h1 {
font-size: 2em;
font-weight: normal;
margin: 0 0 1em 0; }
h2 {
font-size: 1.4em;
margin: 1em 0 .5em 0; }
pre {
font-size: 13px;
font-family: Monaco, "DejaVu Sans Mono", "Bitstream Vera Mono", "Courier New", monospace; }
</style>
</head>
<body>
<h1>Samples of pagination styling for will_paginate</h1>
<p>
Find these styles in <b>"examples/pagination.css"</b> of <i>will_paginate</i> library.
There is a Sass version of it for all you sassy people.
</p>
<p>
Read about good rules for pagination:
<a href='http://kurafire.net/log/archive/2007/06/22/pagination-101'>Pagination 101</a>
</p>
<p>
<em>Warning:</em>
page links below don't lead anywhere (so don't click on them).
</p>
<h2>
Unstyled pagination <span style="font-weight:normal">(<i>ewww!</i>)</span>
</h2>
<div>
<span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
</div>
<h2>Digg.com</h2>
<div class='digg_pagination'>
<span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
</div>
<h2>Digg-style, no page links</h2>
<div class='digg_pagination'>
<span class="disabled prev_page">&laquo; Previous</span> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
</div>
<p>Code that renders this:</p>
<pre>
<code>&lt;%= will_paginate @posts, :page_links =&gt; false %&gt;</code>
</pre>
<h2>Digg-style, extra content</h2>
<div class='digg_pagination'>
<div class='page_info'>
Displaying entries <b>1&nbsp;-&nbsp;6</b> of <b>180</b> in total
</div>
<span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
</div>
<p>Code that renders this:</p>
<pre>
<code>&lt;div class="digg_pagination"&gt;
&lt;div clas="page_info"&gt;
&lt;%= page_entries_info @posts %&gt;
&lt;/div&gt;
&lt;%= will_paginate @posts, :container =&gt; false %&gt;
&lt;/div&gt;</code>
</pre>
<h2>Apple.com store</h2>
<div class='apple_pagination'>
<span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
</div>
<h2>Flickr.com</h2>
<div class='flickr_pagination'>
<span class="disabled prev_page">&laquo; Previous</span> <span class="current">1</span> <a href="./?page=2" rel="next">2</a> <a href="./?page=3">3</a> <a href="./?page=4">4</a> <a href="./?page=5">5</a> <a href="./?page=6">6</a> <a href="./?page=7">7</a> <a href="./?page=8">8</a> <a href="./?page=9">9</a> <span class="gap">&hellip;</span> <a href="./?page=29">29</a> <a href="./?page=30">30</a> <a href="./?page=2" rel="next" class="next_page">Next &raquo;</a>
<div class='page_info'>(118 photos)</div>
</div>
</body>

View file

@ -0,0 +1,90 @@
.digg_pagination {
background: white;
/* self-clearing method: */ }
.digg_pagination a, .digg_pagination span {
padding: .2em .5em;
display: block;
float: left;
margin-right: 1px; }
.digg_pagination span.disabled {
color: #999;
border: 1px solid #DDD; }
.digg_pagination span.current {
font-weight: bold;
background: #2E6AB1;
color: white;
border: 1px solid #2E6AB1; }
.digg_pagination a {
text-decoration: none;
color: #105CB6;
border: 1px solid #9AAFE5; }
.digg_pagination a:hover, .digg_pagination a:focus {
color: #003;
border-color: #003; }
.digg_pagination .page_info {
background: #2E6AB1;
color: white;
padding: .4em .6em;
width: 22em;
margin-bottom: .3em;
text-align: center; }
.digg_pagination .page_info b {
color: #003;
background: #6aa6ed;
padding: .1em .25em; }
.digg_pagination:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden; }
* html .digg_pagination {
height: 1%; }
*:first-child+html .digg_pagination {
overflow: hidden; }
.apple_pagination {
background: #F1F1F1;
border: 1px solid #E5E5E5;
text-align: center;
padding: 1em; }
.apple_pagination a, .apple_pagination span {
padding: .2em .3em; }
.apple_pagination span.disabled {
color: #AAA; }
.apple_pagination span.current {
font-weight: bold;
background: transparent url(apple-circle.gif) no-repeat 50% 50%; }
.apple_pagination a {
text-decoration: none;
color: black; }
.apple_pagination a:hover, .apple_pagination a:focus {
text-decoration: underline; }
.flickr_pagination {
text-align: center;
padding: .3em; }
.flickr_pagination a, .flickr_pagination span {
padding: .2em .5em; }
.flickr_pagination span.disabled {
color: #AAA; }
.flickr_pagination span.current {
font-weight: bold;
color: #FF0084; }
.flickr_pagination a {
border: 1px solid #DDDDDD;
color: #0063DC;
text-decoration: none; }
.flickr_pagination a:hover, .flickr_pagination a:focus {
border-color: #003366;
background: #0063DC;
color: white; }
.flickr_pagination .page_info {
color: #aaa;
padding-top: .8em; }
.flickr_pagination .prev_page, .flickr_pagination .next_page {
border-width: 2px; }
.flickr_pagination .prev_page {
margin-right: 1em; }
.flickr_pagination .next_page {
margin-left: 1em; }

View file

@ -0,0 +1,91 @@
.digg_pagination
:background white
a, span
:padding .2em .5em
:display block
:float left
:margin-right 1px
span.disabled
:color #999
:border 1px solid #DDD
span.current
:font-weight bold
:background #2E6AB1
:color white
:border 1px solid #2E6AB1
a
:text-decoration none
:color #105CB6
:border 1px solid #9AAFE5
&:hover, &:focus
:color #003
:border-color #003
.page_info
:background #2E6AB1
:color white
:padding .4em .6em
:width 22em
:margin-bottom .3em
:text-align center
b
:color #003
:background = #2E6AB1 + 60
:padding .1em .25em
/* self-clearing method:
&:after
:content "."
:display block
:height 0
:clear both
:visibility hidden
* html &
:height 1%
*:first-child+html &
:overflow hidden
.apple_pagination
:background #F1F1F1
:border 1px solid #E5E5E5
:text-align center
:padding 1em
a, span
:padding .2em .3em
span.disabled
:color #AAA
span.current
:font-weight bold
:background transparent url(apple-circle.gif) no-repeat 50% 50%
a
:text-decoration none
:color black
&:hover, &:focus
:text-decoration underline
.flickr_pagination
:text-align center
:padding .3em
a, span
:padding .2em .5em
span.disabled
:color #AAA
span.current
:font-weight bold
:color #FF0084
a
:border 1px solid #DDDDDD
:color #0063DC
:text-decoration none
&:hover, &:focus
:border-color #003366
:background #0063DC
:color white
.page_info
:color #aaa
:padding-top .8em
.prev_page, .next_page
:border-width 2px
.prev_page
:margin-right 1em
.next_page
:margin-left 1em

View file

@ -1,2 +1 @@
require 'will_paginate'
WillPaginate.enable

View file

@ -21,7 +21,7 @@ module WillPaginate
require 'will_paginate/view_helpers'
ActionView::Base.class_eval { include ViewHelpers }
if ActionController::Base.respond_to? :rescue_responses
if defined?(ActionController::Base) and ActionController::Base.respond_to? :rescue_responses
ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found
end
end
@ -33,28 +33,45 @@ module WillPaginate
require 'will_paginate/finder'
ActiveRecord::Base.class_eval { include Finder }
associations = ActiveRecord::Associations
collection = associations::AssociationCollection
# to support paginating finders on associations, we have to mix in the
# method_missing magic from WillPaginate::Finder::ClassMethods to AssociationProxy
# subclasses, but in a different way for Rails 1.2.x and 2.0
(collection.instance_methods.include?(:create!) ?
collection : collection.subclasses.map(&:constantize)
).push(associations::HasManyThroughAssociation).each do |klass|
# support pagination on associations
a = ActiveRecord::Associations
returning([ a::AssociationCollection ]) { |classes|
# detect http://dev.rubyonrails.org/changeset/9230
unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation
classes << a::HasManyThroughAssociation
end
}.each do |klass|
klass.class_eval do
include Finder::ClassMethods
alias_method_chain :method_missing, :paginate
end
end
end
# Enable named_scope, a feature of Rails 2.1, even if you have older Rails
# (tested on Rails 2.0.2 and 1.2.6).
#
# You can pass +false+ for +patch+ parameter to skip monkeypatching
# *associations*. Use this if you feel that <tt>named_scope</tt> broke
# has_many, has_many :through or has_and_belongs_to_many associations in
# your app. By passing +false+, you can still use <tt>named_scope</tt> in
# your models, but not through associations.
def enable_named_scope(patch = true)
return if defined? ActiveRecord::NamedScope
require 'will_paginate/named_scope'
require 'will_paginate/named_scope_patch' if patch
ActiveRecord::Base.class_eval do
include WillPaginate::NamedScope
end
end
end
module Deprecation #:nodoc:
extend ActiveSupport::Deprecation
def self.warn(message, callstack = caller)
message = 'WillPaginate: ' + message.strip.gsub(/ {3,}/, ' ')
message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ')
behavior.call(message, callstack) if behavior && !silenced?
end
@ -63,3 +80,7 @@ module WillPaginate
end
end
end
if defined?(Rails) and defined?(ActiveRecord) and defined?(ActionController)
WillPaginate.enable
end

View file

@ -0,0 +1,16 @@
require 'will_paginate/collection'
# http://www.desimcadam.com/archives/8
Array.class_eval do
def paginate(options = {})
raise ArgumentError, "parameter hash expected (got #{options.inspect})" unless Hash === options
WillPaginate::Collection.create(
options[:page] || 1,
options[:per_page] || 30,
options[:total_entries] || self.length
) { |pager|
pager.replace self[pager.offset, pager.per_page].to_a
}
end
end

View file

@ -1,11 +1,18 @@
require 'will_paginate'
module WillPaginate
# = OMG, invalid page number!
# = Invalid page number error
# This is an ArgumentError raised in case a page was requested that is either
# zero or negative number. You should decide how do deal with such errors in
# the controller.
#
# If you're using Rails 2, then this error will automatically get handled like
# 404 Not Found. The hook is in "will_paginate.rb":
#
# ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found
#
# If you don't like this, use your preffered method of rescuing exceptions in
# public from your controllers to handle this differently. The +rescue_from+
# method is a nice addition to Rails 2.
#
# This error is *not* raised when a page further than the last page is
# requested. Use <tt>WillPaginate::Collection#out_of_bounds?</tt> method to
# check for those cases and manually deal with them as you see fit.
@ -15,26 +22,34 @@ module WillPaginate
end
end
# Arrays returned from paginating finds are, in fact, instances of this.
# You may think of WillPaginate::Collection as an ordinary array with some
# extra properties. Those properties are used by view helpers to generate
# = The key to pagination
# Arrays returned from paginating finds are, in fact, instances of this little
# class. You may think of WillPaginate::Collection as an ordinary array with
# some extra properties. Those properties are used by view helpers to generate
# correct page links.
#
# WillPaginate::Collection also assists in rolling out your own pagination
# solutions: see +create+.
#
# If you are writing a library that provides a collection which you would like
# to conform to this API, you don't have to copy these methods over; simply
# make your plugin/gem dependant on the "will_paginate" gem:
#
# gem 'will_paginate'
# require 'will_paginate/collection'
#
# # now use WillPaginate::Collection directly or subclass it
class Collection < Array
attr_reader :current_page, :per_page, :total_entries
attr_reader :current_page, :per_page, :total_entries, :total_pages
# Arguments to this constructor are the current page number, per-page limit
# Arguments to the constructor are the current page number, per-page limit
# and the total number of entries. The last argument is optional because it
# is best to do lazy counting; in other words, count *conditionally* after
# populating the collection using the +replace+ method.
#
def initialize(page, per_page, total = nil)
@current_page = page.to_i
raise InvalidPage.new(page, @current_page) if @current_page < 1
@per_page = per_page.to_i
@per_page = per_page.to_i
raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1
self.total_entries = total if total
@ -65,29 +80,25 @@ module WillPaginate
# end
# end
#
# The Array#paginate API has since then changed, but this still serves as a
# fine example of WillPaginate::Collection usage.
def self.create(page, per_page, total = nil, &block)
pager = new(page, per_page, total)
yield pager
pager
end
# The total number of pages.
def page_count
@total_pages
end
# Helper method that is true when someone tries to fetch a page with a
# larger number than the last page. Can be used in combination with flashes
# and redirecting.
def out_of_bounds?
current_page > page_count
current_page > total_pages
end
# Current offset of the paginated collection. If we're on the first page,
# it is always 0. If we're on the 2nd page and there are 30 entries per page,
# the offset is 30. This property is useful if you want to render ordinals
# besides your records: simply start with offset + 1.
#
def offset
(current_page - 1) * per_page
end
@ -99,7 +110,7 @@ module WillPaginate
# current_page + 1 or nil if there is no next page
def next_page
current_page < page_count ? (current_page + 1) : nil
current_page < total_pages ? (current_page + 1) : nil
end
def total_entries=(number)
@ -120,13 +131,15 @@ module WillPaginate
# +total_entries+ and set it to a proper value if it's +nil+. See the example
# in +create+.
def replace(array)
returning super do
# The collection is shorter then page limit? Rejoice, because
# then we know that we are on the last page!
if total_entries.nil? and length > 0 and length < per_page
self.total_entries = offset + length
end
result = super
# The collection is shorter then page limit? Rejoice, because
# then we know that we are on the last page!
if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
self.total_entries = offset + length
end
result
end
end
end

View file

@ -1,5 +1,5 @@
require 'will_paginate'
require 'set'
require 'will_paginate/array'
unless Hash.instance_methods.include? 'except'
Hash.class_eval do
@ -30,51 +30,3 @@ unless Hash.instance_methods.include? 'slice'
end
end
end
unless Hash.instance_methods.include? 'rec_merge!'
Hash.class_eval do
# Same as Hash#merge!, but recursively merges sub-hashes
# (stolen from Haml)
def rec_merge!(other)
other.each do |key, other_value|
value = self[key]
if value.is_a?(Hash) and other_value.is_a?(Hash)
value.rec_merge! other_value
else
self[key] = other_value
end
end
self
end
end
end
require 'will_paginate/collection'
unless Array.instance_methods.include? 'paginate'
# http://www.desimcadam.com/archives/8
Array.class_eval do
def paginate(options_or_page = {}, per_page = nil)
if options_or_page.nil? or Fixnum === options_or_page
if defined? WillPaginate::Deprecation
WillPaginate::Deprecation.warn <<-DEPR
Array#paginate now conforms to the main, ActiveRecord::Base#paginate API. You should \
call it with a parameters hash (:page, :per_page). The old API (numbers as arguments) \
has been deprecated and is going to be unsupported in future versions of will_paginate.
DEPR
end
page = options_or_page
options = {}
else
options = options_or_page
page = options[:page]
raise ArgumentError, "wrong number of arguments (1 hash or 2 Fixnums expected)" if per_page
per_page = options[:per_page]
end
WillPaginate::Collection.create(page || 1, per_page || 30, options[:total_entries] || size) do |pager|
pager.replace self[pager.offset, pager.per_page].to_a
end
end
end
end

View file

@ -2,7 +2,7 @@ require 'will_paginate/core_ext'
module WillPaginate
# A mixin for ActiveRecord::Base. Provides +per_page+ class method
# and makes +paginate+ finders possible with some method_missing magic.
# and hooks things up to provide paginating finders.
#
# Find out more in WillPaginate::Finder::ClassMethods
#
@ -18,9 +18,9 @@ module WillPaginate
# = Paginating finders for ActiveRecord models
#
# WillPaginate adds +paginate+ and +per_page+ methods to ActiveRecord::Base
# class methods and associations. It also hooks into +method_missing+ to
# intercept pagination calls to dynamic finders such as
# WillPaginate adds +paginate+, +per_page+ and other methods to
# ActiveRecord::Base class methods and associations. It also hooks into
# +method_missing+ to intercept pagination calls to dynamic finders such as
# +paginate_by_user_id+ and translate them to ordinary finders
# (+find_all_by_user_id+ in this case).
#
@ -85,6 +85,31 @@ module WillPaginate
pager.total_entries = wp_count(count_options, args, finder) unless pager.total_entries
end
end
# Iterates through all records by loading one page at a time. This is useful
# for migrations or any other use case where you don't want to load all the
# records in memory at once.
#
# It uses +paginate+ internally; therefore it accepts all of its options.
# You can specify a starting page with <tt>:page</tt> (default is 1). Default
# <tt>:order</tt> is <tt>"id"</tt>, override if necessary.
#
# See http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord where
# Jamis Buck describes this and also uses a more efficient way for MySQL.
def paginated_each(options = {}, &block)
options = { :order => 'id', :page => 1 }.merge options
options[:page] = options[:page].to_i
options[:total_entries] = 0 # skip the individual count queries
total = 0
begin
collection = paginate(options)
total += collection.each(&block).size
options[:page] += 1
end until collection.size < collection.per_page
total
end
# Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
# based on the params otherwise used by paginating finds: +page+ and
@ -159,19 +184,27 @@ module WillPaginate
unless options[:select] and options[:select] =~ /^\s*DISTINCT\b/i
excludees << :select # only exclude the select param if it doesn't begin with DISTINCT
end
# count expects (almost) the same options as find
count_options = options.except *excludees
# merge the hash found in :count
# this allows you to specify :select, :order, or anything else just for the count query
count_options.update options[:count] if options[:count]
# we may be in a model or an association proxy
klass = (@owner and @reflection) ? @reflection.klass : self
# forget about includes if they are irrelevant (Rails 2.1)
if count_options[:include] and
klass.private_methods.include?('references_eager_loaded_tables?') and
!klass.send(:references_eager_loaded_tables?, count_options)
count_options.delete :include
end
# we may have to scope ...
counter = Proc.new { count(count_options) }
# we may be in a model or an association proxy!
klass = (@owner and @reflection) ? @reflection.klass : self
count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with'))
# scope_out adds a 'with_finder' method which acts like with_scope, if it's present
# then execute the count with the scoping provided by the with_finder

View file

@ -0,0 +1,132 @@
## stolen from: http://dev.rubyonrails.org/browser/trunk/activerecord/lib/active_record/named_scope.rb?rev=9084
module WillPaginate
# This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate,
# but in other aspects when managing complex conditions that you want to be reusable.
module NamedScope
# All subclasses of ActiveRecord::Base have two named_scopes:
# * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
# * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
#
# Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
#
# These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
# intermediate values (scopes) around as first-class objects is convenient.
def self.included(base)
base.class_eval do
extend ClassMethods
named_scope :all
named_scope :scoped, lambda { |scope| scope }
end
end
module ClassMethods
def scopes #:nodoc:
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
end
# Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
# such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
#
# class Shirt < ActiveRecord::Base
# named_scope :red, :conditions => {:color => 'red'}
# named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
# end
#
# 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>,
# in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
#
# Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
# constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
# <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
# as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
# <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
#
# 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.
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
#
# All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
# <tt>has_many</tt> associations. If,
#
# class Person < ActiveRecord::Base
# has_many :shirts
# end
#
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
# only shirts.
#
# Named scopes can also be procedural.
#
# class Shirt < ActiveRecord::Base
# named_scope :colored, lambda { |color|
# { :conditions => { :color => color } }
# }
# end
#
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
#
# Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
#
# class Shirt < ActiveRecord::Base
# named_scope :red, :conditions => {:color => 'red'} do
# def dom_id
# 'red_shirts'
# end
# end
# end
#
def named_scope(name, options = {}, &block)
scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options
when Hash
options
when Proc
options.call(*args)
end, &block)
end
(class << self; self end).instance_eval do
define_method name do |*args|
scopes[name].call(self, *args)
end
end
end
end
class Scope #:nodoc:
attr_reader :proxy_scope, :proxy_options
[].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
delegate :scopes, :with_scope, :to => :proxy_scope
def initialize(proxy_scope, options, &block)
[options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
extend Module.new(&block) if block_given?
@proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
end
def reload
load_found; self
end
protected
def proxy_found
@found || load_found
end
private
def method_missing(method, *args, &block)
if scopes.include?(method)
scopes[method].call(self, *args)
else
with_scope :find => proxy_options do
proxy_scope.send(method, *args, &block)
end
end
end
def load_found
@found = find(:all)
end
end
end
end

View file

@ -0,0 +1,39 @@
## based on http://dev.rubyonrails.org/changeset/9084
ActiveRecord::Associations::AssociationProxy.class_eval do
protected
def with_scope(*args, &block)
@reflection.klass.send :with_scope, *args, &block
end
end
[ ActiveRecord::Associations::AssociationCollection,
ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass|
klass.class_eval do
protected
alias :method_missing_without_scopes :method_missing_without_paginate
def method_missing_without_paginate(method, *args, &block)
if @reflection.klass.scopes.include?(method)
@reflection.klass.scopes[method].call(self, *args, &block)
else
method_missing_without_scopes(method, *args, &block)
end
end
end
end
# Rails 1.2.6
ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
protected
def method_missing(method, *args, &block)
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
super
elsif @reflection.klass.scopes.include?(method)
@reflection.klass.scopes[method].call(self, *args)
else
@reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
@reflection.klass.send(method, *args, &block)
end
end
end
end if ActiveRecord::Base.respond_to? :find_first

View file

@ -0,0 +1,9 @@
module WillPaginate
module VERSION
MAJOR = 2
MINOR = 3
TINY = 3
STRING = [MAJOR, MINOR, TINY].join('.')
end
end

View file

@ -49,12 +49,13 @@ module WillPaginate
# * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
# * <tt>:params</tt> -- additional parameters when generating pagination links
# (eg. <tt>:controller => "foo", :action => nil</tt>)
# * <tt>:renderer</tt> -- class name of the link renderer (default: WillPaginate::LinkRenderer)
# * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default:
# <tt>WillPaginate::LinkRenderer</tt>)
# * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
# * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
# false only when you are rendering your own pagination markup (default: true)
# * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID automatically
# generated from the class name of objects in collection: for example, paginating
# * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID
# automatically generated from the class name of objects in collection: for example, paginating
# ArticleComment models would yield an ID of "article_comments_pagination".
#
# All options beside listed ones are passed as HTML attributes to the container
@ -85,29 +86,99 @@ module WillPaginate
collection_name = "@#{controller.controller_name}"
collection = instance_variable_get(collection_name)
raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " +
"forget to specify the collection object for will_paginate?" unless collection
"forget to pass the collection object for will_paginate?" unless collection
end
# early exit if there is nothing to render
return nil unless collection.page_count > 1
return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(collection) > 1
options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options
# create the renderer instance
renderer_class = options[:renderer].to_s.constantize
renderer = renderer_class.new collection, options, self
# get the renderer instance
renderer = case options[:renderer]
when String
options[:renderer].to_s.constantize.new
when Class
options[:renderer].new
else
options[:renderer]
end
# render HTML for pagination
renderer.prepare collection, options, self
renderer.to_html
end
# Wrapper for rendering pagination links at both top and bottom of a block
# of content.
#
# <% paginated_section @posts do %>
# <ol id="posts">
# <% for post in @posts %>
# <li> ... </li>
# <% end %>
# </ol>
# <% end %>
#
# will result in:
#
# <div class="pagination"> ... </div>
# <ol id="posts">
# ...
# </ol>
# <div class="pagination"> ... </div>
#
# Arguments are passed to a <tt>will_paginate</tt> call, so the same options
# apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two
# blocks of pagination links sharing the same ID (which is invalid HTML).
def paginated_section(*args, &block)
pagination = will_paginate(*args).to_s
content = pagination + capture(&block) + pagination
concat content, block.binding
end
# Renders a helpful message with numbers of displayed vs. total entries.
# You can use this as a blueprint for your own, similar helpers.
#
# <%= page_entries_info @posts %>
# #-> Displaying entries 6 - 10 of 26 in total
def page_entries_info(collection)
%{Displaying entries <b>%d&nbsp;-&nbsp;%d</b> of <b>%d</b> in total} % [
collection.offset + 1,
collection.offset + collection.length,
collection.total_entries
]
# #-> Displaying posts 6 - 10 of 26 in total
#
# By default, the message will use the humanized class name of objects
# in collection: for instance, "project types" for ProjectType models.
# Override this to your liking with the <tt>:entry_name</tt> parameter:
#
# <%= page_entries_info @posts, :entry_name => 'item' %>
# #-> Displaying items 6 - 10 of 26 in total
def page_entries_info(collection, options = {})
entry_name = options[:entry_name] ||
(collection.empty?? 'entry' : collection.first.class.name.underscore.sub('_', ' '))
if collection.total_pages < 2
case collection.size
when 0; "No #{entry_name.pluralize} found"
when 1; "Displaying <b>1</b> #{entry_name}"
else; "Displaying <b>all #{collection.size}</b> #{entry_name.pluralize}"
end
else
%{Displaying #{entry_name.pluralize} <b>%d&nbsp;-&nbsp;%d</b> of <b>%d</b> in total} % [
collection.offset + 1,
collection.offset + collection.length,
collection.total_entries
]
end
end
def self.total_pages_for_collection(collection) #:nodoc:
if collection.respond_to?('page_count') and !collection.respond_to?('total_pages')
WillPaginate::Deprecation.warn <<-MSG
You are using a paginated collection of class #{collection.class.name}
which conforms to the old API of WillPaginate::Collection by using
`page_count`, while the current method name is `total_pages`. Please
upgrade yours or 3rd-party code that provides the paginated collection.
MSG
class << collection
def total_pages; page_count; end
end
end
collection.total_pages
end
end
@ -115,22 +186,43 @@ module WillPaginate
# links. It is used by +will_paginate+ helper internally.
class LinkRenderer
def initialize(collection, options, template)
# The gap in page links is represented by:
#
# <span class="gap">&hellip;</span>
attr_accessor :gap_marker
def initialize
@gap_marker = '<span class="gap">&hellip;</span>'
end
# * +collection+ is a WillPaginate::Collection instance or any other object
# that conforms to that API
# * +options+ are forwarded from +will_paginate+ view helper
# * +template+ is the reference to the template being rendered
def prepare(collection, options, template)
@collection = collection
@options = options
@template = template
# reset values in case we're re-using this instance
@total_pages = @param_name = @url_string = nil
end
# Process it! This method returns the complete HTML string which contains
# pagination links. Feel free to subclass LinkRenderer and change this
# method as you see fit.
def to_html
links = @options[:page_links] ? windowed_links : []
# previous/next buttons
links.unshift page_link_or_span(@collection.previous_page, 'disabled', @options[:prev_label])
links.push page_link_or_span(@collection.next_page, 'disabled', @options[:next_label])
links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:prev_label])
links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label])
html = links.join(@options[:separator])
@options[:container] ? @template.content_tag(:div, html, html_attributes) : html
end
# Returns the subset of +options+ this instance was initialized with that
# represent HTML attributes for the container element of pagination links.
def html_attributes
return @html_attributes if @html_attributes
@html_attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class])
@ -143,20 +235,21 @@ module WillPaginate
protected
def gap_marker; '...'; end
# Collects link items for visible page numbers.
def windowed_links
prev = nil
visible_page_numbers.inject [] do |links, n|
# detect gaps:
links << gap_marker if prev and n > prev + 1
links << page_link_or_span(n)
links << page_link_or_span(n, 'current')
prev = n
links
end
end
# Calculates visible page numbers using the <tt>:inner_window</tt> and
# <tt>:outer_window</tt> options.
def visible_page_numbers
inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
window_from = current_page - inner_window
@ -166,9 +259,11 @@ module WillPaginate
if window_to > total_pages
window_from -= window_to - total_pages
window_to = total_pages
elsif window_from < 1
end
if window_from < 1
window_to += 1 - window_from
window_from = 1
window_to = total_pages if window_to > total_pages
end
visible = (1..total_pages).to_a
@ -180,21 +275,63 @@ module WillPaginate
visible
end
def page_link_or_span(page, span_class = 'current', text = nil)
def page_link_or_span(page, span_class, text = nil)
text ||= page.to_s
if page and page != current_page
@template.link_to text, url_options(page), :rel => rel_value(page)
classnames = span_class && span_class.index(' ') && span_class.split(' ', 2).last
page_link page, text, :rel => rel_value(page), :class => classnames
else
@template.content_tag :span, text, :class => span_class
page_span page, text, :class => span_class
end
end
def url_options(page)
options = { param_name => page }
# page links should preserve GET parameters
options = params.merge(options) if @template.request.get?
options.rec_merge!(@options[:params]) if @options[:params]
return options
def page_link(page, text, attributes = {})
@template.link_to text, url_for(page), attributes
end
def page_span(page, text, attributes = {})
@template.content_tag :span, text, attributes
end
# Returns URL params for +page_link_or_span+, taking the current GET params
# and <tt>:params</tt> option into account.
def url_for(page)
page_one = page == 1
unless @url_string and !page_one
@url_params = {}
# page links should preserve GET parameters
stringified_merge @url_params, @template.params if @template.request.get?
stringified_merge @url_params, @options[:params] if @options[:params]
if complex = param_name.index(/[^\w-]/)
page_param = (defined?(CGIMethods) ? CGIMethods : ActionController::AbstractRequest).
parse_query_parameters("#{param_name}=#{page}")
stringified_merge @url_params, page_param
else
@url_params[param_name] = page_one ? 1 : 2
end
url = @template.url_for(@url_params)
return url if page_one
if complex
@url_string = url.sub(%r!((?:\?|&amp;)#{CGI.escape param_name}=)#{page}!, '\1@')
return url
else
@url_string = url
@url_params[param_name] = 3
@template.url_for(@url_params).split(//).each_with_index do |char, i|
if char == '3' and url[i, 1] == '2'
@url_string[i] = '@'
break
end
end
end
end
# finally!
@url_string.sub '@', page.to_s
end
private
@ -212,15 +349,25 @@ module WillPaginate
end
def total_pages
@collection.page_count
@total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@collection)
end
def param_name
@param_name ||= @options[:param_name].to_sym
@param_name ||= @options[:param_name].to_s
end
def params
@params ||= @template.params.to_hash.symbolize_keys
# Recursively merge into target hash by using stringified keys from the other one
def stringified_merge(target, other)
other.each do |key, value|
key = key.to_s # this line is what it's all about!
existing = target[key]
if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
stringified_merge(existing || (target[key] = {}), value)
else
target[key] = value
end
end
end
end
end

View file

@ -19,5 +19,3 @@ else
gem 'activerecord'
end
end
$:.unshift "#{plugin_root}/lib"

View file

@ -1,5 +1,5 @@
require File.dirname(__FILE__) + '/helper'
require 'will_paginate/core_ext'
require 'helper'
require 'will_paginate/array'
class ArrayPaginationTest < Test::Unit::TestCase
def test_simple
@ -11,7 +11,8 @@ class ArrayPaginationTest < Test::Unit::TestCase
{ :page => 3, :per_page => 5, :expected => [] },
].
each do |conditions|
assert_equal conditions[:expected], collection.paginate(conditions.slice(:page, :per_page))
expected = conditions.delete :expected
assert_equal expected, collection.paginate(conditions)
end
end
@ -22,14 +23,8 @@ class ArrayPaginationTest < Test::Unit::TestCase
end
def test_deprecated_api
assert_deprecated 'paginate API' do
result = (1..50).to_a.paginate(2, 10)
assert_equal 2, result.current_page
assert_equal (11..20).to_a, result
assert_equal 50, result.total_entries
end
assert_deprecated { [].paginate nil }
assert_raise(ArgumentError) { [].paginate(2) }
assert_raise(ArgumentError) { [].paginate(2, 10) }
end
def test_total_entries_has_precedence
@ -50,14 +45,28 @@ class ArrayPaginationTest < Test::Unit::TestCase
end
assert_equal entries, collection
assert_respond_to_all collection, %w(page_count each offset size current_page per_page total_entries)
assert_respond_to_all collection, %w(total_pages each offset size current_page per_page total_entries)
assert_kind_of Array, collection
assert_instance_of Array, collection.entries
assert_equal 3, collection.offset
assert_equal 4, collection.page_count
assert_equal 4, collection.total_pages
assert !collection.out_of_bounds?
end
def test_previous_next_pages
collection = create(1, 1, 3)
assert_nil collection.previous_page
assert_equal 2, collection.next_page
collection = create(2, 1, 3)
assert_equal 1, collection.previous_page
assert_equal 3, collection.next_page
collection = create(3, 1, 3)
assert_equal 2, collection.previous_page
assert_nil collection.next_page
end
def test_out_of_bounds
entries = create(2, 3, 2){}
assert entries.out_of_bounds?
@ -90,13 +99,20 @@ class ArrayPaginationTest < Test::Unit::TestCase
pager.replace array(0)
end
assert_equal nil, entries.total_entries
entries = create(1) do |pager|
# collection is empty and we're on page 1,
# so the whole thing must be empty, too
pager.replace array(0)
end
assert_equal 0, entries.total_entries
end
def test_invalid_page
bad_input = [0, -1, nil, '', 'Schnitzel']
bad_inputs = [0, -1, nil, '', 'Schnitzel']
bad_input.each do |bad|
assert_raise(WillPaginate::InvalidPage) { create(bad) }
bad_inputs.each do |bad|
assert_raise(WillPaginate::InvalidPage) { create bad }
end
end
@ -104,6 +120,11 @@ class ArrayPaginationTest < Test::Unit::TestCase
assert_raise(ArgumentError) { create(1, -1) }
end
def test_page_count_was_removed
assert_raise(NoMethodError) { create.page_count }
# It's `total_pages` now.
end
private
def create(page = 2, limit = 5, total = nil, &block)
if block_given?
@ -116,16 +137,4 @@ class ArrayPaginationTest < Test::Unit::TestCase
def array(size = 3)
Array.new(size)
end
def collect_deprecations
old_behavior = WillPaginate::Deprecation.behavior
deprecations = []
WillPaginate::Deprecation.behavior = Proc.new do |message, callstack|
deprecations << message
end
result = yield
[result, deprecations]
ensure
WillPaginate::Deprecation.behavior = old_behavior
end
end

View file

@ -1,9 +1,8 @@
#!/usr/bin/env ruby
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
libs = []
dirname = File.dirname(__FILE__)
libs << 'irb/completion'
libs << File.join(dirname, 'lib', 'load_fixtures')
libs << File.join('lib', 'load_fixtures')
exec "#{irb}#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt"
exec "#{irb} -Ilib:test#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt"

View file

@ -1,8 +1,9 @@
require File.dirname(__FILE__) + '/helper'
require File.dirname(__FILE__) + '/lib/activerecord_test_case'
require 'helper'
require 'lib/activerecord_test_case'
require 'will_paginate'
WillPaginate.enable_activerecord
WillPaginate.enable_named_scope
class FinderTest < ActiveRecordTestCase
fixtures :topics, :replies, :users, :projects, :developers_projects
@ -12,18 +13,18 @@ class FinderTest < ActiveRecordTestCase
end
def test_simple_paginate
entries = Topic.paginate :page => nil
assert_equal 1, entries.current_page
assert_nil entries.previous_page
assert_nil entries.next_page
assert_equal 1, entries.page_count
assert_equal 4, entries.size
assert_queries(1) do
entries = Topic.paginate :page => nil
assert_equal 1, entries.current_page
assert_equal 1, entries.total_pages
assert_equal 4, entries.size
end
entries = Topic.paginate :page => 2
assert_equal 2, entries.current_page
assert_equal 1, entries.previous_page
assert_equal 1, entries.page_count
assert entries.empty?
assert_queries(2) do
entries = Topic.paginate :page => 2
assert_equal 1, entries.total_pages
assert entries.empty?
end
end
def test_parameter_api
@ -41,31 +42,31 @@ class FinderTest < ActiveRecordTestCase
def test_paginate_with_per_page
entries = Topic.paginate :page => 1, :per_page => 1
assert_equal 1, entries.size
assert_equal 4, entries.page_count
assert_equal 4, entries.total_pages
# Developer class has explicit per_page at 10
entries = Developer.paginate :page => 1
assert_equal 10, entries.size
assert_equal 2, entries.page_count
assert_equal 2, entries.total_pages
entries = Developer.paginate :page => 1, :per_page => 5
assert_equal 11, entries.total_entries
assert_equal 5, entries.size
assert_equal 3, entries.page_count
assert_equal 3, entries.total_pages
end
def test_paginate_with_order
entries = Topic.paginate :page => 1, :order => 'created_at desc'
expected = [topics(:futurama), topics(:harvey_birdman), topics(:rails), topics(:ar)].reverse
assert_equal expected, entries.to_a
assert_equal 1, entries.page_count
assert_equal 1, entries.total_pages
end
def test_paginate_with_conditions
entries = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago]
expected = [topics(:rails), topics(:ar)]
assert_equal expected, entries.to_a
assert_equal 1, entries.page_count
assert_equal 1, entries.total_pages
end
def test_paginate_with_include_and_conditions
@ -85,11 +86,14 @@ class FinderTest < ActiveRecordTestCase
end
def test_paginate_with_include_and_order
entries = Topic.paginate \
:page => 1,
:include => :replies,
:order => 'replies.created_at asc, topics.created_at asc',
:per_page => 10
entries = nil
assert_queries(2) do
entries = Topic.paginate \
:page => 1,
:include => :replies,
:order => 'replies.created_at asc, topics.created_at asc',
:per_page => 10
end
expected = Topic.find :all,
:include => 'replies',
@ -104,7 +108,7 @@ class FinderTest < ActiveRecordTestCase
entries, project = nil, projects(:active_record)
assert_nothing_raised "THIS IS A BUG in Rails 1.2.3 that was fixed in [7326]. " +
"Please upgrade to the 1-2-stable branch or edge Rails." do
"Please upgrade to a newer version of Rails." do
entries = project.topics.paginate \
:page => 1,
:include => :replies,
@ -125,10 +129,12 @@ class FinderTest < ActiveRecordTestCase
expected_name_ordered = [projects(:action_controller), projects(:active_record)]
expected_id_ordered = [projects(:active_record), projects(:action_controller)]
# with association-specified order
entries = dhh.projects.paginate(:page => 1)
assert_equal expected_name_ordered, entries
assert_equal 2, entries.total_entries
assert_queries(2) do
# with association-specified order
entries = dhh.projects.paginate(:page => 1)
assert_equal expected_name_ordered, entries
assert_equal 2, entries.total_entries
end
# with explicit order
entries = dhh.projects.paginate(:page => 1, :order => 'projects.id')
@ -148,29 +154,43 @@ class FinderTest < ActiveRecordTestCase
def test_paginate_association_extension
project = Project.find(:first)
entries = project.replies.paginate_recent :page => 1
assert_equal [replies(:brave)], entries
assert_queries(2) do
entries = project.replies.paginate_recent :page => 1
assert_equal [replies(:brave)], entries
end
end
def test_paginate_with_joins
entries = Developer.paginate :page => 1,
:joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id',
:conditions => 'project_id = 1'
assert_equal 2, entries.size
developer_names = entries.map { |d| d.name }
assert developer_names.include?('David')
assert developer_names.include?('Jamis')
entries = nil
assert_queries(1) do
entries = Developer.paginate :page => 1,
:joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id',
:conditions => 'project_id = 1'
assert_equal 2, entries.size
developer_names = entries.map &:name
assert developer_names.include?('David')
assert developer_names.include?('Jamis')
end
expected = entries.to_a
entries = Developer.paginate :page => 1,
:joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id',
:conditions => 'project_id = 1', :count => { :select => "users.id" }
assert_equal expected, entries.to_a
assert_queries(1) do
expected = entries.to_a
entries = Developer.paginate :page => 1,
:joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id',
:conditions => 'project_id = 1', :count => { :select => "users.id" }
assert_equal expected, entries.to_a
assert_equal 2, entries.total_entries
end
end
def test_paginate_with_group
entries = Developer.paginate :page => 1, :per_page => 10,
:group => 'salary', :select => 'salary', :order => 'salary'
entries = nil
assert_queries(1) do
entries = Developer.paginate :page => 1, :per_page => 10,
:group => 'salary', :select => 'salary', :order => 'salary'
end
expected = [ users(:david), users(:jamis), users(:dev_10), users(:poor_jamis) ].map(&:salary).sort
assert_equal expected, entries.map(&:salary)
end
@ -201,6 +221,56 @@ class FinderTest < ActiveRecordTestCase
assert_equal 2, entries.total_entries
end
## named_scope ##
def test_paginate_in_named_scope
entries = Developer.poor.paginate :page => 1, :per_page => 1
assert_equal 1, entries.size
assert_equal 2, entries.total_entries
end
def test_paginate_in_named_scope_on_habtm_association
project = projects(:active_record)
assert_queries(2) do
entries = project.developers.poor.paginate :page => 1, :per_page => 1
assert_equal 1, entries.size, 'one developer should be found'
assert_equal 1, entries.total_entries, 'only one developer should be found'
end
end
def test_paginate_in_named_scope_on_hmt_association
project = projects(:active_record)
expected = [replies(:brave)]
assert_queries(2) do
entries = project.replies.recent.paginate :page => 1, :per_page => 1
assert_equal expected, entries
assert_equal 1, entries.total_entries, 'only one reply should be found'
end
end
def test_paginate_in_named_scope_on_has_many_association
project = projects(:active_record)
expected = [topics(:ar)]
assert_queries(2) do
entries = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1
assert_equal expected, entries
assert_equal 1, entries.total_entries, 'only one topic should be found'
end
end
## misc ##
def test_count_and_total_entries_options_are_mutually_exclusive
e = assert_raise ArgumentError do
Developer.paginate :page => 1, :count => {}, :total_entries => 1
end
assert_match /exclusive/, e.to_s
end
def test_readonly
assert_nothing_raised { Developer.paginate :readonly => true, :page => 1 }
end
@ -216,13 +286,15 @@ class FinderTest < ActiveRecordTestCase
end
# Is this Rails 2.0? Find out by testing find_all which was removed in [6998]
unless Developer.respond_to? :find_all
unless ActiveRecord::Base.respond_to? :find_all
def test_paginate_array_of_ids
# AR finders also accept arrays of IDs
# (this was broken in Rails before [6912])
entries = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id')
assert_equal (4..6).to_a, entries.map(&:id)
assert_equal 8, entries.total_entries
assert_queries(1) do
entries = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id')
assert_equal (4..6).to_a, entries.map(&:id)
assert_equal 8, entries.total_entries
end
end
end
@ -230,7 +302,7 @@ class FinderTest < ActiveRecordTestCase
def test_implicit_all_with_dynamic_finders
Topic.expects(:find_all_by_foo).returns([])
Topic.expects(:count).returns(0)
Topic.paginate_by_foo :page => 1
Topic.paginate_by_foo :page => 2
end
def test_guessing_the_total_count
@ -241,6 +313,14 @@ class FinderTest < ActiveRecordTestCase
assert_equal 6, entries.total_entries
end
def test_guessing_that_there_are_no_records
Topic.expects(:find).returns([])
Topic.expects(:count).never
entries = Topic.paginate :page => 1, :per_page => 4
assert_equal 0, entries.total_entries
end
def test_extra_parameters_stay_untouched
Topic.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => 0 }).returns(Array.new(5))
Topic.expects(:count).with({:foo => 'bar'}).returns(1)
@ -251,13 +331,13 @@ class FinderTest < ActiveRecordTestCase
def test_count_skips_select
Developer.stubs(:find).returns([])
Developer.expects(:count).with({}).returns(0)
Developer.paginate :select => 'salary', :page => 1
Developer.paginate :select => 'salary', :page => 2
end
def test_count_select_when_distinct
Developer.stubs(:find).returns([])
Developer.expects(:count).with(:select => 'DISTINCT salary').returns(0)
Developer.paginate :select => 'DISTINCT salary', :page => 1
Developer.paginate :select => 'DISTINCT salary', :page => 2
end
def test_should_use_scoped_finders_if_present
@ -288,16 +368,16 @@ class FinderTest < ActiveRecordTestCase
Developer.expects(:find_by_sql).returns([])
Developer.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS count_table").returns(0)
entries = Developer.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 1
Developer.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 2
end
# TODO: counts are still wrong
def test_ability_to_use_with_custom_finders
# acts_as_taggable defines find_tagged_with(tag, options)
Topic.expects(:find_tagged_with).with('will_paginate', :offset => 0, :limit => 5).returns([])
Topic.expects(:find_tagged_with).with('will_paginate', :offset => 5, :limit => 5).returns([])
Topic.expects(:count).with({}).returns(0)
Topic.paginate_tagged_with 'will_paginate', :page => 1, :per_page => 5
Topic.paginate_tagged_with 'will_paginate', :page => 2, :per_page => 5
end
def test_array_argument_doesnt_eliminate_count
@ -310,7 +390,6 @@ class FinderTest < ActiveRecordTestCase
def test_paginating_finder_doesnt_mangle_options
Developer.expects(:find).returns([])
Developer.expects(:count).returns(0)
options = { :page => 1 }
options.expects(:delete).never
options_before = options.dup
@ -318,5 +397,38 @@ class FinderTest < ActiveRecordTestCase
Developer.paginate(options)
assert_equal options, options_before
end
def test_paginated_each
collection = stub('collection', :size => 5, :empty? => false, :per_page => 5)
collection.expects(:each).times(2).returns(collection)
last_collection = stub('collection', :size => 4, :empty? => false, :per_page => 5)
last_collection.expects(:each).returns(last_collection)
params = { :order => 'id', :total_entries => 0 }
Developer.expects(:paginate).with(params.merge(:page => 2)).returns(collection)
Developer.expects(:paginate).with(params.merge(:page => 3)).returns(collection)
Developer.expects(:paginate).with(params.merge(:page => 4)).returns(last_collection)
assert_equal 14, Developer.paginated_each(:page => '2') { }
end
# detect ActiveRecord 2.1
if ActiveRecord::Base.private_methods.include?('references_eager_loaded_tables?')
def test_removes_irrelevant_includes_in_count
Developer.expects(:find).returns([1])
Developer.expects(:count).with({}).returns(0)
Developer.paginate :page => 1, :per_page => 1, :include => :projects
end
def test_doesnt_remove_referenced_includes_in_count
Developer.expects(:find).returns([1])
Developer.expects(:count).with({ :include => :projects, :conditions => 'projects.id > 2' }).returns(0)
Developer.paginate :page => 1, :per_page => 1,
:include => :projects, :conditions => 'projects.id > 2'
end
end
end
end

View file

@ -7,5 +7,7 @@ class Developer < User
end
end
named_scope :poor, :conditions => ['salary <= ?', 80000], :order => 'salary'
def self.per_page() 10 end
end

View file

@ -1,7 +1,6 @@
action_controller:
id: 2
name: Active Controller
active_record:
id: 1
name: Active Record
action_controller:
id: 2
name: Active Controller

View file

@ -1,5 +1,7 @@
class Reply < ActiveRecord::Base
belongs_to :topic, :include => [:replies]
named_scope :recent, :conditions => ['replies.created_at > ?', 15.minutes.ago]
validates_presence_of :content
end

View file

@ -1,4 +1,6 @@
class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC'
belongs_to :project
named_scope :mentions_activerecord, :conditions => ['topics.title LIKE ?', '%ActiveRecord%']
end

View file

@ -4,7 +4,7 @@ require 'rubygems'
# gem install redgreen for colored test output
begin require 'redgreen'; rescue LoadError; end
require File.join(File.dirname(__FILE__), 'boot') unless defined?(ActiveRecord)
require 'boot' unless defined?(ActiveRecord)
class Test::Unit::TestCase
protected
@ -13,6 +13,18 @@ class Test::Unit::TestCase
[method.to_s, method.to_sym].each { |m| assert_respond_to object, m }
end
end
def collect_deprecations
old_behavior = WillPaginate::Deprecation.behavior
deprecations = []
WillPaginate::Deprecation.behavior = Proc.new do |message, callstack|
deprecations << message
end
result = yield
[result, deprecations]
ensure
WillPaginate::Deprecation.behavior = old_behavior
end
end
# Wrap tests that use Mocha and skip if unavailable.

View file

@ -1,4 +1,4 @@
require File.join(File.dirname(__FILE__), 'activerecord_test_connector')
require 'lib/activerecord_test_connector'
class ActiveRecordTestCase < Test::Unit::TestCase
# Set our fixture path
@ -18,6 +18,19 @@ class ActiveRecordTestCase < Test::Unit::TestCase
# Default so Test::Unit::TestCase doesn't complain
def test_truth
end
protected
def assert_queries(num = 1)
$query_count = 0
yield
ensure
assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
end
def assert_no_queries(&block)
assert_queries(0, &block)
end
end
ActiveRecordTestConnector.setup

View file

@ -6,6 +6,8 @@ class ActiveRecordTestConnector
cattr_accessor :able_to_connect
cattr_accessor :connected
FIXTURES_PATH = File.join(File.dirname(__FILE__), '..', 'fixtures')
# Set our defaults
self.connected = false
self.able_to_connect = true
@ -14,13 +16,12 @@ class ActiveRecordTestConnector
unless self.connected || !self.able_to_connect
setup_connection
load_schema
# require_fixture_models
Dependencies.load_paths.unshift(File.dirname(__FILE__) + "/../fixtures")
Dependencies.load_paths.unshift FIXTURES_PATH
self.connected = true
end
rescue Exception => e # errors from ActiveRecord setup
$stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
#$stderr.puts " #{e.backtrace.join("\n ")}\n"
$stderr.puts "\nSkipping ActiveRecord tests: #{e}"
$stderr.puts "Install SQLite3 to run the full test suite for will_paginate.\n\n"
self.able_to_connect = false
end
@ -38,7 +39,7 @@ class ActiveRecordTestConnector
ActiveRecord::Base.establish_connection(configuration)
ActiveRecord::Base.configurations = { db => configuration }
ActiveRecord::Base.connection
prepare ActiveRecord::Base.connection
unless Object.const_defined?(:QUOTED_TYPE)
Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')
@ -48,13 +49,21 @@ class ActiveRecordTestConnector
def self.load_schema
ActiveRecord::Base.silence do
ActiveRecord::Migration.verbose = false
load File.dirname(__FILE__) + "/../fixtures/schema.rb"
load File.join(FIXTURES_PATH, 'schema.rb')
end
end
def self.require_fixture_models
models = Dir.glob(File.dirname(__FILE__) + "/../fixtures/*.rb")
models = (models.grep(/user.rb/) + models).uniq
models.each { |f| require f }
def self.prepare(conn)
class << conn
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SHOW FIELDS /]
def execute_with_counting(sql, name = nil, &block)
$query_count ||= 0
$query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
execute_without_counting(sql, name, &block)
end
alias_method_chain :execute, :counting
end
end
end

View file

@ -1,21 +0,0 @@
require 'action_controller/test_process'
module HTML
class Node
def inner_text
children.map(&:inner_text).join('')
end
end
class Text
def inner_text
self.to_s
end
end
class Tag
def inner_text
childless?? '' : super
end
end
end

View file

@ -1,13 +1,11 @@
dirname = File.dirname(__FILE__)
require File.join(dirname, '..', 'boot')
require File.join(dirname, 'activerecord_test_connector')
require 'boot'
require 'lib/activerecord_test_connector'
# setup the connection
ActiveRecordTestConnector.setup
# load all fixtures
fixture_path = File.join(dirname, '..', 'fixtures')
Fixtures.create_fixtures(fixture_path, ActiveRecord::Base.connection.tables)
Fixtures.create_fixtures(ActiveRecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables)
require 'will_paginate'
WillPaginate.enable_activerecord

View file

@ -0,0 +1,165 @@
require 'action_controller'
require 'action_controller/test_process'
require 'will_paginate'
WillPaginate.enable_actionpack
ActionController::Routing::Routes.draw do |map|
map.connect 'dummy/page/:page', :controller => 'dummy'
map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dots'
map.connect 'ibocorp/:page', :controller => 'ibocorp',
:requirements => { :page => /\d+/ },
:defaults => { :page => 1 }
map.connect ':controller/:action/:id'
end
ActionController::Base.perform_caching = false
class WillPaginate::ViewTestCase < Test::Unit::TestCase
def setup
super
@controller = DummyController.new
@request = @controller.request
@html_result = nil
@template = '<%= will_paginate collection, options %>'
@view = ActionView::Base.new
@view.assigns['controller'] = @controller
@view.assigns['_request'] = @request
@view.assigns['_params'] = @request.params
end
def test_no_complain; end
protected
def paginate(collection = {}, options = {}, &block)
if collection.instance_of? Hash
page_options = { :page => 1, :total_entries => 11, :per_page => 4 }.merge(collection)
collection = [1].paginate(page_options)
end
locals = { :collection => collection, :options => options }
if defined? ActionView::InlineTemplate
# Rails 2.1
args = [ ActionView::InlineTemplate.new(@view, @template, locals) ]
else
# older Rails versions
args = [nil, @template, nil, locals]
end
@html_result = @view.render_template(*args)
@html_document = HTML::Document.new(@html_result, true, false)
if block_given?
classname = options[:class] || WillPaginate::ViewHelpers.pagination_options[:class]
assert_select("div.#{classname}", 1, 'no main DIV', &block)
end
end
def response_from_page_or_rjs
@html_document.root
end
def validate_page_numbers expected, links, param_name = :page
param_pattern = /\W#{CGI.escape(param_name.to_s)}=([^&]*)/
assert_equal(expected, links.map { |e|
e['href'] =~ param_pattern
$1 ? $1.to_i : $1
})
end
def assert_links_match pattern, links = nil, numbers = nil
links ||= assert_select 'div.pagination a[href]' do |elements|
elements
end
pages = [] if numbers
links.each do |el|
assert_match pattern, el['href']
if numbers
el['href'] =~ pattern
pages << ($1.nil?? nil : $1.to_i)
end
end
assert_equal numbers, pages, "page numbers don't match" if numbers
end
def assert_no_links_match pattern
assert_select 'div.pagination a[href]' do |elements|
elements.each do |el|
assert_no_match pattern, el['href']
end
end
end
end
class DummyRequest
attr_accessor :symbolized_path_parameters
def initialize
@get = true
@params = {}
@symbolized_path_parameters = { :controller => 'foo', :action => 'bar' }
end
def get?
@get
end
def post
@get = false
end
def relative_url_root
''
end
def params(more = nil)
@params.update(more) if more
@params
end
end
class DummyController
attr_reader :request
attr_accessor :controller_name
def initialize
@request = DummyRequest.new
@url = ActionController::UrlRewriter.new(@request, @request.params)
end
def params
@request.params
end
def url_for(params)
@url.rewrite(params)
end
end
module HTML
Node.class_eval do
def inner_text
children.map(&:inner_text).join('')
end
end
Text.class_eval do
def inner_text
self.to_s
end
end
Tag.class_eval do
def inner_text
childless?? '' : super
end
end
end

View file

@ -1,272 +0,0 @@
require File.dirname(__FILE__) + '/helper'
require 'action_controller'
require File.dirname(__FILE__) + '/lib/html_inner_text'
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end
ActionController::Base.perform_caching = false
require 'will_paginate'
WillPaginate.enable_actionpack
class PaginationTest < Test::Unit::TestCase
class DevelopersController < ActionController::Base
def list_developers
@options = session[:wp] || {}
@developers = (1..11).to_a.paginate(
:page => params[@options[:param_name] || :page] || 1,
:per_page => params[:per_page] || 4
)
render :inline => '<%= will_paginate @developers, @options %>'
end
def guess_collection_name
@developers = session[:wp]
@options = session[:wp_options]
render :inline => '<%= will_paginate @options %>'
end
protected
def rescue_errors(e) raise e end
def rescue_action(e) raise e end
end
def setup
@controller = DevelopersController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
super
end
def test_will_paginate
get :list_developers
entries = assigns :developers
assert entries
assert_equal 4, entries.size
assert_select 'div.pagination', 1, 'no main DIV' do |pagination|
assert_select 'a[href]', 3 do |elements|
validate_page_numbers [2,3,2], elements
assert_select elements.last, ':last-child', "Next &raquo;"
end
assert_select 'span', 2
assert_select 'span.disabled:first-child', "&laquo; Previous"
assert_select 'span.current', entries.current_page.to_s
assert_equal '&laquo; Previous 1 2 3 Next &raquo;', pagination.first.inner_text
end
end
def test_will_paginate_with_options
get :list_developers, { :page => 2 }, :wp => {
:class => 'will_paginate', :prev_label => 'Prev', :next_label => 'Next'
}
assert_response :success
entries = assigns :developers
assert entries
assert_equal 4, entries.size
assert_select 'div.will_paginate', 1, 'no main DIV' do
assert_select 'a[href]', 4 do |elements|
validate_page_numbers [1,1,3,3], elements
# test rel attribute values:
assert_select elements[1], 'a', '1' do |link|
assert_equal 'prev start', link.first['rel']
end
assert_select elements.first, 'a', "Prev" do |link|
assert_equal 'prev start', link.first['rel']
end
assert_select elements.last, 'a', "Next" do |link|
assert_equal 'next', link.first['rel']
end
end
assert_select 'span.current', entries.current_page.to_s
end
end
def test_will_paginate_without_container
get :list_developers, {}, :wp => { :container => false }
assert_select 'div.pagination', 0, 'no main DIV'
assert_select 'a[href]', 3
end
def test_will_paginate_without_page_links
get :list_developers, { :page => 2 }, :wp => { :page_links => false }
assert_select 'a[href]', 2 do |elements|
validate_page_numbers [1,3], elements
end
end
def test_will_paginate_preserves_parameters_on_get
get :list_developers, :foo => { :bar => 'baz' }
assert_links_match /foo%5Bbar%5D=baz/
end
def test_will_paginate_doesnt_preserve_parameters_on_post
post :list_developers, :foo => 'bar'
assert_no_links_match /foo=bar/
end
def test_adding_additional_parameters
get :list_developers, {}, :wp => { :params => { :foo => 'bar' } }
assert_links_match /foo=bar/
end
def test_removing_arbitrary_parameters
get :list_developers, { :foo => 'bar' }, :wp => { :params => { :foo => nil } }
assert_no_links_match /foo=bar/
end
def test_adding_additional_route_parameters
get :list_developers, {}, :wp => { :params => { :controller => 'baz' } }
assert_links_match %r{\Wbaz/list_developers\W}
end
def test_will_paginate_with_custom_page_param
get :list_developers, { :developers_page => 2 }, :wp => { :param_name => :developers_page }
assert_response :success
entries = assigns :developers
assert entries
assert_equal 4, entries.size
assert_select 'div.pagination', 1, 'no main DIV' do
assert_select 'a[href]', 4 do |elements|
validate_page_numbers [1,1,3,3], elements, :developers_page
end
assert_select 'span.current', entries.current_page.to_s
end
end
def test_will_paginate_windows
get :list_developers, { :page => 6, :per_page => 1 }, :wp => { :inner_window => 1 }
assert_response :success
entries = assigns :developers
assert entries
assert_equal 1, entries.size
assert_select 'div.pagination', 1, 'no main DIV' do |pagination|
assert_select 'a[href]', 8 do |elements|
validate_page_numbers [5,1,2,5,7,10,11,7], elements
assert_select elements.first, 'a', "&laquo; Previous"
assert_select elements.last, 'a', "Next &raquo;"
end
assert_select 'span.current', entries.current_page.to_s
assert_equal '&laquo; Previous 1 2 ... 5 6 7 ... 10 11 Next &raquo;', pagination.first.inner_text
end
end
def test_will_paginate_eliminates_small_gaps
get :list_developers, { :page => 6, :per_page => 1 }, :wp => { :inner_window => 2 }
assert_response :success
assert_select 'div.pagination', 1, 'no main DIV' do
assert_select 'a[href]', 12 do |elements|
validate_page_numbers [5,1,2,3,4,5,7,8,9,10,11,7], elements
end
end
end
def test_no_pagination
get :list_developers, :per_page => 12
entries = assigns :developers
assert_equal 1, entries.page_count
assert_equal 11, entries.size
assert_equal '', @response.body
end
def test_faulty_input_raises_error
assert_raise WillPaginate::InvalidPage do
get :list_developers, :page => 'foo'
end
end
uses_mocha 'helper internals' do
def test_collection_name_can_be_guessed
collection = mock
collection.expects(:page_count).returns(1)
get :guess_collection_name, {}, :wp => collection
end
end
def test_inferred_collection_name_raises_error_when_nil
ex = assert_raise ArgumentError do
get :guess_collection_name, {}, :wp => nil
end
assert ex.message.include?('@developers')
end
def test_setting_id_for_container
get :list_developers
assert_select 'div.pagination', 1 do |div|
assert_nil div.first['id']
end
# magic ID
get :list_developers, {}, :wp => { :id => true }
assert_select 'div.pagination', 1 do |div|
assert_equal 'fixnums_pagination', div.first['id']
end
# explicit ID
get :list_developers, {}, :wp => { :id => 'custom_id' }
assert_select 'div.pagination', 1 do |div|
assert_equal 'custom_id', div.first['id']
end
end
if ActionController::Base.respond_to? :rescue_responses
def test_rescue_response_hook_presence
assert_equal :not_found,
DevelopersController.rescue_responses['WillPaginate::InvalidPage']
end
end
protected
def validate_page_numbers expected, links, param_name = :page
param_pattern = /\W#{param_name}=([^&]*)/
assert_equal(expected, links.map { |e|
e['href'] =~ param_pattern
$1 ? $1.to_i : $1
})
end
def assert_links_match pattern
assert_select 'div.pagination a[href]' do |elements|
elements.each do |el|
assert_match pattern, el['href']
end
end
end
def assert_no_links_match pattern
assert_select 'div.pagination a[href]' do |elements|
elements.each do |el|
assert_no_match pattern, el['href']
end
end
end
end
class ViewHelpersTest < Test::Unit::TestCase
include WillPaginate::ViewHelpers
def test_page_entries_info
arr = ('a'..'z').to_a
collection = arr.paginate :page => 2, :per_page => 5
assert_equal %{Displaying entries <b>6&nbsp;-&nbsp;10</b> of <b>26</b> in total},
page_entries_info(collection)
collection = arr.paginate :page => 7, :per_page => 4
assert_equal %{Displaying entries <b>25&nbsp;-&nbsp;26</b> of <b>26</b> in total},
page_entries_info(collection)
end
end

View file

@ -0,0 +1,56 @@
require 'rake/testtask'
desc 'Test the will_paginate plugin.'
Rake::TestTask.new(:test) do |t|
t.pattern = 'test/**/*_test.rb'
t.verbose = true
t.libs << 'test'
end
# I want to specify environment variables at call time
class EnvTestTask < Rake::TestTask
attr_accessor :env
def ruby(*args)
env.each { |key, value| ENV[key] = value } if env
super
env.keys.each { |key| ENV.delete key } if env
end
end
for configuration in %w( sqlite3 mysql postgres )
EnvTestTask.new("test_#{configuration}") do |t|
t.pattern = 'test/finder_test.rb'
t.verbose = true
t.env = { 'DB' => configuration }
t.libs << 'test'
end
end
task :test_databases => %w(test_mysql test_sqlite3 test_postgres)
desc %{Test everything on SQLite3, MySQL and PostgreSQL}
task :test_full => %w(test test_mysql test_postgres)
desc %{Test everything with Rails 1.2.x and 2.0.x gems}
task :test_all do
all = Rake::Task['test_full']
ENV['RAILS_VERSION'] = '~>1.2.6'
all.invoke
# reset the invoked flag
%w( test_full test test_mysql test_postgres ).each do |name|
Rake::Task[name].instance_variable_set '@already_invoked', false
end
# do it again
ENV['RAILS_VERSION'] = '~>2.0.2'
all.invoke
end
task :rcov do
excludes = %w( lib/will_paginate/named_scope*
lib/will_paginate/core_ext.rb
lib/will_paginate.rb
rails* )
system %[rcov -Itest:lib test/*.rb -x #{excludes.join(',')}]
end

View file

@ -0,0 +1,355 @@
require 'helper'
require 'lib/view_test_process'
class AdditionalLinkAttributesRenderer < WillPaginate::LinkRenderer
def initialize(link_attributes = nil)
super()
@additional_link_attributes = link_attributes || { :default => 'true' }
end
def page_link(page, text, attributes = {})
@template.link_to text, url_for(page), attributes.merge(@additional_link_attributes)
end
end
class ViewTest < WillPaginate::ViewTestCase
## basic pagination ##
def test_will_paginate
paginate do |pagination|
assert_select 'a[href]', 3 do |elements|
validate_page_numbers [2,3,2], elements
assert_select elements.last, ':last-child', "Next &raquo;"
end
assert_select 'span', 2
assert_select 'span.disabled:first-child', '&laquo; Previous'
assert_select 'span.current', '1'
assert_equal '&laquo; Previous 1 2 3 Next &raquo;', pagination.first.inner_text
end
end
def test_no_pagination_when_page_count_is_one
paginate :per_page => 30
assert_equal '', @html_result
end
def test_will_paginate_with_options
paginate({ :page => 2 },
:class => 'will_paginate', :prev_label => 'Prev', :next_label => 'Next') do
assert_select 'a[href]', 4 do |elements|
validate_page_numbers [1,1,3,3], elements
# test rel attribute values:
assert_select elements[1], 'a', '1' do |link|
assert_equal 'prev start', link.first['rel']
end
assert_select elements.first, 'a', "Prev" do |link|
assert_equal 'prev start', link.first['rel']
end
assert_select elements.last, 'a', "Next" do |link|
assert_equal 'next', link.first['rel']
end
end
assert_select 'span.current', '2'
end
end
def test_will_paginate_using_renderer_class
paginate({}, :renderer => AdditionalLinkAttributesRenderer) do
assert_select 'a[default=true]', 3
end
end
def test_will_paginate_using_renderer_instance
renderer = WillPaginate::LinkRenderer.new
renderer.gap_marker = '<span class="my-gap">~~</span>'
paginate({ :per_page => 2 }, :inner_window => 0, :outer_window => 0, :renderer => renderer) do
assert_select 'span.my-gap', '~~'
end
renderer = AdditionalLinkAttributesRenderer.new(:title => 'rendered')
paginate({}, :renderer => renderer) do
assert_select 'a[title=rendered]', 3
end
end
def test_prev_next_links_have_classnames
paginate do |pagination|
assert_select 'span.disabled.prev_page:first-child'
assert_select 'a.next_page[href]:last-child'
end
end
def test_full_output
paginate
expected = <<-HTML
<div class="pagination"><span class="disabled prev_page">&laquo; Previous</span>
<span class="current">1</span>
<a href="/foo/bar?page=2" rel="next">2</a>
<a href="/foo/bar?page=3">3</a>
<a href="/foo/bar?page=2" class="next_page" rel="next">Next &raquo;</a></div>
HTML
expected.strip!.gsub!(/\s{2,}/, ' ')
assert_dom_equal expected, @html_result
end
def test_escaping_of_urls
paginate({:page => 1, :per_page => 1, :total_entries => 2},
:page_links => false, :params => { :tag => '<br>' })
assert_select 'a[href]', 1 do |links|
query = links.first['href'].split('?', 2)[1]
assert_equal %w(page=2 tag=%3Cbr%3E), query.split('&amp;').sort
end
end
## advanced options for pagination ##
def test_will_paginate_without_container
paginate({}, :container => false)
assert_select 'div.pagination', 0, 'main DIV present when it shouldn\'t'
assert_select 'a[href]', 3
end
def test_will_paginate_without_page_links
paginate({ :page => 2 }, :page_links => false) do
assert_select 'a[href]', 2 do |elements|
validate_page_numbers [1,3], elements
end
end
end
def test_will_paginate_windows
paginate({ :page => 6, :per_page => 1 }, :inner_window => 1) do |pagination|
assert_select 'a[href]', 8 do |elements|
validate_page_numbers [5,1,2,5,7,10,11,7], elements
assert_select elements.first, 'a', '&laquo; Previous'
assert_select elements.last, 'a', 'Next &raquo;'
end
assert_select 'span.current', '6'
assert_equal '&laquo; Previous 1 2 &hellip; 5 6 7 &hellip; 10 11 Next &raquo;', pagination.first.inner_text
end
end
def test_will_paginate_eliminates_small_gaps
paginate({ :page => 6, :per_page => 1 }, :inner_window => 2) do
assert_select 'a[href]', 12 do |elements|
validate_page_numbers [5,1,2,3,4,5,7,8,9,10,11,7], elements
end
end
end
def test_container_id
paginate do |div|
assert_nil div.first['id']
end
# magic ID
paginate({}, :id => true) do |div|
assert_equal 'fixnums_pagination', div.first['id']
end
# explicit ID
paginate({}, :id => 'custom_id') do |div|
assert_equal 'custom_id', div.first['id']
end
end
## other helpers ##
def test_paginated_section
@template = <<-ERB
<% paginated_section collection, options do %>
<%= content_tag :div, '', :id => "developers" %>
<% end %>
ERB
paginate
assert_select 'div.pagination', 2
assert_select 'div.pagination + div#developers', 1
end
def test_page_entries_info
@template = '<%= page_entries_info collection %>'
array = ('a'..'z').to_a
paginate array.paginate(:page => 2, :per_page => 5)
assert_equal %{Displaying strings <b>6&nbsp;-&nbsp;10</b> of <b>26</b> in total},
@html_result
paginate array.paginate(:page => 7, :per_page => 4)
assert_equal %{Displaying strings <b>25&nbsp;-&nbsp;26</b> of <b>26</b> in total},
@html_result
end
def test_page_entries_info_with_longer_class_name
@template = '<%= page_entries_info collection %>'
collection = ('a'..'z').to_a.paginate
collection.first.stubs(:class).returns(mock('class', :name => 'ProjectType'))
paginate collection
assert @html_result.index('project types'), "expected <#{@html_result.inspect}> to mention 'project types'"
end
def test_page_entries_info_with_single_page_collection
@template = '<%= page_entries_info collection %>'
paginate(('a'..'d').to_a.paginate(:page => 1, :per_page => 5))
assert_equal %{Displaying <b>all 4</b> strings}, @html_result
paginate(['a'].paginate(:page => 1, :per_page => 5))
assert_equal %{Displaying <b>1</b> string}, @html_result
paginate([].paginate(:page => 1, :per_page => 5))
assert_equal %{No entries found}, @html_result
end
def test_page_entries_info_with_custom_entry_name
@template = '<%= page_entries_info collection, :entry_name => "author" %>'
entries = (1..20).to_a
paginate(entries.paginate(:page => 1, :per_page => 5))
assert_equal %{Displaying authors <b>1&nbsp;-&nbsp;5</b> of <b>20</b> in total}, @html_result
paginate(entries.paginate(:page => 1, :per_page => 20))
assert_equal %{Displaying <b>all 20</b> authors}, @html_result
paginate(['a'].paginate(:page => 1, :per_page => 5))
assert_equal %{Displaying <b>1</b> author}, @html_result
paginate([].paginate(:page => 1, :per_page => 5))
assert_equal %{No authors found}, @html_result
end
## parameter handling in page links ##
def test_will_paginate_preserves_parameters_on_get
@request.params :foo => { :bar => 'baz' }
paginate
assert_links_match /foo%5Bbar%5D=baz/
end
def test_will_paginate_doesnt_preserve_parameters_on_post
@request.post
@request.params :foo => 'bar'
paginate
assert_no_links_match /foo=bar/
end
def test_adding_additional_parameters
paginate({}, :params => { :foo => 'bar' })
assert_links_match /foo=bar/
end
def test_adding_anchor_parameter
paginate({}, :params => { :anchor => 'anchor' })
assert_links_match /#anchor$/
end
def test_removing_arbitrary_parameters
@request.params :foo => 'bar'
paginate({}, :params => { :foo => nil })
assert_no_links_match /foo=bar/
end
def test_adding_additional_route_parameters
paginate({}, :params => { :controller => 'baz', :action => 'list' })
assert_links_match %r{\Wbaz/list\W}
end
def test_will_paginate_with_custom_page_param
paginate({ :page => 2 }, :param_name => :developers_page) do
assert_select 'a[href]', 4 do |elements|
validate_page_numbers [1,1,3,3], elements, :developers_page
end
end
end
def test_complex_custom_page_param
@request.params :developers => { :page => 2 }
paginate({ :page => 2 }, :param_name => 'developers[page]') do
assert_select 'a[href]', 4 do |links|
assert_links_match /\?developers%5Bpage%5D=\d+$/, links
validate_page_numbers [1,1,3,3], links, 'developers[page]'
end
end
end
def test_custom_routing_page_param
@request.symbolized_path_parameters.update :controller => 'dummy', :action => nil
paginate :per_page => 2 do
assert_select 'a[href]', 6 do |links|
assert_links_match %r{/page/(\d+)$}, links, [2, 3, 4, 5, 6, 2]
end
end
end
def test_custom_routing_page_param_with_dot_separator
@request.symbolized_path_parameters.update :controller => 'dummy', :action => 'dots'
paginate :per_page => 2 do
assert_select 'a[href]', 6 do |links|
assert_links_match %r{/page\.(\d+)$}, links, [2, 3, 4, 5, 6, 2]
end
end
end
def test_custom_routing_with_first_page_hidden
@request.symbolized_path_parameters.update :controller => 'ibocorp', :action => nil
paginate :page => 2, :per_page => 2 do
assert_select 'a[href]', 7 do |links|
assert_links_match %r{/ibocorp(?:/(\d+))?$}, links, [nil, nil, 3, 4, 5, 6, 3]
end
end
end
## internal hardcore stuff ##
class LegacyCollection < WillPaginate::Collection
alias :page_count :total_pages
undef :total_pages
end
def test_deprecation_notices_with_page_count
collection = LegacyCollection.new(1, 1, 2)
assert_deprecated collection.class.name do
paginate collection
end
end
uses_mocha 'view internals' do
def test_collection_name_can_be_guessed
collection = mock
collection.expects(:total_pages).returns(1)
@template = '<%= will_paginate options %>'
@controller.controller_name = 'developers'
@view.assigns['developers'] = collection
paginate(nil)
end
end
def test_inferred_collection_name_raises_error_when_nil
@template = '<%= will_paginate options %>'
@controller.controller_name = 'developers'
e = assert_raise ArgumentError do
paginate(nil)
end
assert e.message.include?('@developers')
end
if ActionController::Base.respond_to? :rescue_responses
# only on Rails 2
def test_rescue_response_hook_presence
assert_equal :not_found,
ActionController::Base.rescue_responses['WillPaginate::InvalidPage']
end
end
end

View file

@ -0,0 +1,21 @@
Gem::Specification.new do |s|
s.name = 'will_paginate'
s.version = '2.3.2'
s.date = '2008-05-16'
s.summary = "Most awesome pagination solution for Rails"
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."
s.authors = ['Mislav Marohnić', 'PJ Hyett']
s.email = 'mislav.marohnic@gmail.com'
s.homepage = 'http://github.com/mislav/will_paginate/wikis'
s.has_rdoc = true
s.rdoc_options = ['--main', 'README.rdoc']
s.rdoc_options << '--inline-source' << '--charset=UTF-8'
s.extra_rdoc_files = ['README.rdoc', 'LICENSE', 'CHANGELOG']
s.add_dependency 'activesupport', ['>= 1.4.4']
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)
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)
end