From 2a7b686b0b01ed2e642ab73143aab2f3bc33d6f1 Mon Sep 17 00:00:00 2001 From: Nikita Afanasenko Date: Sun, 31 Mar 2013 13:48:21 +0400 Subject: [PATCH] Implement proxy class methods caching. Now we have a proxy classes with the methods predefined to prevent tons of `method_missing` calls. --- lib/attr_ability/model/instance_proxy.rb | 51 +++++++++++++++---- lib/attr_ability/model_additions.rb | 2 +- .../attr_ability/model/instance_proxy_spec.rb | 38 ++++++++++++++ 3 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 spec/attr_ability/model/instance_proxy_spec.rb diff --git a/lib/attr_ability/model/instance_proxy.rb b/lib/attr_ability/model/instance_proxy.rb index bd138ac..8fcfabb 100644 --- a/lib/attr_ability/model/instance_proxy.rb +++ b/lib/attr_ability/model/instance_proxy.rb @@ -1,20 +1,49 @@ module AttrAbility module Model - class InstanceProxy - instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ } + module InstanceProxy + @@initialization_mutex = Mutex.new - def initialize(model, sanitizer) - @model = model - @sanitizer = sanitizer + def self.[](klass) + @@initialization_mutex.synchronize do + @_proxy_classes ||= {} + @_proxy_classes[klass] ||= Class.new do + include ProxyMethods + end + end end - protected + module ProxyMethods + extend ActiveSupport::Concern - def method_missing(name, *args, &block) - @model.mass_assignment_sanitizer = @sanitizer - @model.send(name, *args, &block) - ensure - @model.mass_assignment_sanitizer = nil + included do + alias __proxy_class__ class + instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^initialize$|^method_missing$)/ } + end + + def initialize(model, sanitizer) + @model = model + @sanitizer = sanitizer + end + + protected + + def method_missing(name, *args, &block) + delegate_method(name) + self.send(name, *args, &block) + end + + private + + def delegate_method(name) + __proxy_class__.class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{name}(*args, &block) + @model.mass_assignment_sanitizer = @sanitizer + @model.#{name}(*args, &block) + ensure + @model.mass_assignment_sanitizer = nil + end + METHOD + end end end end diff --git a/lib/attr_ability/model_additions.rb b/lib/attr_ability/model_additions.rb index a5b7c62..34999b7 100644 --- a/lib/attr_ability/model_additions.rb +++ b/lib/attr_ability/model_additions.rb @@ -62,7 +62,7 @@ def ability(action, attributes) end def as(ability) - AttrAbility::Model::InstanceProxy.new(self, self.class.build_sanitizer(ability)) + AttrAbility::Model::InstanceProxy[self.class].new(self, self.class.build_sanitizer(ability)) end def as_system diff --git a/spec/attr_ability/model/instance_proxy_spec.rb b/spec/attr_ability/model/instance_proxy_spec.rb new file mode 100644 index 0000000..a5dec19 --- /dev/null +++ b/spec/attr_ability/model/instance_proxy_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe AttrAbility::Model::InstanceProxy do + with_model :ProxyPost do + table do |t| + t.string :title + t.integer :author_id + t.string :status + end + end + + with_model :ProxyAuthor do + table do |t| + t.string :name + end + end + + subject { AttrAbility::Model::InstanceProxy } + let(:sanitizer) { AttrAbility::Model::Sanitizer.new(SanitizerTestAbility.new(:system)) } + let(:post) { ProxyPost.new(title: "some") } + let(:author) { ProxyAuthor.new(name: "some") } + let(:proxied_post) { subject[ProxyPost].new(post, sanitizer) } + let(:proxied_author) { subject[ProxyAuthor].new(author, sanitizer) } + + it "caches class" do + subject[ProxyPost] == subject[ProxyPost] + end + + it "proxies methods" do + proxied_post.title.should == post.title + end + + it "does not leak methods to another model" do + # ensure post cached + proxied_post.title + proxied_author.methods.should_not include(:title) + end +end