Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend the ActiveSupport::Notifications.subscribe, instantiation.active_record hook

I am exploring ActiveSupport::Notifications, and would like more information about 'instantiation.active_record', than just the :record_count and :class_name [1]. For example,

ActiveSupport::Notifications.subscribe /instantiation.active_record/ do |*args|

  args.status #Database or ActiveRecord return status
  args.result #The actual result set returned
  args.etc .. #Any other info I can collect on the event

  # Additional code ...
end

Can this be done, and if so, how?

Update: Thanks to Tom and Pavel below. Part of the challenge, is that I am building a 3rd party gem, that will analyze all SQL returns of the host app, without requiring the modification of their code. Basically, plug it into the Gemfile, do a bundle, and away you go.

I am considering extending ActiveRecord::Base, and using the method append_info_to_payload, but thus far have neither working in a gem.

As an example, I would like to overwrite the ActiveRecord::Querying find_by_sql() method's payload, to look like this:

payload = {
  record_count: result_set.length,
  class_name: name,
  result_set: result_set
}

In testing, and manually editing my local ActiveRecord gem, this delivers the complete result_set via ActiveSupport::Notifications pub/sub.

Answer: So, the answer is to use the class_eval method, and overwrite the find_by_sql method in ActiveRecord::Querying:

ActiveRecord::Querying.class_eval do
  def find_by_sql(sql, binds = [], preparable: nil, &block)
    result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
    column_types = result_set.column_types.dup
    columns_hash.each_key { |k| column_types.delete k }
    message_bus = ActiveSupport::Notifications.instrumenter

    payload = {
        record_count: result_set.length,
        class_name: name,
        result_set: result_set
    }

    message_bus.instrument("instantiation.active_record", payload) do
      result_set.map { |record| instantiate(record, column_types, &block) }
    end
  end
end

I added this to my gem's initializer.rb file.

Final: I created two pull requests on ActiveRecord, to have the result_set pass forward in the payload for use in ActiveSupport::Notifications: querying.rb and join_dependency.rb.

like image 286
Martin Sommer Avatar asked Dec 03 '18 22:12

Martin Sommer


2 Answers

Can this be done, and if so, how?

I don't think so.

Take a look at this and this - in both cases payload is a Hash with only record_count and class_name options. No extra options than described in the guide.

like image 99
Pavel Mikhailyuk Avatar answered Dec 27 '22 09:12

Pavel Mikhailyuk


You can create a custom hook with ::instrument. You can then define a payload that has status, result_set, etc, and define their behavior in the block.

You can find more robust documentation on ::instrument and ::subscribe here.

EDIT: I was looking into this more and came across this stack overflow question. It looks like the method append_info_to_payload is what you are looking for. It has been moved to a private method is Rails 5.

One last edit: If you are feeling brave the notification is used here and here if you want to insert your extra payload directly 😶.

like image 24
Tom Avatar answered Dec 27 '22 09:12

Tom