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.