Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@casperisfine
Last active June 10, 2016 15:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save casperisfine/a40137b7b3ce0413d23f16cd7d1d8a05 to your computer and use it in GitHub Desktop.
Save casperisfine/a40137b7b3ce0413d23f16cd7d1d8a05 to your computer and use it in GitHub Desktop.
begin
require 'bundler/inline'
rescue LoadError => e
$stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
raise e
end
gemfile(true) do
source 'https://rubygems.org'
gem 'activesupport', '5.0.0rc1'
gem 'benchmark-ips'
end
require 'benchmark/ips'
require 'active_support/all'
class Module
def optimized_delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
unless to
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
end
if prefix == true && to =~ /^[^a-z_]/
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
method_prefix = \
if prefix
"#{prefix == true ? to : prefix}_"
else
''
end
location = caller_locations(1, 1).first
file, line = location.path, location.lineno
to = to.to_s
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
methods.each do |method|
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
# The following generated method calls the target exactly once, storing
# the returned value in a dummy variable.
#
# Reason is twofold: On one hand doing less calls is in general better.
# On the other hand it could be that the target has side-effects,
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
if allow_nil
method_def = [
"def #{method_prefix}#{method}(#{definition})",
"_ = #{to}",
"if !_.nil? || nil.respond_to?(:#{method})",
" _.#{method}(#{definition})",
"end",
"end"
].join ';'
else
exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
method_def = [
"def #{method_prefix}#{method}(#{definition})",
" _ = #{to}",
" _.#{method}(#{definition})",
"rescue NoMethodError => e",
" if _.nil? && e.name == :#{method}",
" #{exception}",
" else",
" raise",
" end",
"end"
].join ';'
end
module_eval(method_def, file, line)
end
end
end
def bench_delegate
Class.new do
delegate :foo, to: :@bar
end
end
def bench_optimized_delegate
Class.new do
optimized_delegate :foo, to: :@bar
end
end
Benchmark.ips do |x|
x.report('as::delegate') do
bench_delegate
end
x.report('op::delegate') do
bench_optimized_delegate
end
end

Fetching gem metadata from https://rubygems.org/ Fetching version metadata from https://rubygems.org/ Resolving dependencies... Using concurrent-ruby 1.0.2 Using i18n 0.7.0 Using minitest 5.9.0 Using thread_safe 0.3.5 Using benchmark-ips 2.6.1 Using bundler 1.12.3 Using tzinfo 1.2.2 Using activesupport 5.0.0.rc1 Warming up -------------------------------------- as::delegate 1.855k i/100ms op::delegate 2.080k i/100ms Calculating ------------------------------------- as::delegate 20.128k (± 5.6%) i/s - 102.025k in 5.082693s op::delegate 20.725k (± 9.6%) i/s - 104.000k in 5.073423s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment