Wednesday, 20 August 2014
Wednesday, 13 August 2014
Monday, 11 August 2014
Proxy
http://blog.rubybestpractices.com/posts/gregory/060-issue-26-structural-design-patterns.html
Proxy
A Proxy is any object that acts as a drop-in replacement object that does a bit of work and then delegates to some other underlying object. This is another pattern that’s easy to recognize for Rails developers, because it is used extensively in ActiveRecord associations support. The following example shows a typical interaction between a base model and one of its associations.
- quiz = Quiz.first
- quiz.questions.class #=> Array
- quiz.questions.count #=> 10
- quiz.questions.create(:question_text => "Who is the creator of Ruby?",
- :answer => "Matz")
- quiz.questions.count #=> 11
- quiz.questions[-1].answer #=> "Matz"
While the questions association claims to be an array, it also provides some of the common helpers found in ActiveRecord::Base. If we stick to the core idea and ignore some of the Rails specific details, creating such an association proxy is fairly easy to do using Ruby’s delegate standard library. The code below more or less does the trick.
- require "delegate"
- class Quiz
- def questions
- @questions ||= HasManyAssociation.new([])
- @questions.associated_class ||= Question
- @questions
- end
- end
- class Question
- def initialize(params)
- @params = params
- end
- attr_reader :params
- def answer
- params[:answer]
- end
- end
- class HasManyAssociation < DelegateClass(Array)
- attr_accessor :associated_class
- def initialize(array)
- super(array)
- end
- def create(params)
- self << associated_class.new(params)
- end
- end
- quiz = Quiz.new
- # grab the proxy object
- questions = quiz.questions
- # use custom create() method on proxy object
- questions.create(:question_text => "Who is the creator of Ruby?",
- :answer => "Matz")
- questions.create(:question_text => "Who is the creator of Perl?",
- :answer => "Larry Wall")
- # use array like behavior on proxy object
- p questions[0].answer #=> "Matz"
- p questions[1].answer #=> "Larry Wall"
- p questions.map { |q| q.answer }.join(", ") #=> "Matz, Larry Wall"
Interestingly enough, while Ruby provides a standard library for building Proxy objects, most people tend to implement them in a different way, through the use of an explicitmethod_missing() hook on a blank slate object such as Ruby 1.9’s BasicObject. For example, we could have written our HasManyAssociation code in the manner shown below and things would still work as expected.
- class HasManyAssociation < BasicObject
- attr_accessor :associated_class
- def initialize(array)
- @array = array
- end
- def create(params)
- self << associated_class.new(params)
- end
- def method_missing(*a, &b)
- @array.send(*a, &b)
- end
- end
Without looking at the source, I’m almost sure that Rails does something similar to this, because doing some_association.class returns Array rather than the name of the proxy object. This is the only noticeable difference on the surface between this approach and the DelegateClass approach.
Personally, I’ve written proxies in both ways, and I tend to prefer the DelegateClass()approach slightly, simply because it’s more explicit and doesn’t require me to explicitly define amethod_missing() hook. But on the other hand, we can see that rolling your own proxy implementation is trivial in Ruby, and some may prefer the self contained nature of doing the delegation work yourself. It’d be interesting to hear what readers have to say on this topic, so please feel free to post to the mailing list if you prefer one approach over the other.
Subscribe to:
Posts (Atom)