460 lines
15 KiB
Ruby
460 lines
15 KiB
Ruby
require 'spec_helper'
|
|
require 'will_paginate/finders/active_record'
|
|
require File.dirname(__FILE__) + '/activerecord_test_connector'
|
|
|
|
require 'will_paginate'
|
|
WillPaginate::enable_named_scope
|
|
|
|
class ArProject < ActiveRecord::Base
|
|
def self.column_names
|
|
["id"]
|
|
end
|
|
|
|
named_scope :distinct, :select => "DISTINCT #{table_name}.*"
|
|
end
|
|
|
|
ActiverecordTestConnector.setup
|
|
|
|
describe WillPaginate::Finders::ActiveRecord do
|
|
|
|
extend ActiverecordTestConnector::FixtureSetup
|
|
|
|
it "should integrate with ActiveRecord::Base" do
|
|
ActiveRecord::Base.should respond_to(:paginate)
|
|
end
|
|
|
|
it "should paginate" do
|
|
ArProject.expects(:find).with(:all, { :limit => 5, :offset => 0 }).returns([])
|
|
ArProject.paginate(:page => 1, :per_page => 5)
|
|
end
|
|
|
|
it "should respond to paginate_by_sql" do
|
|
ArProject.should respond_to(:paginate_by_sql)
|
|
end
|
|
|
|
it "should support explicit :all argument" do
|
|
ArProject.expects(:find).with(:all, instance_of(Hash)).returns([])
|
|
ArProject.paginate(:all, :page => nil)
|
|
end
|
|
|
|
it "should put implicit all in dynamic finders" do
|
|
ArProject.expects(:find_all_by_foo).returns([])
|
|
ArProject.expects(:count).returns(0)
|
|
ArProject.paginate_by_foo :page => 2
|
|
end
|
|
|
|
it "should leave extra parameters intact" do
|
|
ArProject.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => 0 }).returns(Array.new(5))
|
|
ArProject.expects(:count).with({:foo => 'bar'}).returns(1)
|
|
|
|
ArProject.paginate :foo => 'bar', :page => 1, :per_page => 4
|
|
end
|
|
|
|
describe "counting" do
|
|
it "should ignore nil in :count parameter" do
|
|
ArProject.expects(:find).returns([])
|
|
lambda { ArProject.paginate :page => nil, :count => nil }.should_not raise_error
|
|
end
|
|
|
|
it "should guess the total count" do
|
|
ArProject.expects(:find).returns(Array.new(2))
|
|
ArProject.expects(:count).never
|
|
|
|
result = ArProject.paginate :page => 2, :per_page => 4
|
|
result.total_entries.should == 6
|
|
end
|
|
|
|
it "should guess that there are no records" do
|
|
ArProject.expects(:find).returns([])
|
|
ArProject.expects(:count).never
|
|
|
|
result = ArProject.paginate :page => 1, :per_page => 4
|
|
result.total_entries.should == 0
|
|
end
|
|
end
|
|
|
|
it "should not ignore :select parameter when it says DISTINCT" do
|
|
ArProject.stubs(:find).returns([])
|
|
ArProject.expects(:count).with(:select => 'DISTINCT salary').returns(0)
|
|
ArProject.paginate :select => 'DISTINCT salary', :page => 2
|
|
end
|
|
|
|
it "should count with scoped select when :select => DISTINCT" do
|
|
ArProject.stubs(:find).returns([])
|
|
ArProject.expects(:count).with(:select => 'DISTINCT ar_projects.id').returns(0)
|
|
ArProject.distinct.paginate :page => 2
|
|
end
|
|
|
|
it "should use :with_foo for scope-out compatibility" do
|
|
ArProject.expects(:find_best).returns(Array.new(5))
|
|
ArProject.expects(:with_best).returns(1)
|
|
|
|
ArProject.paginate_best :page => 1, :per_page => 4
|
|
end
|
|
|
|
describe "paginate_by_sql" do
|
|
it "should paginate" do
|
|
ArProject.expects(:find_by_sql).with(regexp_matches(/sql LIMIT 3(,| OFFSET) 3/)).returns([])
|
|
ArProject.expects(:count_by_sql).with('SELECT COUNT(*) FROM (sql) AS count_table').returns(0)
|
|
|
|
ArProject.paginate_by_sql 'sql', :page => 2, :per_page => 3
|
|
end
|
|
|
|
it "should respect total_entrier setting" do
|
|
ArProject.expects(:find_by_sql).returns([])
|
|
ArProject.expects(:count_by_sql).never
|
|
|
|
entries = ArProject.paginate_by_sql 'sql', :page => 1, :total_entries => 999
|
|
entries.total_entries.should == 999
|
|
end
|
|
|
|
it "should strip the order when counting" do
|
|
ArProject.expects(:find_by_sql).returns([])
|
|
ArProject.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS count_table").returns(0)
|
|
|
|
ArProject.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 2
|
|
end
|
|
|
|
it "shouldn't change the original query string" do
|
|
query = 'SQL QUERY'
|
|
original_query = query.dup
|
|
ArProject.expects(:find_by_sql).returns([])
|
|
|
|
ArProject.paginate_by_sql(query, :page => 1)
|
|
query.should == original_query
|
|
end
|
|
end
|
|
|
|
# TODO: counts would still be wrong!
|
|
it "should be able to paginate custom finders" do
|
|
# acts_as_taggable defines find_tagged_with(tag, options)
|
|
ArProject.expects(:find_tagged_with).with('will_paginate', :offset => 5, :limit => 5).returns([])
|
|
ArProject.expects(:count).with({}).returns(0)
|
|
|
|
ArProject.paginate_tagged_with 'will_paginate', :page => 2, :per_page => 5
|
|
end
|
|
|
|
it "should not skip count when given an array argument to a finder" do
|
|
ids = (1..8).to_a
|
|
ArProject.expects(:find_all_by_id).returns([])
|
|
ArProject.expects(:count).returns(0)
|
|
|
|
ArProject.paginate_by_id(ids, :per_page => 3, :page => 2, :order => 'id')
|
|
end
|
|
|
|
# Is this Rails 2.0? Find out by testing find_all which was removed in [6998]
|
|
unless ActiveRecord::Base.respond_to? :find_all
|
|
it "should paginate array of IDs" do
|
|
# AR finders also accept arrays of IDs
|
|
# (this was broken in Rails before [6912])
|
|
lambda {
|
|
result = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id')
|
|
result.map(&:id).should == (4..6).to_a
|
|
result.total_entries.should == 8
|
|
}.should run_queries(1)
|
|
end
|
|
end
|
|
|
|
it "doesn't mangle options" do
|
|
ArProject.expects(:find).returns([])
|
|
options = { :page => 1 }
|
|
options.expects(:delete).never
|
|
options_before = options.dup
|
|
|
|
ArProject.paginate(options)
|
|
options.should == options_before
|
|
end
|
|
|
|
if ::ActiveRecord::Calculations::CALCULATIONS_OPTIONS.include?(:from)
|
|
# for ActiveRecord 2.1 and newer
|
|
it "keeps the :from parameter in count" do
|
|
ArProject.expects(:find).returns([1])
|
|
ArProject.expects(:count).with {|options| options.key?(:from) }.returns(0)
|
|
ArProject.paginate(:page => 2, :per_page => 1, :from => 'projects')
|
|
end
|
|
else
|
|
it "excludes :from parameter from count" do
|
|
ArProject.expects(:find).returns([1])
|
|
ArProject.expects(:count).with {|options| !options.key?(:from) }.returns(0)
|
|
ArProject.paginate(:page => 2, :per_page => 1, :from => 'projects')
|
|
end
|
|
end
|
|
|
|
if ActiverecordTestConnector.able_to_connect
|
|
fixtures :topics, :replies, :users, :projects, :developers_projects
|
|
|
|
it "should get first page of Topics with a single query" do
|
|
lambda {
|
|
result = Topic.paginate :page => nil
|
|
result.current_page.should == 1
|
|
result.total_pages.should == 1
|
|
result.size.should == 4
|
|
}.should run_queries(1)
|
|
end
|
|
|
|
it "should get second (inexistent) page of Topics, requiring 2 queries" do
|
|
lambda {
|
|
result = Topic.paginate :page => 2
|
|
result.total_pages.should == 1
|
|
result.should be_empty
|
|
}.should run_queries(2)
|
|
end
|
|
|
|
it "should paginate with :order" do
|
|
result = Topic.paginate :page => 1, :order => 'created_at DESC'
|
|
result.should == topics(:futurama, :harvey_birdman, :rails, :ar).reverse
|
|
result.total_pages.should == 1
|
|
end
|
|
|
|
it "should paginate with :conditions" do
|
|
result = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago]
|
|
result.should == topics(:rails, :ar)
|
|
result.total_pages.should == 1
|
|
end
|
|
|
|
it "should paginate with :include and :conditions" do
|
|
result = Topic.paginate \
|
|
:page => 1,
|
|
:include => :replies,
|
|
:conditions => "replies.content LIKE 'Bird%' ",
|
|
:per_page => 10
|
|
|
|
expected = Topic.find :all,
|
|
:include => 'replies',
|
|
:conditions => "replies.content LIKE 'Bird%' ",
|
|
:limit => 10
|
|
|
|
result.should == expected
|
|
result.total_entries.should == 1
|
|
end
|
|
|
|
it "should paginate with :include and :order" do
|
|
result = nil
|
|
lambda {
|
|
result = Topic.paginate \
|
|
:page => 1,
|
|
:include => :replies,
|
|
:order => 'replies.created_at asc, topics.created_at asc',
|
|
:per_page => 10
|
|
}.should run_queries(2)
|
|
|
|
expected = Topic.find :all,
|
|
:include => 'replies',
|
|
:order => 'replies.created_at asc, topics.created_at asc',
|
|
:limit => 10
|
|
|
|
result.should == expected
|
|
result.total_entries.should == 4
|
|
end
|
|
|
|
# detect ActiveRecord 2.1
|
|
if ActiveRecord::Base.private_methods.include?('references_eager_loaded_tables?')
|
|
it "should remove :include for count" do
|
|
Developer.expects(:find).returns([1])
|
|
Developer.expects(:count).with({}).returns(0)
|
|
|
|
Developer.paginate :page => 1, :per_page => 1, :include => :projects
|
|
end
|
|
|
|
it "should keep :include for count when they are referenced in :conditions" do
|
|
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
|
|
|
|
describe "associations" do
|
|
it "should paginate with include" do
|
|
project = projects(:active_record)
|
|
|
|
result = project.topics.paginate \
|
|
:page => 1,
|
|
:include => :replies,
|
|
:conditions => ["replies.content LIKE ?", 'Nice%'],
|
|
:per_page => 10
|
|
|
|
expected = Topic.find :all,
|
|
:include => 'replies',
|
|
:conditions => ["project_id = #{project.id} AND replies.content LIKE ?", 'Nice%'],
|
|
:limit => 10
|
|
|
|
result.should == expected
|
|
end
|
|
|
|
it "should paginate" do
|
|
dhh = users(:david)
|
|
expected_name_ordered = projects(:action_controller, :active_record)
|
|
expected_id_ordered = projects(:active_record, :action_controller)
|
|
|
|
lambda {
|
|
# with association-specified order
|
|
result = dhh.projects.paginate(:page => 1)
|
|
result.should == expected_name_ordered
|
|
result.total_entries.should == 2
|
|
}.should run_queries(2)
|
|
|
|
# with explicit order
|
|
result = dhh.projects.paginate(:page => 1, :order => 'projects.id')
|
|
result.should == expected_id_ordered
|
|
result.total_entries.should == 2
|
|
|
|
lambda {
|
|
dhh.projects.find(:all, :order => 'projects.id', :limit => 4)
|
|
}.should_not raise_error
|
|
|
|
result = dhh.projects.paginate(:page => 1, :order => 'projects.id', :per_page => 4)
|
|
result.should == expected_id_ordered
|
|
|
|
# has_many with implicit order
|
|
topic = Topic.find(1)
|
|
expected = replies(:spam, :witty_retort)
|
|
# FIXME: wow, this is ugly
|
|
topic.replies.paginate(:page => 1).map(&:id).sort.should == expected.map(&:id).sort
|
|
topic.replies.paginate(:page => 1, :order => 'replies.id ASC').should == expected.reverse
|
|
end
|
|
|
|
it "should paginate through association extension" do
|
|
project = Project.find(:first)
|
|
expected = [replies(:brave)]
|
|
|
|
lambda {
|
|
result = project.replies.paginate_recent :page => 1
|
|
result.should == expected
|
|
}.should run_queries(1)
|
|
end
|
|
end
|
|
|
|
it "should paginate with joins" do
|
|
result = nil
|
|
join_sql = 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id'
|
|
|
|
lambda {
|
|
result = Developer.paginate :page => 1, :joins => join_sql, :conditions => 'project_id = 1'
|
|
result.size.should == 2
|
|
developer_names = result.map(&:name)
|
|
developer_names.should include('David')
|
|
developer_names.should include('Jamis')
|
|
}.should run_queries(1)
|
|
|
|
lambda {
|
|
expected = result.to_a
|
|
result = Developer.paginate :page => 1, :joins => join_sql,
|
|
:conditions => 'project_id = 1', :count => { :select => "users.id" }
|
|
result.should == expected
|
|
result.total_entries.should == 2
|
|
}.should run_queries(1)
|
|
end
|
|
|
|
it "should paginate with group" do
|
|
result = nil
|
|
lambda {
|
|
result = Developer.paginate :page => 1, :per_page => 10,
|
|
:group => 'salary', :select => 'salary', :order => 'salary'
|
|
}.should run_queries(1)
|
|
|
|
expected = users(:david, :jamis, :dev_10, :poor_jamis).map(&:salary).sort
|
|
result.map(&:salary).should == expected
|
|
end
|
|
|
|
it "should paginate with dynamic finder" do
|
|
expected = replies(:witty_retort, :spam)
|
|
Reply.paginate_by_topic_id(1, :page => 1).should == expected
|
|
|
|
result = Developer.paginate :conditions => { :salary => 100000 }, :page => 1, :per_page => 5
|
|
result.total_entries.should == 8
|
|
Developer.paginate_by_salary(100000, :page => 1, :per_page => 5).should == result
|
|
end
|
|
|
|
it "should paginate with dynamic finder and conditions" do
|
|
result = Developer.paginate_by_salary(100000, :page => 1, :conditions => ['id > ?', 6])
|
|
result.total_entries.should == 4
|
|
result.map(&:id).should == (7..10).to_a
|
|
end
|
|
|
|
it "should raise error when dynamic finder is not recognized" do
|
|
lambda {
|
|
Developer.paginate_by_inexistent_attribute 100000, :page => 1
|
|
}.should raise_error(NoMethodError)
|
|
end
|
|
|
|
it "should paginate with_scope" do
|
|
result = Developer.with_poor_ones { Developer.paginate :page => 1 }
|
|
result.size.should == 2
|
|
result.total_entries.should == 2
|
|
end
|
|
|
|
describe "named_scope" do
|
|
it "should paginate" do
|
|
result = Developer.poor.paginate :page => 1, :per_page => 1
|
|
result.size.should == 1
|
|
result.total_entries.should == 2
|
|
end
|
|
|
|
it "should paginate on habtm association" do
|
|
project = projects(:active_record)
|
|
lambda {
|
|
result = project.developers.poor.paginate :page => 1, :per_page => 1
|
|
result.size.should == 1
|
|
result.total_entries.should == 1
|
|
}.should run_queries(2)
|
|
end
|
|
|
|
it "should paginate on hmt association" do
|
|
project = projects(:active_record)
|
|
expected = [replies(:brave)]
|
|
|
|
lambda {
|
|
result = project.replies.recent.paginate :page => 1, :per_page => 1
|
|
result.should == expected
|
|
result.total_entries.should == 1
|
|
}.should run_queries(2)
|
|
end
|
|
|
|
it "should paginate on has_many association" do
|
|
project = projects(:active_record)
|
|
expected = [topics(:ar)]
|
|
|
|
lambda {
|
|
result = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1
|
|
result.should == expected
|
|
result.total_entries.should == 1
|
|
}.should run_queries(2)
|
|
end
|
|
end
|
|
|
|
it "should paginate with :readonly option" do
|
|
lambda { Developer.paginate :readonly => true, :page => 1 }.should_not raise_error
|
|
end
|
|
|
|
end
|
|
|
|
protected
|
|
|
|
def run_queries(num)
|
|
QueryCountMatcher.new(num)
|
|
end
|
|
|
|
end
|
|
|
|
class QueryCountMatcher
|
|
def initialize(num)
|
|
@queries = num
|
|
@old_query_count = $query_count
|
|
end
|
|
|
|
def matches?(block)
|
|
block.call
|
|
@queries_run = $query_count - @old_query_count
|
|
@queries == @queries_run
|
|
end
|
|
|
|
def failure_message
|
|
"expected #{@queries} queries, got #{@queries_run}"
|
|
end
|
|
|
|
def negative_failure_message
|
|
"expected query count not to be #{$queries}"
|
|
end
|
|
end |