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.

22 comments on this entry.

  • Simon Willison | 3 Aug 2006

    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.

  • ctran | 3 Aug 2006

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

  • bobo | 6 Aug 2006

    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”)

  • Tom | 6 Aug 2006

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

  • mexxx | 11 Aug 2006

    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?

  • Tom | 11 Aug 2006

    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.

  • Zargony | 6 Sep 2006

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

  • Gert | 2 Nov 2006

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

  • Daniel | 6 Nov 2006

    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

  • Joey | 1 Mar 2007

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

  • Nate Klaiber | 26 Sep 2007

    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.

  • Nate Klaiber | 26 Sep 2007

    Oops.

    def to_class

    end

  • Mirage | 9 Jun 2008

    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.

  • Chris Hoeppner | 1 Dec 2008

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

  • xprdr | 3 Dec 2008

    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

  • Michael A | 15 Dec 2008

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

  • btower | 3 Jan 2009

    What makes the non-eval version more maintainable?

  • petef | 17 Nov 2009

    And of course FWIW you can go the other way to define classes from Strings:


    # Declare all the classes up front to avoid forward references
    %w(Tom Dick Harry George).each do |c|
    Tunnel.const_set(c, Class.new)
    end

  • Frank | 19 Dec 2009

    Thanks petef. Just needed that answer!

  • Jaydeep Dave | 8 Feb 2010

    This is the answer needed maaan……. you have no idea, what you have helped me to build.

  • DSimon | 11 Jun 2010

    This is lovely! It’s especially nice because it is much more secure than eval when handling user input. I don’t have to worry about some sneaky user trying to get me to load a type named ” exec ‘rm -rf /’ “.

    Of course, I still have to make sure that the class name specified isn’t something unexpected which would have disastrous side effects, but that’s way easier than trying to somehow sanitize a call to exec; all I have to do is make sure the input class includes the base type of the classes I expect to deserialize in its ancestors.

  • Josh Carter | 26 Jul 2010

    @btower you really don’t want to be eval’ing strings that could come from an untrusted source, lest someone get a class name into your system like “system(‘rm -rf /’)”. ;)

    Thanks for the tips here. I had figured out the issue with creating classes under nested modules, but failed to realize that I could condense my several lines of code using inject (thanks @Mirage). FWIW more recent versions of Ruby have an alias “reduce” that I think is closer to the intent of the method than “inject.”

  • 16 Nov 2006

    Trackback: Generic actions for Rails subclasses