views:

214

answers:

4

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 something 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?

A: 

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.

rogerdpack
I updated my question to be a bit more clear -- sorry about hat.
crankharder
A: 

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.

Martinos
I have added the proxied obj as a parameter of the block. This way it's easier to call direct object methods.
Martinos
A: 

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"]
heavysixer
+1  A: 

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.

KandadaBoggu
That didn't work out of the box, I had to include Aquarium::Aspects after the require statement. Also, now User.first produces a SystemStackError: stack level too deep - so something's recursive. But I like where this is going if I can get it to work.
crankharder