mailr/vendor/plugins/will_paginate/spec/finders/active_record_spec.rb
2009-02-10 04:13:08 +10:00

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