Can't alter (decorate) ActiveRecord::Base#method_missing

Hallo – I’m trying to decorate the default behaviour of
ActiveRecord::Base#method_missing() with the following code:

class ActiveRecord::Base

alias_method( :b023_method_missing, :method_missing )
unless method_defined?( :b023_method_missing )

def method_missing( method, args )
puts "\n
**** – In method_missing()"
b023_method_missing( method, args )
end

end

This, to my thinking, should simply do what ActiveRecord normally does
with calls to the attributes like “model.name”, etc.
However, what is happening is that I am getting a “stack level too deep”
error.
When run in the console, I get “***** – In method_missing()” printed a
million times, then the stack overflow error.
It seems to be that the the call to alias_method is happening twice
(despite the unless guard), thus creating an infinite loop.
Has anyone successfully done something like this with ActiveRecord, and
if so, could they please tell me how!

Cheers,
Doug.

I’m not sure why that doesn’t work, unless the method_defined? call
actually hits the original method_missing.

In any case, is there a reason why you need to use alias_method instead
of just overwriting the method in your model class? You can then just
call super() to hit the original method. It’s what inheritance is there
for.

Bryan D. wrote:

In any case, is there a reason why you need to use alias_method instead
of just overwriting the method in your model class? You can then just
call super() to hit the original method. It’s what inheritance is there
for.

That’s what I’m looking at now.
Basically, I want to create a plugin that allows delegation of methods
to encapsulated models upon the raising of NoMethodError in its own
method_missing() method.
Cheers,
Doug.

Then what you probably want to do is define a module that, when
included, dynamically overwrites the method_missing method. You’d have
to do an “include MethodMissingWhackiness” in each class that makes use
of that functionality.

I’m not sure I understand your use case, though. You want to implement
subsets of your model’s functionality in separate models that will be
what, nested classes? And you’d like the parent model to transparently
delegate those methods? That seems somewhat confusing to me.

Maybe instead, you should consider implementing the subsets of methods
as modules and just including them into the parent class.

Bryan D. wrote:

Then what you probably want to do is define a module that, when
included, dynamically overwrites the method_missing method. You’d have
to do an “include MethodMissingWhackiness” in each class that makes use
of that functionality.

I’m not sure I understand your use case, though. You want to implement
subsets of your model’s functionality in separate models that will be
what, nested classes? And you’d like the parent model to transparently
delegate those methods? That seems somewhat confusing to me.

Maybe instead, you should consider implementing the subsets of methods
as modules and just including them into the parent class.

You could be right.
I think, however, that this could be useful as a simple plugin for those
situations where you have, say, a Person class that can take on the role
of Salesman, Customer, Manager, etc.
There is, in fact, a handy plugin that looks like it might answer this
at:
http://daveverwer.com/2007/3/16/activerecord-delegation

(I’ve just been referred to it by its author on a UK Ruby list.)

You’d have
to do an “include MethodMissingWhackiness” in each class that makes use
of that functionality.

Yes – although to make it look more “Railsy”, I’ve reopened
ActiveRecord::Base and put it in the following method:

def self.delegates_to( delegatee, options )

other setup stuff

include MethodMissingWhackiness
end

& cheers for your input, man!
Doug.

Hi again – just for completeness – I’ve got what I wanted to happen
working.
It’s an ongoing thing (so delegated validations are next), but the
working code so far (after considerable duck-punching) is:

require "active_record"

module Biot023
module ActsAsDelegator

def method_missing( method, *args )
  begin
    super
  rescue NoMethodError => err
    self.methods.grep( /^_fetch_delegatee_/ ).each do |mname|
      begin
        model = self.method( mname ).call
        raise( "#{ mname } failed to return model in method_missing" 

)
unless model
_method, _args = [ model.method( method.to_sym ), *args ]
rescue _method, _args = [ model.method( :method_missing ),
[ method, *args ] ]
return _args ? _method.call( *_args ) : _method.call
rescue => inner_err
# do nothing
end
end
raise
end
end

end
end

class ActiveRecord::Base

Delegate to a delegatee, whose id must be referenced from this

model, just

the same as a belongs_to() call. In fact, this takes the same

options as

belongs_to(), as it then calls it.

def self.delegates_to( delegatee, options={} )
classname = options[:class_name] || delegatee.to_s.camelize
belongs_to( delegatee.to_sym, options )
include Biot023::ActsAsDelegator
class_eval( <<-EOS, “_fetch_delegatee”, 1 )
def fetch_delegatee#{ delegatee.to_s }
self.#{ delegatee.to_s } ||= #{ classname }.new
self.#{ delegatee.to_s }
end
EOS
end

before_save do |model|
model.methods.grep( /^fetch_delegatee/ ).each do |mname|
del = model.method( mname ).call
del.save!
end
end

end

Cheers again,
Doug.