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
Copyright © 2010, 2011 Túlio Ornelas. See LICENSE for details.