hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Hatching New Methods in Mid-air #

by why in inspect

Ken Miller sent in a very interesting class, which creates new methods as they are needed. And, also, the respond_to? method is hacked to respond positively in these cases. I could see this kind of thing going into Rails so you can Student.find_classes or Student.find_grades. That sort of thing.

 class MethodMagic

   alias_method :old_respond_to?, :respond_to?

   def respond_to? (method, include_private=false)
     old_respond_to?(method.to_sym, include_private) || 
     create_dynamic_method(method)
   end

   alias_method :old_method_missing, :method_missing

   def method_missing(method, *args)
     if create_dynamic_method(method)
       send(method.to_sym, *args)
     else
       old_method_missing(method)
     end
   end

   # If the method name conforms to our rigorous specifications, 
   # create a new body on the fly and define the sucker
   def create_dynamic_method(method)
     if method.to_s.match /^find_by_/
       self.class.send(:define_method, method.to_sym) { |*args|
         puts "Called #{method}(#{args.join ", "})" 
       } 
       true
     else
       false
     end
   end

   # just here as an example of a normal method
   def find; end

end

mm = MethodMagic.new

p mm.respond_to?(:find)            # => true
p mm.respond_to?(:find_by_letters) # => true
mm.find_by_letters("a", "b", "c")  # prints 'Called find_by_letters(a, b, c)'
mm.find_by_numbers(1, 2, 3)        # prints 'Called find_by_numbers(1, 2, 3)'

p mm.respond_to?(:poopy)           # => false
mm.poopy                           # raises NoMethodError

What about you? Do you have any metaprogramming tricks for us? If nothing else, read Ruby/X11 source and get back to me.

said on 30 May 2005 at 18:58

Well, Rails already has Student.find_by_first_name and so forth, though it doesn’t build new methods as they are requested. Doesn’t OpenStruct do something like this, too? (I’ve never really looked, so I don’t know for sure.)

said on 31 May 2005 at 09:44

I played with runtime-defined methods too some time ago. If the only reason you do this are speed issues, please benchmark and profile before you try this method.

In my tests, using method_missing only was faster than defining the methods on the fly (esp. on subsequent calls), I think this is because of Ruby’s method caching.

said on 31 May 2005 at 11:49

Depending on the approach used to add the method, there will be varying degrees of overhead when calling it.

While ugly, eschewing define_method and directly eval-ing a string containing a def clause will result in a method involving the least overhead to call.

Of course, with string evals you don’t get to take a closure or any of that other good stuff, and you have to be very careful about sanitizing/untainting your strings if you try to interpolate values into them—it’s quite easy to introduce a security hole!

If you do dynamically add methods, define_method with a block is the cleanest and safest approach by far.

Always profile before deciding to do things the ugly way. Remember Knuth: “Premature optimization is the root of all evil.”

said on 31 May 2005 at 12:14

I’ve read Redhanded for a while now, and sure, it’s funny, and sure I love Ruby…

but what the heck is the “good idea” that stands behind “creating methods at runtime” when it’s not possible for those methods to have guts that work correctly.

Unless I’m mistaken, “mm.perfectly_translate_english_to_arabic(‘hello, world’” won’t work at all.

I’m not trying to be mean, I just want to know “why this is a good idea”...

said on 31 May 2005 at 12:28

Just as a technical note, Student.find_classes and Student.find_grades would be class methods, which wouldn’t make sense. Only student objects would have classes associated with them. s = Student.find 1; s.find_classes

said on 31 May 2005 at 13:08

First of all, thanks for the feedback. That’s what I was hoping for when I posted it.

For certain kinds of things, it IS possible for them to have guts that work correctly, a la the find_* methods in ActiveRecord. Essentially, you’re parsing your method names and creating your own mini-language. (Pause while any java programmers reading run screaming from the room.) It means you don’t have to explicitly create every single permutatation of a class of methods if the meaning can be reasonably inferred from the method name.

As for performance, Mentalguy is correct, using class_eval (not instance_eval!) is about 20% faster, at least for the trivial cases I profiled. That surprised me. Both approaches are about twice as fast (in my tests) as just doing the work in method_missing every time, assuming you call the method lots of times. Given that chris2 seems to have seen different results, profiling in your own environment is a must if performance matters.

This approach might be cleaner and faster if the runtime were more aware of it…

said on 31 May 2005 at 13:16

I don’t think the idea here is to save memory or speed. The idea behind metaprogramming is to teach Ruby your conventions and let it do some guessing, in order to save you some code.

Think of how handy dynamic methods could potentially be in an extension like FXRuby, which has hundreds of wrapper methods, many of which could be minimized if, at some point, it could all be boiled down to a bit of introspection against the extension itself. You know?

Nothing against FXRuby. I only mention it because I’ve been working with it and its enormity of late.

Dave: This isn’t a “best practices” blog. I’m not about teaching a right or correct way here. In fact, I’d rather unearth some questionable practices, in the possibility that they may just need to be given a second chance. And I like the harshness: who can blame your gag reflex?

said on 31 May 2005 at 14:04

I just don’t see the point of dispatching the name and defining the methods when you could just dispatch… YAGNI .

said on 01 Jun 2005 at 10:01

What _why said. The point is to let you do things the most expressive way (the inverse of what I mean by “ugly”).

Fast or small, you can deal with later if you have to. Usually you don’t.

said on 01 Jun 2005 at 16:03

chris2: Well, one difference is that MethodMagic#methods will include any methods you’ve called.

said on 10 Jun 2005 at 15:54

why: Oh, I get it now. Actually, I kind of did think this was a “best practices + odd things” blog. d’oh. :)

Comments are closed for this entry.