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 to self. 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

  • Module#define_method

  • [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:

  1. Class definitions
  2. Module definitions
  3. 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 and module_eval takes a block and assumes the contents of an existing class.
  • class_eval changes both self and the current class - subject to a caveat - instance_eval changes the current class, but via Singleton classes.
  • class requires a constant, while class_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:

  • 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
  • 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.)

Written on March 30, 2019