02
August
2006

code

Tagged as:
, , , .

Getting a class object in Ruby from a string containing that class’ name

Matt posted a really elegant piece of code today that generates Graphviz files (suitable for importing into OmniGraffle) of your Rails ActiveRecord relationships. It’s pretty neat, and certainly handy for getting to know foreign codebases.

There’s one neat trick in there, though, that I wanted to expand on, as Matt breifly chatted to me about the problem earlier today over IM - namely, how you get the actual class object so that you can call reflect_on_all_associations on it.

In Ruby, it’s easy to dynamically call methods - you can put the name of the method into a string, and then simply run Object.send(methodname). Getting the actual Class Object for a particular object - that’s much trickier.

There’s the obvious solution of using eval. So, to get all the methods on your classname:

classname = 'Integer'
eval classname + '.methods'

but that, of course, is pretty nasty and kludgy. This is Ruby, after all; there’s got to be a better solution, right?

There is. If you look in the Pickaxe, you’ll find that Class Names Are Constants:

All the built-in classes, along with the classes you define, have a corresponding global constant with the same name as the class.

So this means that by passing the class name to the const_get method on the Kernel module, we’ll confirm if a class exists with that name (eg Integer). Then, because that constant is really a reference to an object of the same name, by sending a message (the method calal) to the constant, it will be passed on to the object and run (which is the best way I’ve got of explaining this). Job done! To use the previous example:

classname = 'Integer'
Kernel.const_get(classname).methods

Which is, you must admit, a bit more elegant and maintainable than the evil that is eval.

19 comments on this entry to date. Add your own.

  • 16 November 2006
  • 26 April 2009
  • 3 August 2006
    Simon Willison said... 1

    How does that work with classes with the same name as other classes, but in different modules? I never really figured out classes and modules when I was playing with Ruby so this is probably a dumb question.

  • 3 August 2006
    ctran said... 2

    There’s a shortcut in Rails for this, ‘Integer’.constantize.methods will do the same.

  • 6 August 2006
    bobo said... 3

    Re: classes with same name? In a way you’ve stumbled on the very purpose of ruby modules, as I understand it: it’s a mechanism for disambiguating classes, for avoiding class name clashes. A module provides a http://en.wikipedia.org/wiki/Namespace

    Anyway, to answer your question:

    ModuleName.const_get(”MyClass”)

  • 6 August 2006
    Tom said... 4

    Simon and I have now discussed this over IM. As Bobo points out, we discussed namespacing, and how classes not inside a module end up as part of Kernel. (And, of course, that Kernel is a module that Class includes).

  • 11 August 2006
    mexxx said... 5

    Very nice. I have another question in a way related to this.

    Let’s say that I have a class BigTree and I have a string ‘big_tree’ how do you get the string translated to ‘BigTree’ so that I can use this translated string the way you showed?

  • 11 August 2006
    Tom said... 6

    No problems, mexxx. Have a look at this little blog post for some tips.

    Once you’ve done that, you can then use what I describe in this post.

  • 6 September 2006
    Zargony said... 7

    mexxx, in Rails there’s the camelcase method:
    “big_tree”.camelcase #=> ‘BigTree’

  • 2 November 2006
    Gert said... 8

    About constantize as alternative, doesn’t that use eval, which was precisely what this post wanted to avoid?
    http://dev.rubyonrails.org/ticket/2856

  • 6 November 2006
    Daniel said... 9

    How would you do this if you don’t know your module and class in advance. For example I want to get an instance of ‘MyModule::MyClass’ it works with eval but neighter with constantize nor with Kernel. Thanks, Daniel

  • 1 March 2007
    Joey said... 10

    In (late) response to Daniel’s question:
    Object.const_get(’MyModule’).const_get(’MyClass’)
    => MyModule::MyClass

  • 26 September 2007
    Nate Klaiber said... 11

    I recently had the need to do the same thing as discussed here. Since I used it in several places I just extended String to have a ‘to_class’ method that simply said:

    def.to_class
    Kernel.const_get(self)
    end

    So I could use something like “Product”.to_class which would then allow me to use all methods on Product.

    This could even be extended more by using ‘classify’ or the like before you return the constant.

  • 26 September 2007
    Nate Klaiber said... 12

    Oops.

    def to_class

    end

  • 9 June 2008
    Mirage said... 13

    A nice one-liner to handle the case with classes nested in an arbitrary number of modules is the following:

    class_name = “MyModule::MyClass”
    class_name.split(’::’).inject(Kernel) {|scope, const_name| scope.const_get(const_name)}

    Which as Nate pointed out could be put in a String.to_class method, or Kernel.get_class.

  • 1 December 2008
    Chris Hoeppner said... 14

    Time to dig into #inject. It’s a method I have not yet understood.

  • 3 December 2008
    xprdr said... 15

    inject is not used for the purpose you are trying to accomplish here. plus there are at least two types of injects:
    http://www.java2s.com/Code/Ruby/Range/Injectarange.htm

    and

    http://www.java2s.com/Code/Ruby/Hash/injectwithregularexpression.htm

  • 15 December 2008
    Michael A said... 16

    Well done Mirage. I’ve never used inject for any real work but it makes short work of this problem.

  • 3 January 2009
    btower said... 17

    What makes the non-eval version more maintainable?

Post a comment:

Please note that your comment may be moderated, and as such might not appear immediately.

My links and notes for this day

Endnotes