2. Metaprogramming
• Writing code that manipulates language constructs at runtime
• In Ruby no distinction:
• Same as “regular” programming
• Enables:
• Code that writes code
• DSLs
• Introspection (e.g., reflection)
2
3. Introspection
• Allows to get information about objects at runtime
• Methods
• Instance variables
• etc.
class MyClass
! def initialize()
! ! @var1, @var2 = 0, 2
! end
! def my_method
! end
end
m = MyClass.new
m.class
=> MyClass
m.methods
=> # lot of methods ...
m.instance_variables
=> [:@var1, :@var2]
m.public_methods
=> # ...
m.private_methods
=> # ...
m.instance_of? MyClass
=> true
m.instance_of? Object
=> false
m.is_a? MyClass
=> true
m.is_a? Object
=> true
3
4. Open Classes
• Classes can be “opened” for change
• What about the open/closed principle?
class MyClass
! def a; "method a"; end
end
m = MyClass.new
m.methods - Object.new.methods
=> [:a]
class MyClass
! def b; "method b"; end
end
m.methods - Object.new.methods
=> [:a, :b]
class Numeric
! KILOBYTE = 1024
! def kilobytes
! ! self * KILOBYTE
! end
end
puts 2.kilobytes
=> 2048
4
5. Monkeypatching
• Refers to the general idea of modifying the runtime code
without modifying the original source code
• Problems:
• Redefining existing methods
• Change methods used in other pieces of the code
• Ruby 2.0 introduced “scoped” monkeypatching
5
6. Objects and classes
• Objects contains instance variables
• Remember: instance variables exists only when assigned
• Objects contains:
• Instance variables
• Reference to its class
• object_id
class MyClass
! def my_method
! ! @v = 1
! end
end
obj = MyClass.new
obj.my_method
obj1
@v = 1
MyClass
my_method()
object
instance variables
class
methods
class
6
7. Classes are objects too
• class
• superclass
• All objects inherit from BasicObject
Class.superclass # => Module
Module.superclass # => Object
String.superclass # => Object
Object.superclass # => BasicObject
BasicObject.superclass # => nil
7
9. Invoking methods
• Calling a method involves two steps:
1. Method lookup
• Identify receiver class
• Escalate ancestor chain until the method is found
2. Method execution
• The actual code is executed
class A; end
class B < A; end
B.ancestors
=> [B, A, Object, Kernel, BasicObject]
The ancestor chain
includes also modules
9
10. Ancestor chains
module M
! def my_method
! ! 'M#my_method'
! end
end
class C
! include M
end
class D < C; end
D
C
M
Object
Kernel
BasicObject
10
11. self
• Every line of Ruby is executed within an object
• Called current object: self
• Only one object holds the self at any given time
• Instance variables and methods (without explicit
receiver) are called on self
• In class or module definition the role of self is
taken by the class or module
class MyClass
! self! # => MyClass
end
11
12. Calling methods
dynamically
• Remember “sending messages to objects” ?
• Method send sends messages to objects
• Can be used to dynamically call methods
class MyClass
! def my_method(my_arg)
! ! my_arg * 2
! end
end
obj = MyClass.new
obj.send(:my_method, 3)! # => 6
12
13. Defining methods
dynamically
• Use the Module#define_method method
• Provide a block for the method body
class MyClass
! ["steve", "jeff", "larry"].each{|d|
! ! ! define_method d.to_sym do
! ! ! ! puts d
! ! ! end
! ! }
end
obj = MyClass.new
obj.steve! # => "steve"
obj.jeff! # => "jeff"
obj.larry! # => "larry"
13
14. method_missing
• What happens if no method is found in the ancestor
hierarchy?
• A method called method_missing is called
• A common idiom is to override this method in order
to intercept unknown messages
• Define ghost methods
• Methods that do not actually exists!
14
15. An example
class Mapper
! def initialize()
! ! @map = {}
! end
! def add(key, value)
! ! @map[key.downcase] = value
! end
! def method_missing(method_name, *args)
! ! key = method_name.to_s.downcase
! ! return @map[key] if @map.key? key
! end
end
m = Mapper.new
m.add("Rome","IT")
m.add("London","UK")
puts m.rome
puts m.london
15
16. instance_eval
• Allows to evaluate a piece of code within the
scope of an object
• That is: changes the self for a piece of code
class MyClass
! def initialize
! ! @v = 1
! end
end
obj = MyClass.new
obj.instance_eval do
! self!# => #<MyClass:0x83fd33 @v=1>
! @v! ! # => 1
end
v = 2
obj.instance_eval { @v = v}
obj.instance_eval { @v }! # => 2
BREAKS ENCAPSULATION!
Read/write private data
With great power comes
great responsibility!
16
17. class_eval
• Evaluates a block in the context of an existing class
• Changes the self (i.e., it reopens the class)
def add_method_to_(a_class)
! a_class.class_eval do
! ! def m
! ! ! "Hello!"
! ! end
! end!!
end
add_method_to String
"abc".m ! # => "Hello!"
More flexible than reopening
it with the class keyword
(i.e., parametric)
17
18. Singleton methods
• In Ruby it is possible to add a method to a single
instance of an object
str1 = "This is a string!"
str2 = "Another str"
def str1.title?
! self.upcase == self
end
str1.title?!# => false
str2.title?
=> NoMethodError: undefined method `title?' for "Another str":String
18
19. Singleton methods - 2
• Are stored in special classes called eigenclasses
• Invoking Object#class does not show eigenclasses
• Special syntax to enter in their scope
class << str1
! # Eigenclass scope
! def title?
! ! upcase == self
! end
end
#str1
title?
String
Object
str1
superclass
superclass
Eigenclass
Method lookup
revisited
19
20. Method aliases
• Introduce new names for methods
class MyClass
! def my_method; 'my_method()'; end
! alias :m :my_method
end
obj = MyClass.new
obj.my_method! # => "my_method()"
obj.m ! ! ! # => "my_method()"
class String
! alias :real_length :length
! def length
! ! real_length > 5 ? 'long' : 'short'
! end
end
We can invoke the method
with two names
Redefine but still use the old one
(this is also called around alias)
20
21. Kernel#eval
• Instead of taking a block, it takes a string containing the
code
• Code is executed and the result of expression is
returned
a = 1
b = 2
c = eval("a + b")
puts c
=> 3
Problems
• Code injection
• Readability
• etc.
21
22. Hook methods
• Various aspects of classes and method definition can
be caught at runtime
• Similar to events
• For example method_missing
class String
! def self.inherited(subclass)
! ! puts "#{self} was inherited by
#{subclass}"
! end
end
class MyString < String; end
=> "String was inherited by MyString"
module M
! def self.included(other_mod)
! ! "M was mixed into #{other_mod}"
! end
end
class C
! include M
end
=> "M was mixed into C"
22