开发者

Make all subclasses of ActiveRecord::Base methods say their name

开发者 https://www.devze.com 2022-12-22 08:00 出处:网络
For cruft-removal purposes I would like to log whenever a method from one of my AR models is called. I can get get all those classes with s开发者_StackOverflow社区omething like this:

For cruft-removal purposes I would like to log whenever a method from one of my AR models is called.

I can get get all those classes with s开发者_StackOverflow社区omething like this:

subclasses = [] ; ObjectSpace.each_object(Module) {|m| subclasses << m if m.ancestors.include? ActiveRecord::Base } ; subclasses.map(&:name)

But then I need a list of only the methods defined on those classes (instance and class methods), and a way to inject a logger statement in them.

The result would be the equivalent of inserting this into every method

def foo
  logger.info "#{class.name} - #{__method__}"
  # ...
end

def self.foo
  logger.info  "#{name} - #{__method__}"
  # ...
end

How can I do that without actually adding it to every single method?

Some awesome meta perhaps?


If you want only the methods defined in the class you can do this:

>> Project.instance_methods
=> ["const_get", "validates_associated", "before_destroy_callback_chain", "reset_mocha", "parent_name", "inspect", "slug_normalizer_block", "set_sequence_name", "require_library_or_gem", "method_exists?", "valid_keys_for_has_and_belongs_to_many_association=", "table_name=", "validate_find_options_without_friendly", "quoted_table_name" (another 100 or so methods)]

Only the methods defined in your class

>> Project.instance_methods(false)
=> ["featured_asset", "category_list", "before_save_associated_records_for_slugs", "asset_ids", "primary_asset", "friendly_id_options", "description", "description_plain"]


You should be using Aspect Oriented Programming pattern for this. In Ruby Aquarium gem provides the AOP DSL.

Create a log_method_initializer.rb in config/initializers/ directory.

require 'aquarium'
Aspect.new(:around, :calls_to => :all_methods, 
            :in_types => [ActiveRecord::Base] ) do |join_point, object, *args|

  log "Entering: #{join_point.target_type.name}##{join_point.method_name}" 

  result = join_point.proceed

  log "Leaving: #{join_point.target_type.name}##{join_point.method_name}" 

  result  
end

Every method calls of classes inherited from ActiveRecord::Base will be logged.


You have

AR::Base.instance_methods

and AR::Base.class_eval "some string"

so you can probably use them to put a header on every existing method.


For instance method call you can use this proxy pattern:

class BlankSlate
   instance_methods.each { |m| undef_method m unless m =~ /^__/ }
end

class MyProxy < BlankSlate
  def initialize(obj, &proc)
    @proc = proc
    @obj = obj
  end

  def method_missing(sym, *args, &block)
    @proc.call(@obj,sym, *args)
    @obj.__send__(sym, *args, &block)
  end
end

Example:

cust = Customer.first
cust = MyProxy.new(cust) do |obj, method_name, *args|
  ActiveRecord::Base.logger.info  "#{obj.class}##{method_name}"
end

cust.city

# This will log:
# Customer#city

This is inspired from: http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc

You will need to find a way to apply this pattern on ActiveRecord::Base object creation.


For Aquarium, seems like adding method_options => :exclude_ancestor_methods does the trick. I had the stack too deep problem as well.

Source http://andrzejonsoftware.blogspot.com/2011/08/tracing-aspect-for-rails-application.html

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号