proxy_machine

A cool proxy implementation pattern in ruby

Details

Proxy Machine is a proxy/delegate framework. It can create a proxy for standalone objects or change the behavior of the entire class, also it is possible to create an execution stack with the proxy life cycle.

Install

sudo gem install proxy_machine

Example usage

Proxy standalone objects

p = proxy_for [1, 2, 3]
p.proxied? # => true
p.reverse # => [3, 2, 1]

Proxy the entire class

class MyClass
  attr_accessor :name

  # Configuring the proxy
  auto_proxy do
    before :name => lambda {|obj, args| puts "old name: #{obj.name}"}
  end

end

obj = MyClass.new
obj.proxied? # => true
obj.name # => old name: nil
              nil

Defining callbefores and callafters in method level

Callbefores:

Standalone way

p = proxy_for [1, 2, 3], :before => {
  :reverse => lambda {|obj, args| puts 'before reverse'}
}   

p.reverse # => before reverse
               [3, 2, 1]

Class way

class MyClass
  # ...
  auto_proxy do
    before :method => lambda {|obj, args| puts 'implementation'}
  end
  # ...
end

obj = MyClass.new
obj.proxied? # => true

Every instance of “MyClass” will be proxied

Callafters:

Standalone way

p = proxy_for [1, 2, 3], :after => {
 :reverse => lambda {|obj, result, args| result.sort}
}   

p.reverse # => [1, 2, 3] # We reordered the list

Class way

class MyClass
  # ...
  auto_proxy do
    after :method => lambda {|obj, result, args| puts 'implementation'}
  end
  # ...
end

You will always receive the arguments passed to the original method.

Defining callbefores and callafters for all method calls

Callbefores: This callback will receive a reference of the object, the symbol of the called method and the original arguments passed.

Standalone way

p = proxy_for [1, 2, 3], :before_all => lambda {|obj, method, args| puts 'before all'}
p.reverse # => before all
               [3, 2, 1]

p.size # => before all
            3

Class way

class MyClass
  # ...
  auto_proxy do
    before_all {|obj, method, args| puts 'implementation'}
  end
  # ...
end

Callafters: This callback will receive a reference of the object, the result of execution (this result could be nil), the symbol of the called method and the arguments passed.

Standalone way

p = proxy_for [1, 2, 3], :after_all => lambda {|obj, result, method, args| puts result}
p.reverse # => [1, 2, 3]
               [1, 2, 3] # puts

p.size # => 3
            3 # puts

Class way

class MyClass
  # ...
  auto_proxy do
    after_all {|obj, result, method, args| puts 'implementation'}
  end
  # ...
end

Registering a class to perform the callafter or callbefore

The constructor will receive the object, in case of a callafter it will receive the result too. You need to have a ‘call’ method. The ProxyMachine will create a new instance of the class every time it need to use it. You could use this feature with the before_all and after_all too.

# Example of class
class SortPerformer
  def initialize object, result = nil, method = nil, args = nil
    @object = object; @result = result; @method = method, @args = args
  end

  def call; @object.sort! end
end

Standalone way

p = proxy_for [1, 2, 3], :after => {
  :reverse => SortPerformer
}

p.reverse # => [1, 2, 3]

Class way

class MyClass
  # ...
  auto_proxy do
    after :method => Performer
  end
  # ...
end

Controlling the method execution with regexp

For before_all and after_all you could use regexp to configure which methods will be affected.

# Example of class
class MyRegexMethods                    
  attr_accessor :value
  def get_value1; @value ? @value : 'get' end
  def get_value2; @value ? @value : 'get' end
  def another_method; @value ? @value : 'another' end
  def crazy_one; @value ? @value : 'crazy' end
end

Standalone way

p = proxy_for MyRegexMethods.new, :before_all => [
  [/^get_/, lambda {|obj, method, args| obj.value = 'gotcha!' }]
]

p.get_value1 # => gotcha!
p.get_value2 # => gotcha!
p.another_method # => 'another
proxy.crazy_one # => 'crazy'

Class way

class MyRegexMethods
  # ...
  auto_proxy do
    before_all [
      [/^get_/, lambda {|obj, method, args| obj.value = 'gotcha!' }]
    ]
  end
  # ...
end

You could use many definitions if you want, the calls will happen in the declared order.

Standalone way

p = proxy_for MyRegexMethods.new, :before_all => [
  [/get_/, lambda {|obj, method, args| obj.value = "it_"}]
  [/value/, lambda {|obj, method, args| obj.value = "#{obj.value}works"}]
]

p.get_value1 # => it_works
p.get_value2 # => it_works
p.another_method # => another
p.crazy_one # => crazy

Class way

class MyRegexMethods
  # ...
  auto_proxy do
    before_all [
      [/get_/, lambda {|obj, method, args| obj.value = "it_"}]
      [/value/, lambda {|obj, method, args| obj.value = "#{obj.value}works"}]
    ]
  end
  # ...
end

It is also possible to use classes instead of procs.

# Example of class
class Change2Performer
  def initialize object, result = nil, method = nil, args = nil
    @object = object; @result = result; @method = method, @args = args
  end

  def call; @object.value = "#{@object.value}works" end
end

p = proxy_for MyRegexMethods.new, :before_all => [
  [/get_/, lambda {|obj, method, args| obj.value = "it_"}],
  [/value/, Change2Performer]
]

p.get_value1 # => it_works
p.get_value2 # => it_works
p.another_method # => another
p.crazy_one # => crazy

Building an execution stack

# Example of class
class StackClass
  attr_accessor :name, :company_name
end               

# Performers
make_upper = lambda {|obj, args| obj.name = obj.name.upcase }
make_without_space = lambda {|obj, args| obj.name = obj.name.gsub /\s+/, '-'}
make_round_brackets = lambda {|obj, args| obj.name = "(#{obj.name})" }

make_lower = lambda {|obj, args| obj.company_name = obj.company_name.downcase }
make_round_brackets2 = lambda {|obj, args| obj.company_name = "[#{obj.company_name}]" }

obj = StackClass.new
obj.name = "important name"
obj.company_name = "COMPANY NAME"

p = proxy_for obj, :before => {
  :name => [make_upper, make_without_space, make_round_brackets],
  :company_name => [make_lower, make_round_brackets2]
}

p.name # => (IMPORTANT-NAME)                 
p.company_name # => [company name]

How to detect that the object is a proxy?

The beautiful way:

o1 = [1, 2, 3]
o1.proxied? # => false

o2 = proxy_for [1, 2, 3] 
o2.proxied? # => true

It will work with auto_proxy usage too.

Other way:

p = proxy_for [1, 2, 3]
defined? p.proxied_class?

Getting access to the original object

Call original_object method in the proxy object.

proxy = proxy_for [1, 2, 3]
proxy.proxied? # => true
proxy.original_object.proxied? # => false

It will work with auto_proxy usage too.

Special options

1 - allow_dinamic: Allow execute methods based on method missing, that do not exists actually. When allow_dinamic is enabled, proxy_machine will not check if this method really exists. Default is false.

# Example of class
class MyClass
  attr_accessor :value
  def method_missing(symbol, *args); 'nice!'; end
end

Standalone way

p = proxy_for MyClass.new, :allow_dinamic => true, :before => {
  :magic_method => lambda {|obj| obj.value = 'other value' }
}

p.magic_method # => 'other value'

Class way

class MyClass
  # ...
  auto_proxy do
    allow_dinamic true
    before :magic_method => lambda {|obj| obj.value = 'other value' }
  end
  # ...
end

2 - avoid_original_execution: When this option is enabled, proxy_machine will not call the original method. Default is false.

Standalone way

p = proxy_for [3, 2, 1], 
  :avoid_original_execution => true,
  :before => {
    :empty? => lambda {|obj| obj.sort!}
  }

p.empty? # => nil
p.original_object # => [1, 2, 3]

Class way

class MyClass
  # ...
  auto_proxy do
    avoid_original_execution true
  end
  # ...
end

Trying it in irb

irb
require 'proxy_machine'

proxy_for...

class XYZ
  auto_proxy do
    ...
  end
  ...
end

Other ways:

1º - Creates a proxy for the informed object

p = Proxy.new [1,2,3]
p.size # => 3

2º - A proxy with a before callback.

p = Proxy.new [1,2,3], :before => {
  :size => lambda {|obj| puts "before: #{obj.inspect}"}
}

p.size # => before: [1, 2, 3]
            3

3º - A proxy with a after callback

p = Proxy.new [1,2,3], :after => {
  :size => lambda {|obj, result| puts "after: #{obj.inspect}: result => #{result}"}
}

p.size # => after: [1, 2, 3]: result => 3
            3

4º - Both

p = Proxy.new [1,2,3], 
              :before => {
                :size => lambda {|obj| puts "before: #{obj.inspect}"}
              }, :after => {
                :size => lambda {|obj, result| puts "after: #{obj.inspect}: result => #{result}"}
              }

Copyright © 2010, 2011 Túlio Ornelas. See LICENSE for details.