1

I have a set of ActiveRecord models which I am pre-loading in order to avoid N+1 issues. In order to help that process along, I'd like to be able to do some manual testing where I have (conceptually) blocks that look like the following:

# Prepare some data
data = Foo.includes(:bar, {baz: :quux}).find(27)

ActiveRecord.raise_error_if_any_queries_occur
perform_some_work_with_data(data)
ActiveRecord.back_to_normal

This seems like there should be a way to do it, but I haven't had any luck. Has anyone else done this?

asthasr
  • 9,125
  • 1
  • 29
  • 43
  • 1
    slightly related - http://stackoverflow.com/q/26247288/525478 – Brad Werth Oct 13 '14 at 18:46
  • If you want to defer the query, why are you doing the `.find(27)` right away? – lurker Oct 13 '14 at 21:56
  • @lurker -- That happens beforehand. I do not want to defer the query, I want to do it all ahead of time, and then during the section where I am doing work I want to avoid implicit queries. – asthasr Oct 14 '14 at 12:27

4 Answers4

5

You can disconnect ActiveRecord from the database and reconnect it when you are finished:

data = Foo.includes(:bar, {baz: :quux}).find(27)

Foo.connection.disconnect!
perform_some_work_with_data(data)
Foo.connection.reconnect!

This will raise an error if anything tries to run a query against the database while ActiveRecord is disconnected.

infused
  • 24,000
  • 13
  • 68
  • 78
0

You can disconnect from database by following:

ActiveRecord::Base.connection.disconnect!
Rails.logger.info('Disconnected from database')

perform_some_work_with_data(data)

ActiveRecord::Base.establish_connection
Rails.logger.info('Connected to database')

But I think it's a little bit weird. And I prefer using bullet gem for N+1 query detection.

Alexander Karmes
  • 2,438
  • 1
  • 28
  • 34
0

Queries in rails will at some point call the select_all(arel, name = nil, binds = []) method as defined here: https://github.com/rails/rails/blob/89a7187cc0d893da67f53d3215a33043905d68ed/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb#L22

You can see that this method will handle the query to the select(sql, name = nil, binds = []) method. Furthermore, this method handles the query to his parent method because of the use of undef_method :select which you can se in it's definition: https://github.com/rails/rails/blob/89a7187cc0d893da67f53d3215a33043905d68ed/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb#L354

Finally, this means that the select method that is executed is the ConnectionAdapter method, for example the posgresql adapter, as you can see in line 946 of: https://github.com/rails/rails/blob/89a7187cc0d893da67f53d3215a33043905d68ed/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

So, first option: -You could edit one of those files to include some information that they were called, in a log for example.

Second, I don't know if it would work: - You could rewrite the select_all(arel, name = nil, binds = []) to raise an exception like:

  def select_all(arel, name = nil, binds = [])
    raise "error"
  end

and when you want to cancel this overwrite you would undef your method with:

undef_method :select_all

So you would pass the select_all to the parent.

But Again, I don't know if the second works, but the first certainly works, although it's very invasive to edit rails core to include a log functionality.

Joao Cunha
  • 772
  • 4
  • 15
-2

If you remove find(27) and use where(id: 27) you can append .to_sql to the end.

data = Foo.includes(:bar, {baz: :quux}).where(id: 27).to_sql

and then later execute it with:

Foo.find_by_sql(data)

You can also accomplish the same thing with Arel. Arel will let you build more complex queries which you can execute when you so desire.

6ft Dan
  • 2,365
  • 1
  • 33
  • 46