読者です 読者をやめる 読者になる 読者になる

ActiveRecordのSQL実行をフックしていろいろやる

ActiveRecordで、あるSQLが発行された時に、そのSQLが実行されるDBのコネクション情報を調べたいときがある。

Hoge.find() したときにどういうSQLが発行されているのかというのはdevelopment環境ならデフォルトでdevelopment.logにクエリログが出る。
このログ処理は active_record/connection_adapters/abstract_adapter.rb に書かれてる。

# active_record/connection_adapters/abstract_adapter.rb
198         def log(sql, name)
199           name ||= "SQL"
200           @instrumenter.instrument("sql.active_record",
201             :sql => sql, :name => name, :connection_id => object_id) do
202             yield
203           end
204         rescue Exception => e
205           message = "#{e.class.name}: #{e.message}: #{sql}"
206           @logger.debug message if @logger
207           raise translate_exception(e, message)
208         end
209 
210         def translate_exception(e, message)
211           # override in derived class
212           ActiveRecord::StatementInvalid.new(message)
213         end

これをフックして処理を書けばいい気がするけど、クエリログがオフになってる環境ではこのメソッドは実行されないのでちょっと不便。
だからこう書くことにする。*1

::ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
  def execute_with_logging(sql, name = nil)
    result = execute_without_logging(sql, name)
    # ここで解析処理を書く。
    # DBの情報は @config というハッシュに入ってる。
    # たとえば @config[:database] でDB名、 @config[:username] でDBのユーザ名が取得可能
    result
  end
  alias_method_chain :execute, :logging
end

ActiveRecordが発行するSQLは必ずDBアダプタのexecuteメソッドで実行されるので、それをフックしている。