J'ai ajouté la possibilité de vérifier les requêtes par table en me basant sur la solution de Yuriy.
# spec/support/query_counter.rb
require 'support/matchers/query_limit'
module ActiveRecord
class QueryCounter
attr_reader :queries
def initialize
@queries = Hash.new 0
end
def to_proc
lambda(&method(:callback))
end
def callback(name, start, finish, message_id, values)
sql = values[:sql]
if sql.include? 'SAVEPOINT'
table = :savepoints
else
finder = /select.+"(.+)"\..+from/i if sql.include? 'SELECT'
finder = /insert.+"(.+)".\(/i if sql.include? 'INSERT'
finder = /update.+"(.+)".+set/i if sql.include? 'UPDATE'
finder = /delete.+"(.+)" where/i if sql.include? 'DELETE'
table = sql.match(finder)&.send(:[],1)&.to_sym
end
@queries[table] += 1 unless %w(CACHE SCHEMA).include?(values[:name])
return @queries
end
def query_count(table = nil)
if table
@queries[table]
else
@queries.values.sum
end
end
end
end
Les matchers RSpec ressemblent à
# spec/support/matchers/query_limit.rb
RSpec::Matchers.define :exceed_query_limit do |expected, table|
supports_block_expectations
match do |block|
query_count(table, &block) > expected
end
def query_count(table, &block)
@counter = ActiveRecord::QueryCounter.new
ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
@counter.query_count table
end
failure_message_when_negated do |actual|
queries = 'query'.pluralize expected
table_name = table.to_s.singularize.humanize.downcase if table
out = "expected to run a maximum of #{expected}"
out += " #{table_name}" if table
out += " #{queries}, but got #{@counter.query_count table}"
end
end
RSpec::Matchers.define :meet_query_limit do |expected, table|
supports_block_expectations
match do |block|
if expected.is_a? Hash
results = queries_count(table, &block)
expected.all? { |table, count| results[table] == count }
else
query_count(&block) == expected
end
end
def queries_count(table, &block)
@counter = ActiveRecord::QueryCounter.new
ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
@counter.queries
end
def query_count(&block)
@counter = ActiveRecord::QueryCounter.new
ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
@counter.query_count
end
def message(expected, table, negated = false)
queries = 'query'.pluralize expected
if expected.is_a? Hash
results = @counter.queries
table, expected = expected.find { |table, count| results[table] != count }
end
table_name = table.to_s.singularize.humanize.downcase if table
out = 'expected to'
out += ' not' if negated
out += " run exactly #{expected}"
out += " #{table_name}" if table
out += " #{queries}, but got #{@counter.query_count table}"
end
failure_message do |actual|
message expected, table
end
failure_message_when_negated do |actual|
message expected, table, true
end
end
Utilisation
expect { MyModel.do_the_queries }.to_not meet_query_limit(3)
expect { MyModel.do_the_queries }.to meet_query_limit(3)
expect { MyModel.do_the_queries }.to meet_query_limit(my_models: 2, other_tables: 1)