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

Thursday, November 26, 2009

Ways to create symbol on the fly

To convet a string to a symbol on the fly:

# Method 1:
1
2
3
type = "elective"
course = "#{type}_course".to_sym
# >> :elective_course

# Method 2:
1
2
3
type = "core"
course = :"#{type}_course"
# >> :core_course

Wednesday, November 25, 2009

Customizing ur STI column name

To me, at least, :type is a very generic & commonly in use attribute name when doing modelling, and i don't see a good reason for activerecord to use it for STI (single table inheritance) support. To use my preferred naming strategy, i did the following inside RAILS_ROOT/config/initializers/active_record.rb
1
ActiveRecord::Base.set_inheritance_column :sti_type

Dynamically defining class method

There is this concept of metaclass in ruby. If we wanna dynamically define a class method, we need to play with metaclass:
1
2
3
4
5
6
7
8
9
10
11
12
class Human
def self.say_hello
puts %\class says 'hello'\
end
end

message = 'hello world'
meta_klass = (class << Human ; self ; end)
meta_klass.send(:define_method, :say_hello) { puts %\class says '#{message}'\ }

Human.say_hello
# >> class says 'hello world'

Dynamically defining instance method

To dynamically define an instance method in ruby:
1
2
3
4
5
6
7
8
9
10
11
class Human
def say_hello
puts %\instance says 'hello'\
end
end

message = 'hello world'
Human.send(:define_method, :say_hello) { puts %\instance says '#{message}'\ }

Human.new.say_hello
# >> instance says 'hello world'

ActiveRecord: Bitten by :something & :something_id

I must have came across this previously, but i've forgotten all abt it, and today, i got bitten hard as a punishment for being forgetful. Let's same i have the following models:
1
2
3
4
5
class Father < ActiveRecord::Base ; end

class Son < ActiveRecord::Base
belongs_to :father # and yup, we have a :father_id in sons table
end

During creating of sons, both the following strategies work:
1
2
3
father1 = Father.create(:name => 'Papa Jones')
son1 = Son.create(:father => father1) # strategy 1
son2 = Son.create(:father_id => father1.id) # strategy 2

But during update, only strategy 1 works:
1
2
3
father2 = Father.create(:name => 'Papa Tommy')
son1.update_attributes(:father => father2) # strategy 1
son2.update_attributes(:father_id => father2.id) # strategy 2, this DOESN'T work !!

DON'T FORGET !!!

Tuesday, November 3, 2009

Nested Step Definitions: Passing Table as Arg

From within a step definition, it is possible to pass a table to another step using the following technique:
1
2
3
4
5
6
7
8
Given /^I have just entered the results for "([^\"]*)"$/ do |identifier|
Given %\I follow "Enter Results" for "#{identifier}"\
And %\I enter the following class test results:\, table(%\
| # | Name | Score|
| 1 | John Tan | 99.0 |
| 2 | John Hoo | 80.0 |
\
)
end

Here's the step definition that does processing of the table (nothing unusual actually):
1
2
3
4
5
Given /^I enter the following class test results:$/ do |table|
table.hashes.each do |hash|
# blah blah
end
end

Sweet :]

Labels