diff --git a/lib/draper/base.rb b/lib/draper/base.rb index 5c197cd..44004da 100644 --- a/lib/draper/base.rb +++ b/lib/draper/base.rb @@ -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 diff --git a/performance/active_record.rb b/performance/active_record.rb new file mode 100644 index 0000000..146c865 --- /dev/null +++ b/performance/active_record.rb @@ -0,0 +1,4 @@ +module ActiveRecord + class Base + end +end \ No newline at end of file diff --git a/performance/bechmark.rb b/performance/bechmark.rb new file mode 100644 index 0000000..6e92493 --- /dev/null +++ b/performance/bechmark.rb @@ -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 diff --git a/performance/decorators.rb b/performance/decorators.rb new file mode 100644 index 0000000..a6d467a --- /dev/null +++ b/performance/decorators.rb @@ -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 diff --git a/performance/models.rb b/performance/models.rb new file mode 100644 index 0000000..28adbb2 --- /dev/null +++ b/performance/models.rb @@ -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 \ No newline at end of file diff --git a/spec/draper/base_spec.rb b/spec/draper/base_spec.rb index 02cc01a..310ca3e 100644 --- a/spec/draper/base_spec.rb +++ b/spec/draper/base_spec.rb @@ -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