March 9, 2015 by Daniel P. Clark

Ruby: Actionable Meta

The more you program the more likely you will need to change a string to the object the string represents. For example you may have a string with a mystery class name in it the you need to call methods on. Lets go a step further, lets say the method you need to call on it is also a string. Well the first thing most will learn when looking into doing this is normally eval. eval turns your strings into ruby code and evaluates it right in place.

obj = "Array"
mth = "new"

instance = eval("#{obj}.#{mth}")
instance << 1
# => [1]

So this is fine for now. It works. Lets look at an eval inside a method where the first object is not a string.

hsh = Hash.new
mthd = "to_a"

def do_the_meta(h, m)
  eval("h.#{m}")
end
i = do_the_meta( hsh, mthd )
i << 1
# => [1]

Notice in my eval I didn’t interpolate h with #{}. The reason is because of scope; the method h is already the Object we want to run a command on. But the m variable was still a String that we wanted to run as a command.

Now something that I was unaware of for a long time was that you can attach methods right on the end of eval() as if you were writing the method right on the Object that eval produces.

eval("[]").<<( 1 )
# => [1]
eval("Hash").new
# => {}

This will get you a long way. But you may have heard that it’s dangerous to use eval when you don’t know what might be eval‘d. So how else might we achieve this same flexibility of meta execution?

Now enters the send command.  The send command will allow you to send either a Symbol or a String as a command to execute on the object you call send on. That; and you can still chain commands onto the resulting Object just like normal.

xarr = Array
xarr.send("new").<<( 3 )
# => [3]

You might say: “Well that’s cool. But what about the case where the object you want is a string?” Yes if we’re avoiding eval we’ll need to use another option; as send will be invoking a method on the String instance and not the Object it is meant to represent. And for that, Ruby has an answer: Kernel.get_const

my_var = "Array"

Kernel.get_const(my_var).new.<<( 5 )
# => [5]

my_arr = lambda {|o,s| Kernel.get_const(o).send s }
my_arr.call( my_var, :new ).<<( 7 )
# => [7]

In both of these examples it was the string “Array” that was turned into the constant (class Array) from where we were then able to invoke our methods on.

In Rails there is a method available on String objects that will do this for you: constantize

arr = "Array"
arr.class
# => String
arr.constantize.class
# => Array
arr.constantize.new.<<( 9 )
# => [9]
arr.constantize.send("new").<<( 9 )
# => [9]

This make for a much cleaner experience for all your meta-programming needs.

Summary

All the tools you need are right at your finger tips. Practice, experiment, and brain storm. There are countless ways you can take advantage of this. I’ve come across a couple of instances where the use of lambda came in handy for connecting and evaluating Rails models that belonged to one another. So keep these handy ^_^

Hope you enjoyed this! Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

Image by Jason Sweeney via the Creative Commons Attribution-NonCommercial-ShareAlike 2.0 Generic License.

#constantize#eval#get_const#Kernel#lambda#meta#meta-programming#rails#ruby

Comments

  1. nil2
    March 10, 2015 - 8:05 am

    I don’t understand the point of wrapping those things in lambdas?

    • Daniel P. Clark
      March 10, 2015 - 8:27 am

      It’s not that you necessarily need it. You may or may not at times. The point is that this is an option you can use. Lambdas give you the benefit of delaying evaluating code and context. When all else fails, try a lambda. Or you could use a lambdas in the first place; just as you would a method to re-use the same behavior when needed.

      You can define a lambda within a method and it has access to all local variables within that scope. You don’t write methods within methods; you call them from there; so for methods you would have to pass in all the local variables you wanted to evaluate. So the lambda will have the advantage of knowing its surrounding context.

      • nil2
        March 10, 2015 - 8:32 am

        Is the context of your example code a case where lambda is useful? I don’t understand the point of demonstrating it here, it does not seem to add any value in your examples. Sure, I understand that lambdas are useful in ruby, I like them a lot, a great ruby feature — I don’t understand the point of them in this case.

  2. Daniel P. Clark
    March 21, 2015 - 10:36 pm

    If you’d like to see a great example of how removing eval in a Rails application looks then check out this commit: Removed evals in exchange for constantize and send.

Leave a Reply

Your email address will not be published / Required fields are marked *