Sunday, November 29, 2009

Fetching plain ruby code on the fly

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

No comments:

Post a Comment

Labels