Dynamically Building Ruby Code

If there is anything Ruby is good for, it’s for meta programming. Everything in the language is a class and, at some point, inherits from one root class, BasicObject. The most powerful class though, in my humble opinion, is the Object class. Every class in the Ruby language, with the exception of BasicObject, inherits from this class. Say lets say hi to Object!

object = Object.new

Unlike a language like JavaScript, Object is not like a slightly more powerful hash or some other data structure. It is a tool that helps you build Ruby and truly describes a language level component. So why is this so cool? Because it opens the door to some pretty fun meta programming.

That’s why.

Let’s start small and force an method call on an array.

arr = [ 'foo', 'bar' ]
arr.send :join, ' '

The Object class lets us manually call methods on Object instances. This may not make much sense to actually do on the surface, but what if we find ourselves in a situation where method calls may want to happen off of user input?

arr = ['foo', 'bar']
arr.send params[:method], params[:argument]

This could be useful in trying to abstract out some elements of very large APIs. Instead of setting routes for hundreds of API endpoints, we can funnel everything through a single command. We can even raise an exception if an object doesn’t have a particular method.

arr = ['foo', 'bar']
unless arr.method_missing? params[ :method ]
arr.send params[ :method ], params[ :argument ]
else
raise Exception.new "Please use a valid method for array"
end

But, your API will probably want to be more robust than just storing data in a static array. We will want to use different models to describe out app’s data. Well, Object can also give us the ability to return Class references.

clazz = Object.get_const params[ :clazz ]
unless clazz.method_missing? params[ :method ]
clazz.send params[ :method ], params[ :argument ]
else
raise Exception.new "Please use a valid method for #{ params[ :clazz ] }"
end

Now, let’s say our API needs to have an endpoint that returns a list of available methods and instance variables. We can do that too.

clazz = Object.get_const params[ :clazz  ]
{
methods: clazz.public_methods,
variables: clazz.instance_variables
}

Let’s also assume that we may want to call a specific method on the class level instead of instance level. Ruby’s Object class also allows this.

clazz = Object.get_const  params[ :clazz ].camelize.to_sym
clazz.define_singleton_method params[ :method_name ] do
params[ :method_body ]
end
clazz.send params[ :method_name ]

There isn’t much of a limit to how flexible the Object class really is. Most of the things discussed here are probably not the best way to tackle some of the problems discussed above, but they hopefully provide a decent starting point in making use of some of Ruby’s metaprogramming capabilities.