How to create a proxy class in ruby

Creating a proxy class in ruby can be a nice tool to have in your nerdy tool belt, and its actually really simple to do. Have you ever wondered how ActiveRecord is able to pull off its nifty tricks with associations? Ex:

user = User.first
user.orders.build
# => #Order object

user.orders.first
# => #Order ojbect

user.orders.class
# => Array

Wait, ‘build’ isn’t a method available for arrays. How did they do that?

One way is to add a ‘build’ method into the Array class, but this is really intrusive. You don’t necessarily need a build method for every Array object, more importantly build needs to know what kind of object to build. How does it get this information?

The answer to the mystery is a proxy class that delegates method calls to the underlying array. It’s actually really simple to do. Check it out:

class Proxy
    instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }

    def my_awesome_method
       "you just called an awesome method!"
    end

    protected
        def method_missing(name, *args, &block)
          target.send(name, *args, &block)
        end

        def target
          @target ||= []
        end
end

Notice that the first thing we do is undefine just about every method in the class. This makes sure that calls to those methods hit method_missing and ultimately get called on the underlying array. Afterwards we define any special methods, such as ‘my_awesome_method’, and then we handle any calls to undefined methods. That’s it! It’s actually really simple and clever. Here’s how it works:

proxy = Proxy.new
proxy.my_awesome_method
# => "you just called an awesome method!"

proxy.class
# => Array

Why would you want to do this?

Well sometimes you want to add a little extra functionality to a basic object, such as an Array, Hash, etc. This allows you to do that without having to modify the underlying class, which is good. You basically get to play middle man and intercept any calls to that object. Then you have the decision to pass them through or do your own thing. This also promotes much more “ruby like” syntax. Without this, calling associations in AR would look something like:

user.orders.build
user.orders.create
user.orders.all.first
user.orders.all.size

Anything related to the underlying Array must be called off of the “all” method. That just seems dirty. This is so much cleaner:

user.orders.build
user.orders.create
user.orders.first
user.orders.size

Pretty cool huh?

  • Share/Save/Bookmark


13 Responses to “How to create a proxy class in ruby”

  1. Max says:

    So I think I get the point, but…I think your example is too simplistic. What advantage does that proxy class have over subclassing Array, for instance?

  2. benjohnson says:

    Hi Max, I probably should have taken it a little further. Anyways, this method is great for playing middle man. Which is great when you want to execute a performance taxing task only when absolutely needed. Such as executing a query or sending a request off to a web service. Those things will not happen until I need to use the ‘target’. Lastly, this ensures that you don’t muddy up a shared class like Array, where the build method really doesn’t belong.

  3. [...] dan Trik : Mari Belajar Proxy Design Pattern dengan Ruby! 8 08 2009 Ada link bagus untuk siapa saja Rubyist yang sedang berusaha belajar Proxy Pattern. Tulisan tersebut ditulis oleh yang membuat authlogic, satu rubygem yang buat saya pribadi telah [...]

  4. And to take things further, one could use nifty tricks like define_method in the method_missing method (speeds up things when having lots of method calls on the proxy).

  5. Mina Naguib says:

    There’s a good chance you want the “target” method in your example to return the same array over and over, instead of returning a new one which is what it currently does:

    def target
    @array ||= []
    end

    otherwise:

    p = Proxy.new
    p < []

  6. benjohnson says:

    Thanks mina, you are correct. I fixed this.

  7. Did you know you can also add your own methods to that ActiveRecord Array? I did a blog post on this quite a while back (http://blog.antarestrader.com/?p=17) but you can find the documentation in the “Association extensions” in the ActiveRecord Documentation (http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html)

    For whatever it’s worth I find it easier to `include` modules into the returned instance then the try to move everything through method missing.

  8. David Mathers says:

    Is this pretty much the same thing as DelegateClass or am I missing something?

    http://www.ruby-doc.org/stdlib/libdoc/delegate/rdoc/index.html

  9. ehsanul says:

    What a coincidence, I just created a Sinatra adapter for your Authlogic library, and had to create something very similar to a proxy class (it’s a proxy object rather). And then I check your blog and see this post.

    The reason I needed a proxy object was that Authlogic seemed to expect a something like a hash of cookies to read and write to, while Rack handled writing cookies with set_cookie. Authlogic also calls delete, rather than Rack’s delete_cookie. So I guess you can add “interface interoperability” to your list of reasons you might want to use a proxy.

    Here’s the way I had done it:

    def cookies
    unless defined? @cookies
    class << @cookies = Object.new
    def []= ( key, value )
    Authlogic::Session::Base.controller.response.set_cookie key, value
    end
    def method_missing( meth, *args, &block )
    Authlogic::Session::Base.controller.request.cookies.send meth, *args, &block
    end
    def delete( key, value = {} )
    Authlogic::Session::Base.controller.response.delete_cookie key, value
    end
    end
    end
    @cookies
    end

  10. ehsanul says:

    BTW, it might be better if instead of a target method, the variable storing the target is set during initialization:

    def initialize(*args)
    @target = Array.new *args
    end

    This way Proxy.new works properly, emulating the Array class.

  11. ehsanul says:

    Sorry for spamming your comments, but I just figured out a simpler/cleaner way to do a proxy class, based on the proxy object code in the first comment by me. It does the same thing, as far as I can tell:

    class Proxy
    def self.new( *args )
    class << array = Array.new(*args)
    def sweet
    "what a sweet method"
    end
    end
    array
    end
    end

  12. benjohnson says:

    No problem, thats really interesting. That is a really clever way to do that. I’m curious what kind of performance implications your method has. It might better because you are dealing with the object directly. Then again initialization might be much slower. Anyways, thanks for posts.

  13. apeiros says:

    Thanks for your thorough writeup.

    However, some suggestions to improve your code:
    1.
    instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
    –> there’s __send__ and __id__, so you can drop the or’s in this expression and make it just /^__.*__$/

    2.
    target.send(name, *args, &block)
    –> this is not recommended, you should use __send__ which is per convention never overridden

    3.
    @target ||= []
    –> as others already touched, late initialization of instance variables is IMO a bad thing, that’s what initialize is for. Also that gives you the chance to pass in the object you’d like to proxy to as an argument.
    Additionally you can speed up the `target.__send__` piece of code by using `@target.__send__` instead.

    Also one question:
    why did you make the method_missing protected and not private?
    Why protected at all? Object#method_missing is not protected/private.

    regards
    apeiros