davidrac
davidrac

Reputation: 10738

How to test the number of database calls in Rails

I am creating a REST API in rails. I'm using RSpec. I'd like to minimize the number of database calls, so I would like to add an automatic test that verifies the number of database calls being executed as part of a certain action. Is there a simple way to add that to my test? What I'm looking for is some way to monitor/record the calls that are being made to the database as a result of a single API call. If this can't be done with RSpec but can be done with some other testing tool, that's also great.

Upvotes: 3

Views: 2813

Answers (4)

thisismydesign
thisismydesign

Reputation: 25054

Use the db-query-matchers gem.

expect { subject.make_one_query }.to make_database_queries(count: 1)

Upvotes: 1

Jesse Shieh
Jesse Shieh

Reputation: 4850

Fredrick's answer worked great for me, but in my case, I also wanted to know the number of calls for each ActiveRecord class individually. I made some modifications and ended up with this in case it's useful for others.

class SqlCounter< ActiveSupport::LogSubscriber

  # Returns the number of database "Loads" for a given ActiveRecord class.
  def self.count(clazz)
    name = clazz.name + ' Load'
    Thread.current['log'] ||= {}
    Thread.current['log'][name] || 0
  end

  # Returns a list of ActiveRecord classes that were counted.
  def self.counted_classes
    log = Thread.current['log']
    loads = log.keys.select {|key| key =~ /Load$/ }
    loads.map { |key| Object.const_get(key.split.first) }
  end

  def self.reset_count
    Thread.current['log'] = {}
  end

  def sql(event)
    name = event.payload[:name]
    Thread.current['log'] ||= {}
    Thread.current['log'][name] ||= 0
    Thread.current['log'][name] += 1
  end
end

SqlCounter.attach_to :active_record

expect do
  # do stuff
end.to change(SqlCounter, :count).by(2)

Upvotes: 0

Frederick Cheung
Frederick Cheung

Reputation: 84114

The easiest thing in Rails 3 is probably to hook into the notifications api.

This subscriber

class SqlCounter< ActiveSupport::LogSubscriber

  def self.count= value
    Thread.current['query_count'] = value
  end

  def self.count
    Thread.current['query_count'] || 0
  end

  def self.reset_count
    result, self.count = self.count, 0
    result
  end

  def sql(event)
    self.class.count += 1
    puts "logged #{event.payload[:sql]}"
  end
end

SqlCounter.attach_to :active_record

will print every executed sql statement to the console and count them. You could then write specs such as

expect do
  # do stuff
end.to change(SqlCounter, :count).by(2)

You'll probably want to filter out some statements, such as ones starting/committing transactions or the ones active record emits to determine the structures of tables.

Upvotes: 3

Nerian
Nerian

Reputation: 16177

You may be interested in using explain. But that won't be automatic. You will need to analyse each action manually. But maybe that is a good thing, since the important thing is not the number of db calls, but their nature. For example: Are they using indexes?

Check this:

http://weblog.rubyonrails.org/2011/12/6/what-s-new-in-edge-rails-explain/

Upvotes: 1

Related Questions