views:

496

answers:

2

I would like to do something like the following:

class String
  def fancy_thing appendix
   # Just a trivial example to ensure self and params work.
   # Pretend this is something complex.
   self.reverse + appendix
  end
end

# print_method on instance or class should spit out a string
#  containing the actual code for that method
ft_code = "cob".print_method :fancy_thing
ft_code = String.print_instance_method :fancy_thing
  # => "{|appendix| self.reverse + appendix }"  *

# ft_code gets passed around a bit...

# exec on an object should run code (w/ parameters) as if that code is 
#  an instance method on that object (or class method if it's a class)
"cob".exec(ft_code, '!') #=> "boc!"

How might one code print_method and foo.exec? Preferably, they should work for any arbitrary methods, without knowing a priori where they might happen to have been defined or sourced from.

  • Yes, I know methods and blocks aren't completely the same. But this is closer to what yield and call would normally take; I don't know of a better solution.
A: 

This isn't very easy to implement without just reading the Ruby file and searching for the method code. In Ruby 1.8, you could use ParseTree and ruby2ruby. In 1.9, I don't know of any solution.

Chuck
+6  A: 

parse_tree will give you the key step that you'll need:

http://github.com/seattlerb/parsetree/tree/master

I think this does it, in the quickest/hackiest/most insecure manner possible:

require 'parse_tree'
require 'parse_tree_extensions'
require 'ruby2ruby'

class Object
  def method_source(name)
    (class << self; self; end).instance_method(name).to_ruby
  end

  def exec(ruby, *args)
    code = eval(ruby, binding)
    code.call(*args)
  end
end

I'll add that I'm having difficulty seeing how this is a good idea... But there you have it. :-)

[edit]

Also note that your example is busted: your 'fancy_thing' method requires an argument (appendix).

[edit 2]

going over the top, here's your test code with bugs fixed (the way I think you wanted it):

class String
  def fancy_thing(appendix)
    reverse << appendix || nil
  end
end

code = "cob".method_source :fancy_thing
# => "proc {|appendix| reverse << appendix }"  *
"cob".exec code, '!'
# => "boc!"
joshng
My intent for it is in irb / rails console, for easy debugging, method editing, etc (when not necessarily in the debugger proper).And just because I'm curious whether it can be *done* in 1.8 / 1.9. ;-)
Sai Emrys
Fixed the method in OP. Part of the point of its definition though was to use 'self', as a test to ensure that exec had it bound properly. But 's really just a random example.Now I wonder how parse_tree and ruby2ruby work that magic to_ruby call. Looks like I'll need to read their code. ;)
Sai Emrys
Your solution is pretty good, but fails for two things. 1) class methods; 2) built-ins (e.g. "foo".print_method :to_s # => UnsupportedNodeError). I wonder if that's fixable.
Sai Emrys
ok, edited my answer to support class-methods. also renamed the 'print_method' to better reflect what it's doing: returning the method_source.Unfortunately, it won't be possible to access the source for most built-in methods, because they are written in C.Now, could you please click the little check-mark next to my answer? :-)
joshng
Well earned. ;-)
Sai Emrys