Ruby Meta Programming cheat sheet
If you want a comprehensive tutorial, then perhaps consider getting Metaprogramming by Perotta. A huge proportion of the notes here are derived from Perotta.
Handy Methods:
- Object#instance_variables
- Object#methods (includes inherited methods)
some_object.methods.grep(/the_method_i_want_to_find/)
The Ruby Object Model
- Instance variables live in the object.
- There is a pointer to the object’s class. Instance methods live in that class.
- Constants start with a capital letter. You can have the same constant defined in different classes, no problems.
Module#constants
and also a “class method”Module.constants
exists.Module.nesting
shows you are currently at:=> [M::C, M]
Ruby Object Model
Method look ups
- You start with the class you are in, and keep looking up through your ancestors (super classes and included modules, until you get to the method you are looking for.)
- If a module is already included in the chain of ancestors, re-inclusions are silently ignored.
Method executions
Consder: obj.a_method
- now what happens when you get here? a_method
may have instance variables. If that’s the case, then those instance variables “belong to” the receiver - in this case, obj
.
- When you call a method, the receiver becomes
self
, and anything without an implicit receiver resolves toself
. And when you call a method with an explicit receiver, that object becomes self. - Private methods cannot be called with an explicit receiver - it can only be called on self.
- Within a class or module, the role of self is taken by the class or module itself (pun intended).
Refinements
Apply monkey patches only to certain classes. Watch out: it could catch you off guard: you may expect a monkey-patched method to be called, when in reality it calls a “normal” method.
Checkout the code here:Refinements Gotcha
Dynamically Dispatching Methods
- Object#send
- Kernel#send
o = Object.new
o.send(:to_s)
String.send(:to_s) # => String
1.send(:+, 1) # => 2
# The benefit of this is that you can pass in the method that you want called as a argument:
def dynamically_call_a_method(name)
o = Object.new
o.send name
end
dynamically_call_a_method(:to_s) # => "#<Object:0x00005614d7a24fa8>"
# You can pass in the method you want to be called as an argument.
# In the above case we passed in :to_s
Dynamically Defining Methods
-
[Kernel#define_method] - but this is a private method. You’ll have to do something like the following in order to define a method via this technique:
Kernel.send :define_method, :a_new_method do
shared
end
class A
## defines a_newly_defined_method and it accepts a block with a parameter
define_method :a_newly_defined_method do |argument|
# the block is the method body
# the block is evaluated using instance_eval
# this means that the block is exexuted in the context of an object.
# in this case, it will be in the context of an instance
# of a Class `A` object.
argument +1
end
end
a = A.new
puts a.a_newly_defined_method(1) # => 2
## Or you can use Kernel#send
class B
## defines a_newly_defined_method and it accepts a block with a parameter
Kernel.send :define_method, :a_newly_defined_method do |argument|
# the block is the method body
# the block is evaluated using instance_eval
# this means that the block is exexuted in the context of an object.
# in this case, it will be in the context of an instance
# of a Class `A` object.
argument +1
end
end
b = B.new
puts a.a_newly_defined_method(1) # => 2
Removing methods dynamically
Module#undef_method
- removes any method, inluding inherited methods.Module#remove_method
- removes just the method from the receiver.
Blocks and Closures
- When you created a block, you capture local bindings. i.e. local variables.
Scope Gates
There are a few ways in which scopes change. In:
- Class definitions
- Module definitions
- Methods
instance_eval vs instance_exec
- instance_exec allows you to pass parameters to it.
- Always ask what the
self
is. Instance variable operate in the context of self – whatever that may be.
Callable Objects
A block is not an object in ruby.
You can package code in ruby in:
-
A proc
-
A lambda
-
In a method
Procs
- They are blocks turned into an object.
p = Proc.new { |x| x + 1 }
p.call(1) # => 2
You can also create a proc using two Kernel methods: (i) Kernel#lambda
and (ii) Kernel#proc
p = lambda { |x| x + 1} ## Or p = ->(x) {x + 1}
p.call(1) # => 2
The & operator
See this example:
def add(a, b)
yeild(a,b)
end
def an_operation(a, b, &operation)
add(a, b, &operation)
end
The difference between procs and lambdas
It’s confusing. But the key differences centre around: (i) return and (ii) arguments.
A lambda returns from itself. But a proc returns from the scope from where it was defined. (See pg. 93 of Ruby Metaprogramming 2). You can avoid these problems by being careful of when you explicitly use returns.
The other difference concerns arity. A lambda is less tolerant of arity issues, while a proc is very generous.
-
A lambda operates in the scope in which it is defined in.
-
A method operates in the scopeo of its object.
Method Objects
class A
end
a = A.new
m = a.method :to_s
m.call # => #<A:0x000055618e750d10
Unbound methods
module AModule
def a_method
10
end
end
unbound_method = AModule.instance_method(:a_method)
# you can't call it until it's bound. If the method comes from a class, you can only bind to objects of the same class (or derivatives). You can bind module based methods to basically anything.
String.class_eval do
define_method :new_method, unbound_method
end
"random string".new_method # => 10
Class Definitions
class_eval
andmodule_eval
takes a block and assumes the contents of an existing class.class_eval
changes bothself
and the current class - subject to a caveat - instance_eval changes the current class, but via Singleton classes.class
requires a constant, whileclass_eval
can be used on any variable that refers to a class, and class_eval has a flat scope, while the class keyword demarcates a new scope.
Singleton Methods
- They are methods which are defined on an specific object. What does that mean? This is best illustrated with an example:
class Dog
end
# meet my dog rex
rex = Dog.new
# he's the only dog in the world who can do a handstand:
def rex.hand_stand
puts "
\\ /
|
/o\\
"
end
rex.hand_stand
# no other dog in the world can do this.
# We just defined a singleton method on rex. Rex is an instance of Dog.
# If we tried to get Beethoven to do this, then he would fail
beethoven = Dog.new
beethoven.handstand # => undefined method `handstand' for #<Dog:0x000055580fb84018> (NoMethodError)
# Then we get an error.
Here is another example:
class Car
end
c = Car.new
# We can define a singleton method like this:
def c.drive
puts "broom"
end
# Or like this:
class << c
def drive2
puts "broom 2"
end
end
c.drive
c.drive2
# But this will not work:
another_car = Car.new
another_car.drive # => undefined method `drive' for #<Car:0x0000562dd0de45f0> (NoMethodError)
Singleton methods defined on a class
It’s trite to say, but everything in ruby is an object. When you define a class, what you are really doing is getting an instance of a Class
which is tied to a constant. This means that Dog
is an instance of Class
, and it is tied to the constant
which is named: Dog
(remember “constants” have to start with a capital letter in ruby)
So when you write a class method like this:
class Dog
def self.woof
puts "Woof / Bow Wow / wuff / wan-wan"
end
end
What you are really doing is defining a singleton method on Dog
instance of Class
.
Where do singleton methods live?
- For this you will have to understand the ruby object model.
How can I get inside the scope of a singleton class:
Like this:
class << any_object_including_self
# We're in the scope of the singleton class noted above
end
And if you want to get the singleton class itself you can do this:
x = Object.new
# You can do this to get x's singleton class:
x.singleton_class.class # => Class
# Or if you're using an old version of ruby, try this:
x_singleton_class = class << x
# We're in the scope of the singleton class noted above
return self
end
# x_singleton_class is the singleton class of the instance x.
More about the singleton class
- It’s a class in and off itself.
- You can only have a single instance of it
- You cannot derive from it.
Singleton classes and how they relate to instance_eval vs class_eval
- instance_eval: this changes self, and the associated singleton class of the object.
- class_eval: this changes self, and the
current class
.
Method Wrapping: Aliasing vs Refining vs Monkey Patching
What if you wanted to add a method onto a string, for example. If you did something like this, then you would be adding that method to all strings.
class String
def make_upper
upcase
end
end
puts "works for this string".make_upper # => WORKS FOR THIS STRING
puts "and this one too!".make_upper # => AND THIS ONE TOO!
# that's a huge problem. How do you think you can avoid this situation?
(i) Aliasing
Aliasing is an option, but the result is the same in this particular case.
class String
alias_method :make_upper, :upcase
end
puts "works for this string".make_upper # => WORKS FOR THIS STRING
puts "and this one too!".make_upper # => AND THIS ONE TOO!
(ii) Refinements
What are the benefits of this? They allow you to monkey-patch, but to limit the reach of the monkey patching. It won’t work for all strings, just the strings you want it to work on.
module StringExtensions
# refinements only modify classes and not modules
# so we have to pass in the a class
refine String do
def make_upper
upcase
end
end
end
# this won't work because it is called before the `using` statment.
puts "abc".make_upper # => undefined method `make_upper' for "abc":String (NoMethodError)
using StringExtensions
# refinements allow you to use the extensions only where you need to.
puts "abc".make_upper # => "ABC"
Another way of handling the above is to prepend it:
Prepend Wrapper:
module StringExtensions
# refinements only modify classes and not modules
# so we have to pass in the a class
refine String do
def make_upper
upcase
end
end
end
String.class_eval do
prepend StringExtensions
end
Hooks
You can find out when a module is included/prepended (and it’s corresponding including/prepending class) with the following:
Module Related Events
- included
- prepended
module M1
def self.included(including_class)
puts "Was included by #{including_class}"
end
end
module M2
def self.prepended(including_class)
puts "Was prepended by #{including_class}"
end
end
class A
include M1 # => Was included by A
prepend M2 # => Was prepended by A
end
Method related Events:
- Module#method_added
- Module#method_removed
- Module#method_undefined
(These don’t work for when singleton methods are added. For that, you’ll have to use the corresponding singlteon method events. e.g. singleton_method_added, singleton_method_removed, singleton_method_undefined)
module M1
def self.method_added(method)
puts "Was added by #{method}"
end
def test # => Was added by test
end
end
Source
Source for all of this comes from the Ruby Metaprogramming Book by Perrotta. It’s not a bad book. Check it out if you are interested. (I am not receiving any commissions for writing/recommending that book.)