Metaprogramming in Ruby

Dynamic Dispatch

  • There is another way to call a method in Ruby - using the send method
  • First parameter is the method name/symbol; the rest (if any) are method arguments
1
2
3
4
5
6
7
8
9
10
11
12
13
class Dog 
def bark
puts "Woof, woof!"
end
def greet(greeting)
puts greeting
end
end

dog = Dog.new
dog.bark # => Woof, woof! dog.send("bark") # => Woof, woof! dog.send(:bark) # => Woof, woof! method_name = :bark
dog.send method_name # => Woof, woof!
dog.send(:greet, "hello") # => hello

Dynamic Method

  • Not only can you call methods dynamically (with send), you can also define methods dynamically
  • define_method :method_name and a block which
    contains the method definition
1
2
3
4
5
6
7
class Whatever
define_method :make_it_up do
puts "Whatever..."
end
end

whatever = Whatever.new whatever.make_it_up # => Whatever...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require_relative 'store' 

class ReportingSystem

def initialize
@store = Store.new
@store.methods.grep(/^get_(.*)_desc/) { ReportingSystem.define_report_methods_for $1 }
end

def self.define_report_methods_for (item)
define_method("get_#{item}_desc") { @store.send("get_#{item}_desc")} define_method("get_#{item}_price") { @store.send("get_#{item}_price")}
end
end
rs = ReportingSystem.new
puts "#{rs.get_piano_desc} costs #{rs.get_piano_price.to_s.ljust(6, '0')}" # => Excellent piano costs 120.00

Ghost Methods

  • method_missing gives you the power to “fake” the methods
  • Called “ghost methods” because the methods don’t really exist
1
2
3
4
5
6
7
8
9
10
11
12
13
class Mystery
# no_methods defined
def method_missing (method, *args)
puts "Looking for..."
puts "\"#{method}\" with params (#{args.join(',')}) ?" puts "Sorry... He is on vacation..."
yield "Ended up in method_missing" if block_given?
end
end

m = Mystery.new
m.solve_mystery("abc", 123123) do |answer|
puts "And the answer is: #{answer}"
end

Struct and OpenStruct

  • Struct: Generator of specific classes, each one of which is defined to hold a set of variables and their accessors (“Dynamic Methods”)
  • OpenStruct: Object (similar to Struct) whose attributes are created dynamically when first assigned (“Ghost methods”)
1
2
3
4
5
6
7
8
9
10
11
12
Customer = Struct.new(:name, :address) do # block is optional 
def to_s
"#{name} lives at #{address}"
end
end
jim = Customer.new("Jim", "-1000 Wall Street") puts jim # => Jim lives at -1000 Wall Street

require 'ostruct' # => need to require ostruct for OpenStruct
some_obj = OpenStruct.new(name: "Joe", age: 15)
some_obj.sure = "three"
some_obj.really = "yes, it is true" some_obj.not_only_strings = 10
puts "#{some_obj.name} #{some_obj.age} #{some_obj.really}" # => Joe 15 yes, it is true