Last weekend,
zan showed me howto use parsetree & ruby2ruby to dynamically extract a method definition in plain ruby code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| %w{rubygems ruby2ruby parse_tree}.each {|lib| require lib }
class Hello
def self.hello puts "class says hello" end
def hello puts "instance says hello" end
end
extract_code = lambda do |klass, method| sexp = ParseTree.translate(klass, method) Ruby2Ruby.new.process(Unifier.new.process(sexp)) end
instance_method_code = extract_code[Hello, :hello] puts instance_method_code # >> def hello ; puts("instance says hello") ; end
|
To get the class method:
1 2 3 4
| metaclass = (class << Hello ; self ; end) class_method_code = extract_code[metaclass, :hello] puts class_method_code # >> def hello ; puts("class says hello") ; end
|
It is even possible to extract the entire class definitions:
1 2 3 4 5
| puts extract_code[World, nil] # >> class World < Object # >> def hello ; puts("instance says hello") ; end # >> def self.hello ; puts("class says hello") ; end # >> end
|
There are probably tonnes of code stuff you can do with the above capability. One immediate use case i can think of is appending new instance/class methods to another class:
1 2 3 4 5 6
| class World ; end World.class_eval instance_method_code World.instance_eval class_method_code
World.hello # yields: class says hello World.new.hello # yields: instance says hello
|
Another is that we can store the method definition somewhere (db, file, ...), redefine, distort, assault, murder, arson the existing class, and later just rollback the damages:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| cache = {'Hello.hello' => extract_code[Hello, :hello]} File.open('/tmp/original_methods', 'w') {|f| Marshal.dump(cache, f) }
class Hello def hello ; puts "instance is murdered" ; end end
puts Hello.new.hello # >> instance is murdered
# Do lots of other bad stuff, and to undo the damages:
cache = File.open('/tmp/original_methods', 'r') {|f| Marshal.load(f) } Hello.class_eval = cache['Hello.hello']
puts Hello.new.hello # >> instance says hello
|