Roxy: A Ruby Proxy-Object Library

Posted by ryan
at 4:27 PM on Monday, November 10, 2008

Proxies are a powerful tool in software development, allowing you to transparently provide extra functionality or a slight abstraction to an underlying object. One of the more visible uses of proxies is in ActiveRecord which uses a proxy to represent its many associations. For instance, in the following article definition:

1
2
3
class Article < ActiveRecord::Base
  has_many :comments
end

when you call article.comments what you get back is actually a proxy object that wraps the comments collection with some extra functionality like the << and build methods. Although it looks like a normal Array when you directly access comments, it’s really a proxy that’s marshaling method calls to an underlying collection or intercepting the method calls if it’s functionality it wants to handle itself. Named scopes also work in a similar manner.

That’s great and all, but these proxies are tied very specifically to their implementations within ActiveRecord .. and that’s what Roxy is intended to address. Roxy brings some serious moxie to your development with the ability to easily define and use proxies in your non ActiveRecord classes.

Let’s take a look at an example: Suppose I have a person object that has a list of parents and children (again, this is outside the scope of ActiveRecord or any other persistence framework where you might be able to do this with some other mechanism).

1
2
3
class Person
  attr_accessor :first, :last, :parents, :children
end

If you want add functionality to a person that determines if their parents are divorced, or if they have any stepchildren you could easily enough add that functionality directly to the Person object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person
  attr_accessor :first, :last, :parents, :children
  
  def initialize(first, last)
    @first, @last = first, last
  end

  # If my parents have different last names, then assume they're divorced
  def parents_divorced?
      parents.size > 1 and parents.collect { |parent| parent.last }.uniq.size > 1
  end

  # Any child with a different last name than mine is considered
  # a step-child.
  def step_children
    children.select { |child| self.last != child.last }
  end
end

but this approach has always seemed very obtuse, however. If I am strictly modeling my domain to the real world, which is the approach I favor until it becomes unwieldy to do so, what I really want to do is ask a person’s parents if they’re divorced. After all, their divorce status is a property of the parents, not the person itself. With Roxy this structure is easy to model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
require 'roxy'
class Person

  # Add in proxy ability
  include Roxy::Moxie

  attr_accessor :first, :last, :parents, :children
  
  # Add ability to ask the parents collection if they are divorced
  proxy :parents do    
    def divorced?
      proxy_target.size > 1 and
        proxy_target.collect { |parent| parent.last }.uniq.size > 1
    end
  end
  
  # Add ability to ask the children collection for only the step-children
  proxy :children do
    def step
      proxy_target.select { |child| proxy_owner.last != child.last }
    end    
  end    
  
  def initialize(first, last)
    @first, @last = first, last
  end
end

# Now the following is possible:
person.parents.divorced? #=> true|false
person.children.step #=> [<Person...>, <Person...>]

Roxy allows you transparently adorn existing attributes and methods with added functionality, making a more realistic domain model. This is very similar to rails’ association proxies except that you are now free to add functionality to all methods and objects.

Proxy methods are defined in the block that is passed to the proxy call. Within each proxy method you can reference the object that owns the proxy (the person instance here) as proxy_owner and the thing that is being proxied (the parents and children collections here) as proxy_target.

Advanced

You’re not limited to proxying existing methods, you can just as easily proxy to another object using the :to option.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require 'roxy'
class Person

  # Add in proxy ability
  include Roxy::Moxie

  attr_accessor :address

  proxy :shipping, :to => ShippingMethod.all do
    def cheapest
      proxy_target.min { |m| m.cost_from(proxy_owner.address) }
    end
  end
end

# Find the cheapest shipping method from all methods
person.shipping.cheapest #=> <ShippingMethod...>

If the value you want to proxy needs to be evaluated at runtime just pass in a proc. The proc should accept a single argument which will be the proxy_owner instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'roxy'
class Person

  # Add in proxy ability
  include Roxy::Moxie

  attr_accessor :address

  proxy :shipping, :to => proc { |person| ShippingMethod.available_for(person) } do
    def cheapest
      proxy_target.min { |m| m.cost_from(proxy_owner.address) }
    end
  end
end

# Find the cheapest shipping method from the methods
# only available to 'person'
person.shipping.cheapest #=> <ShippingMethod...>

You’ll notice that the best use of proxies is as a lightweight relationship between two things. I.e. instead of creating a whole other object to represent the relationship between a person and the various shipping methods you can quickly add functionality directly to that object-relationship as a proxy method.

A sign of abuse of this particular proxy pattern is when you reference only one of the proxy_owner or proxy_target and neither depends on the other in any way. That is usually an indication that the functionality should live solely in the referenced proxy owner/target and not in the proxy itself.

Proxy methods can also be defined as modules (as in Rails’ association extensions) for greater re-use between similar proxies with the :extend option (which can take one or more modules):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'roxy'
class Person

  # Add in proxy ability
  include Roxy::Moxie

  attr_accessor :age, :children, :parents

  # Re-usable functionality to find the oldest person in a collection
  module PersonCollection
    def oldest
      proxy_target.max { |p| p.age }
    end
  end

  proxy :children, :extend => PersonCollection
  proxy :parents, :extend => PersonCollection

end

# Now the following is possible:
person.parents.oldest #=> <Person...>
person.children.oldest #=> <Person...>

Once you grasp the beauty, simplicity and power of proxies you’ll likely find many uses for them. They’re a great tool to have in your toolbox and Roxy would love it if she found a place in yours.

Update: Nov. 14th, 2008: Based on your feedback I just updated the gem (v0.2) to properly handle proxying methods that have arguments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person
  
  include Roxy::Moxie
  
  # Contrived example to show that reload value is passed through
  # to the proxy target
  def ancestors(reload = false)
    1.upto(4).to_a.collect { |i| "#{reload ? 'r' : ''}ancestor#{i}" }      
  end
  
  # Men = odd numbered ancestors
  proxy :ancestors do
    def men
      proxy_target.select { |a| a.include?('1') || a.include?('3') }
    end
    def women
      proxy_target - men
    end
  end

end

# Note how arg is passed through (reloaded ancestors start
# with 'r')
person.ancestors(true).men #=> ['rancestor1', 'rancestor3']

# Even default arg is retained (default is false, so no
# ancestors have 'r' prefix)
person.ancestors.men #=> ['ancestor1', 'ancestor3']

Installation

Installing is pretty simple…

sudo gem install yfactorial-roxy --source http://gems.github.com

Teaser

I hope to have an extension library up soon that utilizes Roxy to provide ActiveRecord-like association definitions in ActiveResource. Something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'roxy'
class User < ActiveResource::Base

  include Roxy::Moxie

  proxy :articles,
            :to => proc { |u| Article.find(:all, :params => { :user_id => u.id } } do
    def destroy_all
      proxy_target.each { |a| a.destroy }
    end
  end
end

# Now a remote user looks a lot like a first class active record object:
user.articles #=> [<Article...>, <Article...>, ...]
user.articles.destroy_all

Stay tuned for that, and let me know where you’ve found proxies to be a great tool to have around. I’m always looking for better example scenarios.

tags: ruby

Comments

Leave a response

  1. CollinNovember 10, 2008 @ 08:59 PM

    I found a nice use for roxy in my current project. I’m wrapping up a legacy schema with DataMapper and Merb, so It’s not quite an orthordox use involving :proxy_target.

    But it sure is nice to have these kinds of semantics for my weird legacy app. :D

    http://pastie.org/311946

  2. Julian RussellNovember 10, 2008 @ 09:53 PM

    Looks very cool.

    I think in the example about using :extends and modules you need another line:

    proxy :children, :extend => PersonCollection
    proxy :parents, :extend => PersonCollection # <- new line

    So the examples make sense (particularly person.parents.oldest)

  3. John NunemakerNovember 10, 2008 @ 11:26 PM

    Hawt! Can’t wait to need this. :)

  4. Sascha KonietzkeNovember 11, 2008 @ 02:54 AM

    This article just solved my biggest problem today. Thanks!

  5. Henrik NNovember 11, 2008 @ 03:44 AM

    Minor niggle, but the fact that the module is “Roxy::Moxie” but the method is “proxy” means the code is less self-documenting than it could be. As evidenced by the comment here:

    1. Add in proxy ability include Roxy::Moxie

    Moxie is a fun word and all, but how about Roxy::Proxy instead? ;)

    This is assuming Roxy is yours, Ryan—you didn’t explicitly say, but I got that impression.

  6. Steve TookeNovember 11, 2008 @ 05:02 AM

    I’m not convinced by this. Its a nice little trick, but I feel that it adds complexity, and doesn’t make things much more readable. I prefer person.step_children to person.children.step as I think its more explicit.

    I want to think of it in terms of the law of demeter, but the methods are still defined in the right class, it just looks wrong to me.

  7. Ryan DaigleNovember 11, 2008 @ 07:09 AM

    Collin: Does DataMapper not provide a native count functionality that you can use? If not, or if I’m missing some nuance of your example, then glad that you found a use for Roxy!

    Jullian: Right you are. Fix added, thanks!

    Henrik: Yeah, I have a soft spot for rhyming and fun words. Hence my unnatural affinity for “moxie” in this instance. I shall have to rectify that at some point in the future.

    Steve: I think your point is very much a design preference, and a valid one. I prefer to ask the collection of children for the ones that are step-children because step-children are a subset of children. I use this pattern a lot in my ActiveRecord apps. I.e. instead of having user.articles and user.published_articles I have user.articles and user.articles.published (usually implemented with chained named scopes). It just makes sense in my mind to start with a larger thing and narrow it down much like namespacing: com.yfactorial.ruby.... Plus the filtering logic is often reusable and more portable when package in a module that can be attached to the collection itself.

  8. malkomalkoNovember 11, 2008 @ 08:43 AM

    Is there a problem with installing the gem? It doesn’t look like it has propagated to github.

  9. Ryan DaigleNovember 11, 2008 @ 10:09 AM

    @malkomalko: You’re right, the gem hasn’t yet propagated to github. I resubmitted it, should be up shortly.

  10. Mark WildenNovember 11, 2008 @ 11:48 AM

    This is very cool. However, I wouldn’t call it a proxy. The Gang of Four (and Wikipedia) definition is that a proxy is a stand-in for another object. The important difference is that it has the same interface as the target object – clients don’t know that they’re accessing a remote object instead of a local object, for example.

    I also don’t see a big difference between this kind of proxy and one object simply holding a reference to another object, where the interface to the other object is used as well as new features.

    Take a person’s name, for example. In these terms, a name could be seen as association between a Person and a String. It would have the usual properties of a String, but also have additional behavior in prepending a title (Mr. or Ms.) depending on the gender of the Person.

    In other words, what’s the difference between object A proxying to object B, and A just using B as part of its composition? The difference in GoF terms is that they would have the same interface, but that’s not the case here.

    All that said, the proof of the pudding is in its usefulness, and clearly this module is providing use to people. I just don’t think it’s accurately named.

  11. Ryan BatesNovember 11, 2008 @ 12:04 PM

    Very nice Ryan! I can recall several times in the past I could have used this. I’m sure it will be useful in the near future. Thanks.

  12. CollinNovember 11, 2008 @ 12:15 PM

    Ryan,

    DataMapper does have semantics for just this sort of thing if you can use the bulit-in query methods/association proxies.

    But I have to drop down to find_by_sql which means I lose some of those nice things.

    Though, I did run into a problem and had to go back to my underscored methods.

    Roxy eats arguments. If I have a proxy that exposes this api

    Model.spotlights Model.spotlights.count

    This won’t work:

    Model.spotlights(:name => ‘waldo’) Model.spotlights(:name => ‘waldo’).count

    The spotlights method never gets the argument. I’ve poked around the source and it seems like a tricky thing to do. Anybody have any ideas?

  13. Ryan DaigleNovember 11, 2008 @ 02:29 PM

    @Collin: Great point, the current lib does clobber arguments. I’ll take a look at that in the next few days and see what I can figure out.

    @Mark: Your comments are all valid ones. I would like to add that, in the way that I am using them, proxies are a superset of the interface of the proxy target. So while you’re right that they are not the same interface of the target, in the conventional definition of the term, they do fully define the original interface.

    This pattern is sort of a delegate/proxy hybrid. It can be used to simply delegate method invocations to the target but it can also be used to setup a sort of anonymous proxy to another object.

    I shall concede the point, however, that proxy is not a 100% accurate term for this pattern. Thanks for your feedback!

  14. Marius MathiesenNovember 12, 2008 @ 04:42 AM

    Nice work! This is a matter of style, yours may not be like mine. But I really don’t like the “magic” variables proxy_target and proxy_owner appearing inside the proxy block. Personally I’d prefer to be able to name these myself to give more meaning, eg:

    class Person proxy :addresses do |me, addresses| #... end end

    Great stuff!

  15. Ryan DaigleNovember 12, 2008 @ 07:59 AM

    @Marius: I like your style better too :) I’ll post an update shortly.

  16. lomakinNovember 12, 2008 @ 11:56 AM

    Looks great. But I don’t understand, is there differents from named scopes?

  17. Sergio SalvatoreNovember 12, 2008 @ 01:58 PM

    Nice idea Ryan! I did notice that you’re using define_method though. In my tests, calls to methods defined using define_method are about half as fast as if the methods had been defined using class_eval. I have to post my findings soon… I’m confused as to why exactly this is, but I’m far from an expert in the Ruby interpreter’s internals… Good luck!

  18. Ryan DaigleNovember 12, 2008 @ 06:44 PM

    lomakin - Yes, this is different from @named_scope because it does not define SQL conditions, is not dependent on ActiveRecord, and is much more multi-purpose. The only similarities might be the implementation style.

  19. Ben MariniNovember 13, 2008 @ 08:44 PM

    Interesting stuff. This sounds like the Decorator pattern: http://en.wikipedia.org/wiki/Decorator_pattern

  20. Ryan DaigleNovember 14, 2008 @ 07:24 AM

    @Ben – You’re right, it does seem to be most accurate to call this a decorator pattern. Several of the GoF patterns seemed to contain a lot of overlap and similarities to me, hence my poor use of them here.

  21. GuoliangNovember 14, 2008 @ 09:07 AM

    Sounds very useful pattern. It’ll be even better if the names can be changed. Roxy, moxie and proxy appeared next to each other just make me feel unnatural.

  22. Ryan DaigleNovember 14, 2008 @ 09:37 AM

    I am very offended that the Ruby community does not seem to share my love of rhyming. This has made me question my commitment to it.

    @Collin – I just updated the gem so that it doesn’t clobber proxied method arguments. Let me know if that doesn’t work for you.

    Marius - It turns out that being able to have the @proxy_owner and proxy_target specified as block arguments isn’t so simple since the block is applied to the proxy once yet the proxy_target needs to be calculated at every invocation. I’m punting for now :(

  23. 7ransNovember 15, 2008 @ 11:35 AM

    Always enjoy seeing explorations into meta-programming.

    For some background, this is very similar to Facets #method_space. However, that does not “proxy” a pre-existing method, so one would have no name the target different from the proxy. On the upside you don’t need proxy_target (or the proxy_owner method, I think)

    Subsequently, what isn’t made clear in your examples by your approach is that we loose access to the original method. True?

  24. JerryNovember 19, 2008 @ 11:29 AM

    Ryan, really neat, many thanks!

    Can you clarify one thing for me please: I’d have thought the class_evals in moxie.rb are redundant. All klass.class_eval() does is execute the string in the context of ‘klass’. Since there’s no receiver for the class_evals, it must be self, so it’s a no-op as self is the class we’re extending. What have I missed, please?

    I’ve replace them with this:

    if !original_method or original_method.arity == 0 define_method name do (@proxy_for ||= {})[name] ||= Proxy.new(self, options, nil, &block) end else define_method name do | *args | Proxy.new(self, options, args, &block) end end

    and my basic tests still pass. Have I missed an use case where the class_eval will be required, please?

    PS: def self.included(within) within.extend ClassMethods end works too…

  25. Ryan DaigleNovember 19, 2008 @ 09:00 PM

    @Jerry – thanks for the comments. Haven’t dug into them yet, but I will shortly!

  26. Ryan DaigleNovember 22, 2008 @ 10:26 AM

    Jerry, thanks for your comments. My clinging to class_eval was most probably a workaround for an earlier problem I encountered that no longer exists, so thanks for point that out. I’ve update Roxy to use your much more concise implementation.

    Regarding using within.class_eval { extend ClassMethods }, my impression was that in Ruby 1.9 Object.extend will be private. class_eval is my attempt to make this usable in Ruby 1.9. Am I mistaken here?

    Thanks again for your comments!

  27. Dan KubbDecember 04, 2008 @ 02:28 AM

    @Ryan: DataMapper doesn’t include a native count functionality because many storage backends (aside from RDBMS’) don’t provide a way to count all the records of a given type. In many cases the only way is to get that info is to pull back all the records and then count the number returned, which can be incredibly inefficient.

    The core of DataMapper is designed to allow easy object retrieval and persistence management, not necessarily reporting. Instead we chose to make it so that the aggregate functions (count, sum, min, max, etc) are available only after opting in by requiring the dm-aggregates gem.

  28. Christopher J. BottaroDecember 15, 2008 @ 10:03 PM

    Is there any way to get it to work with ActiveRecord attributes? For instance, if I have an ActiveRecord Person that has a “name” column, I can’t say “proxy :name do … end”. I haven’t looked at the source, but I assume it’s because Person#name is handled by #method_missing?