2013-08-23 21:36:39 +00:00
module Shoulda # :nodoc:
2013-09-30 18:47:53 +00:00
module Matchers
module ActiveRecord # :nodoc:
# Ensures that the number of database queries is known. Rails 3.1 or greater is required.
#
# Options:
# * <tt>when_calling</tt> - Required, the name of the method to examine.
# * <tt>with</tt> - Used in conjunction with <tt>when_calling</tt> to pass parameters to the method to examine.
# * <tt>or_less</tt> - Pass if the database is queried no more than the number of times specified, as opposed to exactly that number of times.
#
# Examples:
2015-10-21 14:23:22 +00:00
# it { is_expected.to query_the_database(4.times).when_calling(:complicated_counting_method)
# it { is_expected.to query_the_database(4.times).or_less.when_calling(:generate_big_report)
2015-10-21 14:25:03 +00:00
# it { is_expected.not_to query_the_database.when_calling(:cached_count)
2013-09-30 18:47:53 +00:00
#
def query_the_database ( times = nil )
QueryTheDatabaseMatcher . new ( times )
end
class QueryTheDatabaseMatcher # :nodoc:
def initialize ( times )
@queries = [ ]
@options = { }
if times . respond_to? ( :count )
@options [ :expected_query_count ] = times . count
else
@options [ :expected_query_count ] = times
end
end
def when_calling ( method_name )
@options [ :method_name ] = method_name
self
end
def with ( * method_arguments )
@options [ :method_arguments ] = method_arguments
self
end
def or_less
@options [ :expected_count_is_maximum ] = true
self
end
def matches? ( subject )
subscriber = ActiveSupport :: Notifications . subscribe ( 'sql.active_record' ) do | name , started , finished , id , payload |
@queries << payload unless filter_query ( payload )
end
if @options [ :method_arguments ]
subject . send ( @options [ :method_name ] , * @options [ :method_arguments ] )
else
subject . send ( @options [ :method_name ] )
end
ActiveSupport :: Notifications . unsubscribe ( subscriber )
if @options [ :expected_count_is_maximum ]
@queries . length < = @options [ :expected_query_count ]
elsif @options [ :expected_query_count ] . present?
@queries . length == @options [ :expected_query_count ]
else
@queries . length > 0
end
end
def failure_message_for_should
if @options . key? ( :expected_query_count )
" Expected # #{ @options [ :method_name ] } to cause #{ @options [ :expected_query_count ] } database queries but it actually caused #{ @queries . length } queries: " + friendly_queries
else
" Expected # #{ @options [ :method_name ] } to query the database but it actually caused #{ @queries . length } queries: " + friendly_queries
end
end
def failure_message_for_should_not
if @options [ :expected_query_count ]
" Expected # #{ @options [ :method_name ] } to not cause #{ @options [ :expected_query_count ] } database queries but it actually caused #{ @queries . length } queries: " + friendly_queries
else
" Expected # #{ @options [ :method_name ] } to not query the database but it actually caused #{ @queries . length } queries: " + friendly_queries
end
end
private
def friendly_queries
@queries . map do | query |
" \n ( #{ query [ :name ] } ) #{ query [ :sql ] } "
end . join
end
def filter_query ( query )
query [ :name ] == 'SCHEMA' || looks_like_schema? ( query [ :sql ] )
end
def schema_terms
[ 'FROM sqlite_master' , 'PRAGMA' , 'SHOW TABLES' , 'SHOW KEYS FROM' , 'SHOW FIELDS FROM' , 'begin transaction' , 'commit transaction' ]
end
def looks_like_schema? ( sql )
schema_terms . any? { | term | sql . include? ( term ) }
end
end
end
end
2013-08-23 21:36:39 +00:00
end