The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Roxy: A Ruby Proxy-Object Library

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
rwdaigle

Posts: 312
Nickname: rwdaigle
Registered: Feb, 2003

Ryan is a passionate ruby developer with a strong Java background.
Roxy: A Ruby Proxy-Object Library Posted: Nov 10, 2008 3:02 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by rwdaigle.
Original Post: Roxy: A Ruby Proxy-Object Library
Feed Title: Ryan's Scraps
Feed URL: http://feeds.feedburner.com/RyansScraps
Feed Description: Ryan Daigle's various technically inclined rants along w/ the "What's new in Edge Rails" series.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by rwdaigle
Latest Posts From Ryan's Scraps

Advertisement

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

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.

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

Read: Roxy: A Ruby Proxy-Object Library

Topic: Amethyst and Beyond Previous Topic   Next Topic Topic: CouchDB basic authentication

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use