Ruby Metaprogramming Part 2

We continue with more reflection methods in Ruby like instance_variables, local_variables, constants for variables and public_methods, private_methods, method_definded? for methods.

Note: This is part 2 of metaprogramming in Ruby, in part 1 we covered evaluating strings and blocks with instance_eval and such.

Reflection Methods for Variables and Constants

Here are some methods for listing the names of all instance variables on an object, all class variables of a class or module and all constants.

Given this class

class Color

  def initialize(name)
    @name = name
  end

  @@sky = "blue"
  TREE = "green"
end

we can see class_variables, constants, and instance_variables in action in irb

> Color.class_variables
=> [:@@sky]

> Color.constants
=> [:TREE]

> Color.new("purple").instance_variables
=> [:@name]

In addition to listing these, we can also query and set the value of class and instance variables and constants and check whether one is defined.

o = Object.new
o.instance_variable_set(:@greeting, "hello")

> o.instance_variable_get(:@greeting)
=> "hello"

> o.instance_variable_defined?(:@greeting)
=> true

Reflection for Methods

Here we look at methods for listing, querying, invoking, and defining methods. Yes very meta indeed.

Listing methods

Let's see some methods for listing methods of a String

> s = "hello from codecurious.dev"
=> "hello from codecurious.dev"

> s.methods
=> [:unicode_normalize!,
 :pretty_print,
 :encode!,
 :to_c,
 :unpack,
 :include?,
 :next!,
 :upto,
 :match?,
 :rindex,
 :replace,
 :empty?,
 :eql?,
 :getbyte,
 :setbyte,
 :clear,
 :chr,
 :scrub!,
 :dump,
 :byteslice,
 :scrub,
 :upcase,
 :downcase,
 ...
  ]

I made the mistake of pasting the entire output here and that took over my entire editor. String has a lot of methods. The above is small sample.

public_methods is the same as methods. There is also private_methods and signleton_methods. We can also check if a method is defined.

> String.method_defined? :upcase!
=> true

Invoking methods

We can obtain Method objects by calling method an object like "hello".method(:reverse) returns a Method object bounded to the string "hello". Method objects have a call method that we can use to invoke the method.

Another way to invoke methods is with send method of Object. We can invoke instance method as well as class methods with send. The argument is the method name and the rest are passed on as parameters.

> "hello".send :upcase
=> "HELLO"

> Math.send(:sin, Math::PI/2)
=> 1.0

send can invoke any named methods of an object, including private methods. Ruby 1.9 added public_send which only invokes public methods. __send__ is a synonym for send.

Defining methods

We can use define_method to define new instance method of a class or a module. define_method takes the name of the method as the first argument and method body as either a Method object or a block. define_method is private and we need to be inside the class in order to call it.

def add_method(c, m, &b)
  c.class_eval {
    define_method(m, &b)
  }
end

add_method(String, :greet) {"Hello, " + self}

"world".greet # prints "Hello, world"

attr_reader and attr_accessor are methods that define new methods for a class. Like define_method these are private methods of Module and are meant to be used inside class definition.

We can use alias_method to create a synonym name to a method. alias_method is private and needs to be used inside a method.

Lastly, method_missing of Kernel module can be implemented by a given class to allow the class to handle invocation of methods that do not exist.

method_missing is a one of the most powerful and commonly used dynamic programming techniques in Ruby.

Show Comments