Skip to content

Instantly share code, notes, and snippets.

@Ferdy89
Created January 7, 2019 00:30
Show Gist options
  • Save Ferdy89/4da3a6b5dc78fd85fbacdf74807a617c to your computer and use it in GitHub Desktop.
Save Ferdy89/4da3a6b5dc78fd85fbacdf74807a617c to your computer and use it in GitHub Desktop.
Decorating with metaprogramming
class Decorator
attr_reader :klass, :config, :decorator
def initialize(klass, config, decorator)
@klass = klass
@config = config
@decorator = decorator
end
def >(method_name)
method = klass.instance_method(method_name)
# Only local variables are accessible from within `define_method`
decorator = self.decorator
config = self.config
# Redefine the method wrapping it with the decorator
klass.define_method(method_name) do |*args|
# `self` here is an instance of the class using the decorator.
# Both `method` and `decorator` are unbound and we can now bind them to
# this instance.
bound_method = method.bind(self)
bound_decorator = decorator.bind(self)
bound_decorator.call(config, bound_method, args)
end
end
end
module Decoratable
def decorate(method_name)
method = instance_method(method_name)
define_method(method_name) do |*config|
Decorator.new(self, config, method)
end
end
end
class Bar
extend Memoizable
memoize >
def bar
puts 'first time!'
'bar'
end
end
b = Bar.new # => #<Bar>
b.bar # => puts 'first time!' and returns 'bar'
b.inspect # => #<Bar @bar="bar">
b.bar # => only returns 'bar'
require 'benchmark/ips'
class TraditionalBar
def bar
@bar ||= begin
puts 'first time!'
'bar'
end
end
end
Benchmark.ips do |x|
meta = Bar.new
traditional = TraditionalBar.new
x.report('meta') { meta.bar }
x.report('traditional') { traditional.bar }
x.compare!
end
# Warming up --------------------------------------
# meta
# 50.895k i/100ms
# traditional
# 275.834k i/100ms
# Calculating -------------------------------------
# meta 590.188k (± 3.7%) i/s - 2.952M in 5.009063s
# traditional 8.173M (± 3.8%) i/s - 40.823M in 5.003081s
#
# Comparison:
# traditional: 8173312.4 i/s
# meta: 590187.6 i/s - 13.85x slower
module Memoizable
extend Decoratable
# `config` is not used here, but could be used for configuring the decorator.
# Example:
# # `config` here would be `[{ times: 5 }]`
# retry(times: 5) >
decorate def memoize(config, method, method_args)
var_name = "@#{method.name}"
if instance_variable_defined?(var_name)
instance_variable_get(var_name)
else
result = method.call(*method_args)
instance_variable_set(var_name, result)
result
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment