From 30cbebb9f26894c62585a0a45c2fc08634558390 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Apr 2018 18:27:42 +0300 Subject: [PATCH] Change implementation from `prepend` to `UnboundMethod` `prepend` pollutes `Module#ancestors`: ```ruby module Foo end class Bar prepend Foo end class Baz < Bar prepend Foo end Baz.ancestors # => [Foo, Baz, Foo, Bar, Object, Kernel, BasicObject] ``` --- README.md | 2 +- lib/memery.rb | 40 ++++++++++++---------------------------- spec/memery_spec.rb | 4 +++- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index e2f1c20..f77a6be 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ B.call # => 42 ``` ## Difference with other gems -Memery is very similar to [Memoist](https://github.com/matthewrudy/memoist). The difference is that it doesn't override methods, instead it uses Ruby 2 `Module.prepend` feature. This approach is cleaner and it allows subclasses' methods to work properly: if you redefine a memoized method in a subclass, it's not memoized by default, but you can memoize it normally (without using awkward `identifier: ` argument) and it will just work: +Memery is very similar to [Memoist](https://github.com/matthewrudy/memoist). The difference is that it doesn't create additional prefixed methods, instead it uses `UnboundMethod` as local variable. This approach is cleaner and it allows subclasses' methods to work properly: if you redefine a memoized method in a subclass, it's not memoized by default, but you can memoize it normally (without using awkward `identifier: ` argument) and it will just work: ```ruby class A diff --git a/lib/memery.rb b/lib/memery.rb index 71cbd49..089c2dc 100644 --- a/lib/memery.rb +++ b/lib/memery.rb @@ -21,41 +21,25 @@ def self.method_visibility(klass, method_name) module ClassMethods def memoize(method_name) - prepend_memery_module! - define_memoized_method!(method_name) - end - - private - - def prepend_memery_module! - return if defined?(@_memery_module) - @_memery_module = Module.new - prepend @_memery_module - end - - def define_memoized_method!(method_name) - mod_id = @_memery_module.object_id visibility = Memery.method_visibility(self, method_name) - raise ArgumentError, "Method #{method_name} is not defined on #{self}" unless visibility + old_method = instance_method(method_name) - @_memery_module.module_eval do - define_method(method_name) do |*args, &block| - return super(*args, &block) if block + define_method(method_name) do |*args, &block| + return old_method.bind(self).call(*args, &block) if block - @_memery_memoized_values ||= {} + @_memery_memoized_values ||= {} - key = [method_name, mod_id].join("_").to_sym - store = @_memery_memoized_values[key] ||= {} + key = :"#{method_name}_#{old_method.object_id}" + store = @_memery_memoized_values[key] ||= {} - if store.key?(args) - store[args] - else - store[args] = super(*args) - end + if store.key?(args) + store[args] + else + store[args] = old_method.bind(self).call(*args) end - - send(visibility, method_name) end + + send(visibility, method_name) end end diff --git a/spec/memery_spec.rb b/spec/memery_spec.rb index 98455ed..902c8d1 100644 --- a/spec/memery_spec.rb +++ b/spec/memery_spec.rb @@ -163,7 +163,9 @@ class << self end specify do - expect { klass }.to raise_error(ArgumentError, /Method foo is not defined/) + expect { klass }.to raise_error( + NameError, /undefined method `foo' for class `#'/ + ) end end end