August 07, 2009

Posted by John

Tagged patterns and proxy

Older: Getting Started With MongoMapper and Rails

Newer: MongoMapper Indy.rb Presentation

Patterns Are Not Scary: Method Missing Proxy

Method missing proxy? Ooooh! Sounds scary, right? I got news for you Walter Cronkite, it’s not. Lets start with the definition of proxy, according to Wikipedia.

Definition

A proxy, in its most general form, is a class functioning as an interface to something else.

An interface to something else. That sounds easy enough. You might be thinking that you have never used a proxy, but if you are reading this blog, you are wrong. Chances are you have used Rails, and if you have used Rails, chances are you have used has_many or some other ActiveRecord association, all of which are implemented using proxies under the hood.

Creating Your Own

Now that we have definition out of the way and have confirmed your use of proxies, let’s make one! Yay! The people rejoice! The basic idea of a proxy is a class that is an interface to something else. Lets call something else subject from now on. In order to get started, we’ll make a new proxy that has a subject.

class Proxy
  def initialize(subject)
    @subject = subject
  end
end

proxied_array = Proxy.new([1,2,3])
puts proxied_array.size
# NoMethodError: undefined method ‘size’

FAIL! Our proxy has a subject (the array), but does not proxy anything yet. In order to proxy up the girl, lets throw in some method missing magic.

class Proxy
  def initialize(subject)
    @subject = subject
  end
  
  private
    def method_missing(method, *args, &block)
      @subject.send(method, *args, &block)
    end
end

proxied_array = Proxy.new([1,2,3])
puts proxied_array.size # 3

Method missing takes 3 arguments: the method called, the arguments passed to the method and a block if one is given. With just that tiny method missing addition, we can now do fun things like this:

proxied_array = Proxy.new([1,2,3])
puts proxied_array.size # 3
puts proxied_array[0] # 1
puts proxied_array[1] # 2
puts proxied_array[2] # 3
puts proxied_array.select { |a| a > 1 }.inspect # [2, 3]
proxied_array << 4
puts proxied_array.size # 4
puts proxied_array[3] # 4

Just like that our proxied array behaves just like the original array. Well, almost like the original array.

puts proxied_array.class # Proxy

BlankSlate and BasicObject

Hmm, that is not quite what you would expect. We told the proxy to send everything to the subject, so it should output Array, not Proxy as the class, right? The problem is that any new class automatically has some methods included with it. In order for our Proxy class to be a true proxy, we need to remove those methods as well. In the Ruby 1.8 series, this is often done by defining a BlankSlate object which removes those methods and then have our Proxy inherit from BlankSlate.

class BlankSlate #:nodoc:
  instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval|object_id/ }
end

class Proxy < BlankSlate
  def initialize(subject)
    @subject = subject
  end
  
  private
    def method_missing(method, *args, &block)
      @subject.send(method, *args, &block)
    end
end

proxied_array = Proxy.new([1,2,3])
puts proxied_array.class # Array

Yay! Now we in fact get Array as one would expect. The great news is that Ruby 1.9 comes with a class like this already named BasicObject. The easy way to make this work with Ruby 1.8 and Ruby 1.9 is to just define BasicObject if it does not exist and then inherit from BasicObject, instead of dealing with BlankSlate.

class BasicObject #:nodoc:
  instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval/ }
end unless defined?(BasicObject)

class Proxy < BasicObject
  def initialize(subject)
    @subject = subject
  end
  
  private
    def method_missing(method, *args, &block)
      @subject.send(method, *args, &block)
    end
end

proxied_array = Proxy.new([1,2,3])
puts proxied_array.class # Array

Just like that our proxy is a full fledged proxy and it works with Ruby 1.8 and 1.9.

Example: MongoMapper Pagination

So other than ActiveRecord where else can you check out some proxies in the wild? In MongoMapper, pagination uses a method missing proxy. When someone uses paginate, instead of find, I wanted the result that was returned to also function much like WillPaginate::Collection does, but I didn’t want to inherit from Array.

You can view the pagination proxy on github. The paginate method that uses it looks like this:

def paginate(options)        
  per_page      = options.delete(:per_page)
  page          = options.delete(:page)
  total_entries = count(options[:conditions] || {})
  
  collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
  
  options[:limit] = collection.limit
  options[:offset]  = collection.offset
  
  collection.subject = find_every(options)
  collection
end

Just like that, paginate returns results just like find, but also includes methods for total_pages, previous and next pages, total_entries and the like.

Example: HTTParty Response

In HTTParty, at first I just returned a ruby hash that was the parsed xml or json. Then, people started begging for response codes and headers, so I went with a Response proxy that looks like this:

module HTTParty
  class Response < BlankSlate #:nodoc:
    attr_accessor :body, :code, :message, :headers
    attr_reader :delegate

    def initialize(delegate, body, code, message, headers={})
      @delegate = delegate
      @body = body
      @code = code.to_i
      @message = message
      @headers = headers
    end

    def method_missing(name, *args, &block)
      @delegate.send(name, *args, &block)
    end
  end
end

Now I just pass the parsed response, the codes, headers, and such to Response.new and the people who want that information get it and those who don’t have no API change to wrestle with.

Conclusion

Hope this little primer on the Proxy pattern, specifically using Ruby’s method missing, is helpful. I also hope that because of this you check out some of the other great patterns that are out there. I know I avoided them for far too long. When applied correctly, they really lead to elegant solutions.

9 Comments

  1. Josh Clayton Josh Clayton

    Aug 06, 2009

    Good article John. One pattern I’ve been a proponent of recently is the decorator used for presenters; method missing is an integral part of this, and BasicObject looks to be a perfect fit in my implementation. Thanks!

  2. Jeremy Friesen Jeremy Friesen

    Aug 07, 2009

    Why not use the Delegate class?

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

    When asked what the proxy’s class is, it should say ProxyArray not Array. If the proxy doesn’t give some clue as to what it truly is, you will add confusion later down the line when you have received this proxy in another method and ask what it is, then try to troubleshoot from there.

  3. @Josh – No problem.

    @Jeremy – I forgot about SimpleDelegator. Looks like other than the output of class, it is pretty similar to method missing proxy. Good reminder. Not sure if there are any other differences, but I’ll start playing with it.

    require 'delegate'
    class DelegateProxy &lt; SimpleDelegator; end
    
    delegated_proxy = DelegateProxy.new([1,2,3])
    puts delegated_proxy.size # 3
    puts delegated_proxy[0] # 1
    puts delegated_proxy[1] # 2
    puts delegated_proxy[2] # 3
    puts delegated_proxy.select { |a| a &gt; 1 }.inspect # [2, 3]
    delegated_proxy &lt;&lt; 4
    puts delegated_proxy.size # 4
    puts delegated_proxy[3] # 4
    puts delegated_proxy.class # DelegateProxy

    I haven’t run into issues with needing the proxy class to identify itself as a proxy instead of the proxy subject, but if I do I’ll keep that in mind.

  4. Great post. It’s funny, I just made a post over this same topic a few days ago:

    http://www.binarylogic.com/2009/08/07/how-to-create-a-proxy-class-in-ruby/

    But yours seems to be much more thorough.

  5. I’m in a bit of a “I don’t get it” state here. I just don’t get it. I don’t see that the uses given here match any of the purposes for the GOF Proxy pattern, for example. I get (and appreciate and like) the “how”, I’m just struggling with the “why”.

    Looking at the examples, we want to build something that says it’s an Array and behaves exactly like an Array except when it doesn’t. But it only doesn’t behave exactly like an Array (which it insists it is) in certain circumstances. Sort of locally monkey-patched, if you will.

    I didn’t want to inherit from Array.

    Well why the heck not? If you want something to behave exactly the same as something else but with some specific extensions/decorations, isn’t that exactly what inheritance is for? We still pass Liskov Substitution, don’t we? (Because the subclass can be used anywhere its super class is).

    Help me be smarter…

  6. burtella burtella

    Aug 11, 2009

    Isn’t this the strategy pattern? Or am I missing something?

  7. @Mike – So Jeremy is probably right in that it should identify itself as a ProxyArray instead of an Array in this case. My example may not be top notch. I’ve heard a lot about it being bad to inherit from concrete classes such as String, Array and such so that is why I didn’t want to.

    @burtella Very similar. I’m going to do a post on the strategy pattern as well before too long. Hopefully that will clear up the difference.

  8. Can not make it works.
    Got error message:
    superclass mismatch for class Proxy

    ruby -v
    ruby 1.8.7 (2009-04-08 patchlevel 160) [i386-freebsd7]

  9. Oops. Sorry. Not that version of ruby.
    Last example does not work with
    ruby19 -v
    ruby 1.9.1p129 (2009-05-12 revision 23412) [i386-freebsd7]

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a web developer and programmer who has fallen deeply in love with Ruby. More about John.

Syndication

Feed IconRailsTips Articles - An assortment of howto's and thoughts on Ruby and Rails.

Feed IconRails Quick Tips - Ruby and Rails related links.