Refactoring #method_missing to define a previously missing method in the Decorator when calling it for the first time.

This commit is contained in:
Alexandre Angelim 2011-11-29 17:29:11 -02:00
parent 78b200a84f
commit ebe30511b7
6 changed files with 147 additions and 1 deletions

View File

@ -157,7 +157,10 @@ module Draper
def method_missing(method, *args, &block)
if allow?(method)
begin
model.send(method, *args, &block)
self.class.send :define_method, method do |*args, &block|
model.send(method, *args, &block)
end
self.send(method, *args, &block)
rescue NoMethodError
super
end

View File

@ -0,0 +1,4 @@
module ActiveRecord
class Base
end
end

55
performance/bechmark.rb Normal file
View File

@ -0,0 +1,55 @@
require 'rubygems'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
Bundler.require(:default) if defined?(Bundler)
require "benchmark"
require "draper"
require "./performance/models"
require "./performance/decorators"
Benchmark.bm do |bm|
puts "\n[ Exclusivelly using #method_missing for model delegation ]"
[ 1_000, 10_000, 100_000 ].each do |i|
puts "\n[ #{i} ]"
bm.report("#new ") do
i.times do |n|
ProductDecorator.decorate(Product.new)
end
end
bm.report("#hello_world ") do
i.times do |n|
ProductDecorator.decorate(Product.new).hello_world
end
end
bm.report("#sample_class_method ") do
i.times do |n|
ProductDecorator.decorate(Product.new).class.sample_class_method
end
end
end
puts "\n[ Defining methods on method_missing first hit ]"
[ 1_000, 10_000, 100_000 ].each do |i|
puts "\n[ #{i} ]"
bm.report("#new ") do
i.times do |n|
FastProductDecorator.decorate(FastProduct.new)
end
end
bm.report("#hello_world ") do
i.times do |n|
FastProductDecorator.decorate(FastProduct.new).hello_world
end
end
bm.report("#sample_class_method ") do
i.times do |n|
FastProductDecorator.decorate(FastProduct.new).class.sample_class_method
end
end
end
end

47
performance/decorators.rb Normal file
View File

@ -0,0 +1,47 @@
require "./performance/models"
class ProductDecorator < Draper::Base
decorates :product
def awesome_title
"Awesome Title"
end
# Original #method_missing
def method_missing(method, *args, &block)
if allow?(method)
begin
model.send(method, *args, &block)
rescue NoMethodError
super
end
else
super
end
end
end
class FastProductDecorator < Draper::Base
decorates :product
def awesome_title
"Awesome Title"
end
# Modified #method_missing
def method_missing(method, *args, &block)
if allow?(method)
begin
self.class.send :define_method, method do |*args, &block|
model.send(method, *args, &block)
end
self.send(method, *args, &block)
rescue NoMethodError
super
end
else
super
end
end
end

20
performance/models.rb Normal file
View File

@ -0,0 +1,20 @@
require "./performance/active_record"
class Product < ActiveRecord::Base
def self.sample_class_method
"sample class method"
end
def hello_world
"Hello, World"
end
end
class FastProduct < ActiveRecord::Base
def self.sample_class_method
"sample class method"
end
def hello_world
"Hello, World"
end
end

View File

@ -382,4 +382,21 @@ describe Draper::Base do
subject.kind_of?(source.class).should == true
end
end
describe "#method_missing" do
context "when #hello_world is called for the first time" do
it "hits method missing" do
subject.should_receive(:method_missing)
subject.hello_world
end
end
context "when #hello_world is called again" do
before { subject.hello_world }
it "proxies method directly after first hit" do
subject.should_not_receive(:method_missing)
subject.hello_world
end
end
end
end