memoizable 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # memoizable
1
+ # Memoizable
2
2
 
3
3
  Memoize method return values
4
4
 
@@ -18,6 +18,61 @@ Memoize method return values
18
18
 
19
19
  See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
20
20
 
21
+ ## Rationale
22
+
23
+ Memoization is an optimization that saves the return value of a method so it
24
+ doesn't need to be re-computed every time that method is called. For example,
25
+ perhaps you've written a method like this:
26
+
27
+ ```ruby
28
+ class Planet
29
+ # This is the equation for the area of a sphere. If it's true for a
30
+ # particular instance of a planet, then that planet is spherical.
31
+ def spherical?
32
+ 4 * Math::PI * radius ** 2 == area
33
+ end
34
+ end
35
+ ```
36
+
37
+ This code will re-compute whether a particular planet is spherical every time
38
+ the method is called. If the method is called more than once, it may be more
39
+ efficient to save the computed value in an instance variable, like so:
40
+
41
+ ```ruby
42
+ class Planet
43
+ def spherical?
44
+ @spherical ||= 4 * Math::PI * radius ** 2 == area
45
+ end
46
+ end
47
+ ```
48
+
49
+ One problem with this approach is that, if the return value is `false`, the
50
+ value will still be computed each time the method is called. It also becomes
51
+ unweildy for methods that grow to be longer than one line.
52
+
53
+ These problems can be solved by mixing-in the `Memoizable` module and memoizing
54
+ the method.
55
+
56
+ ```ruby
57
+ require 'memoizable'
58
+
59
+ class Planet
60
+ include Memoizable
61
+ def spherical?
62
+ 4 * Math::PI * radius ** 2 == area
63
+ end
64
+ memoize :spherical?
65
+ end
66
+ ```
67
+
68
+ ## Warning
69
+
70
+ The example above assumes that the radius and area of a planet will not change
71
+ over time. This seems like a reasonable assumption but such an assumption is
72
+ not safe in every domain. If it was possible for one of the attributes to
73
+ change between method calls, memoizing that value could produce the wrong
74
+ result. Please keep this in mind when considering which methods to memoize.
75
+
21
76
  ## Copyright
22
77
 
23
78
  Copyright © 2013 Dan Kubb, Erik Michaels-Ober. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'bundler'
2
4
  require 'rspec/core/rake_task'
3
5
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'thread_safe'
2
4
 
3
5
  require 'memoizable/instance_methods'
@@ -8,6 +10,7 @@ require 'memoizable/version'
8
10
 
9
11
  # Allow methods to be memoized
10
12
  module Memoizable
13
+ include InstanceMethods
11
14
 
12
15
  # Default freezer
13
16
  Freezer = lambda { |object| object.freeze }.freeze
@@ -21,10 +24,9 @@ module Memoizable
21
24
  #
22
25
  # @api private
23
26
  def self.included(descendant)
24
- descendant.module_eval do
25
- extend ModuleMethods
26
- include InstanceMethods
27
- end
27
+ super
28
+ descendant.extend(ModuleMethods)
28
29
  end
30
+ private_class_method :included
29
31
 
30
32
  end # Memoizable
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Memoizable
2
4
 
3
5
  # Methods mixed in to memoizable instances
@@ -40,7 +42,7 @@ module Memoizable
40
42
  #
41
43
  # @api private
42
44
  def memoized_method_cache
43
- @_memoized_method_cache ||= Memory.new(self.class.freezer)
45
+ @_memoized_method_cache ||= Memory.new
44
46
  end
45
47
 
46
48
  end # InstanceMethods
@@ -1,11 +1,18 @@
1
+ # encoding: utf-8
2
+
1
3
  module Memoizable
2
4
 
3
5
  # Storage for memoized methods
4
6
  class Memory
5
7
 
6
- def initialize(freezer)
7
- @memory = ThreadSafe::Cache.new
8
- @freezer = freezer
8
+ # Initialize the memory storage for memoized methods
9
+ #
10
+ # @return [undefined]
11
+ #
12
+ # @api private
13
+ def initialize
14
+ @memory = ThreadSafe::Cache.new
15
+ freeze
9
16
  end
10
17
 
11
18
  # Get the value from memory
@@ -17,7 +24,7 @@ module Memoizable
17
24
  # @api public
18
25
  def [](name)
19
26
  @memory.fetch(name) do
20
- raise NameError, "No method #{name.inspect} was memoized"
27
+ fail NameError, "No method #{name} is memoized"
21
28
  end
22
29
  end
23
30
 
@@ -30,10 +37,12 @@ module Memoizable
30
37
  #
31
38
  # @api public
32
39
  def []=(name, value)
33
- if @memory.key?(name)
34
- raise ArgumentError, "The method #{name} is already memoized"
40
+ memoized = true
41
+ @memory.compute_if_absent(name) do
42
+ memoized = false
43
+ value
35
44
  end
36
- @memory[name] = freeze_value(value)
45
+ fail ArgumentError, "The method #{name} is already memoized" if memoized
37
46
  end
38
47
 
39
48
  # Fetch the value from memory, or store it if it does not exist
@@ -44,8 +53,8 @@ module Memoizable
44
53
  # the value to memoize
45
54
  #
46
55
  # @api public
47
- def fetch(name)
48
- @memory.fetch(name) { self[name] = yield }
56
+ def fetch(name, &block)
57
+ @memory.compute_if_absent(name, &block)
49
58
  end
50
59
 
51
60
  # Set the memory
@@ -71,18 +80,5 @@ module Memoizable
71
80
  @memory.key?(name)
72
81
  end
73
82
 
74
- private
75
-
76
- # Freeze the value
77
- #
78
- # @param [Object] value
79
- #
80
- # @return [Object]
81
- #
82
- # @api private
83
- def freeze_value(value)
84
- @freezer.call(value)
85
- end
86
-
87
83
  end # Memory
88
84
  end # Memoizable
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Memoizable
2
4
 
3
5
  # Build the memoized method
@@ -14,11 +16,26 @@ module Memoizable
14
16
  #
15
17
  # @api private
16
18
  def initialize(descendant, method, arity)
17
- super("Cannot memoize #{descendant}##{method}, it's arity is #{arity}")
19
+ super("Cannot memoize #{descendant}##{method}, its arity is #{arity}")
18
20
  end
19
21
 
20
22
  end # InvalidArityError
21
23
 
24
+ # Raised when a block is passed to a memoized method
25
+ class BlockNotAllowedError < ArgumentError
26
+
27
+ # Initialize a block not allowed exception
28
+ #
29
+ # @param [Module] descendant
30
+ # @param [Symbol] method
31
+ #
32
+ # @api private
33
+ def initialize(descendant, method)
34
+ super("Cannot pass a block to #{descendant}##{method}, it is memoized")
35
+ end
36
+
37
+ end # BlockNotAllowedError
38
+
22
39
  # The original method before memoization
23
40
  #
24
41
  # @return [UnboundMethod]
@@ -30,16 +47,18 @@ module Memoizable
30
47
  #
31
48
  # @param [Module] descendant
32
49
  # @param [Symbol] method_name
50
+ # @param [#call] freezer
33
51
  #
34
52
  # @return [undefined]
35
53
  #
36
54
  # @api private
37
- def initialize(descendant, method_name)
55
+ def initialize(descendant, method_name, freezer)
38
56
  @descendant = descendant
39
57
  @method_name = method_name
58
+ @freezer = freezer
40
59
  @original_visibility = visibility
41
60
  @original_method = @descendant.instance_method(@method_name)
42
- assert_zero_arity
61
+ assert_arity(@original_method.arity)
43
62
  end
44
63
 
45
64
  # Build a new memoized method
@@ -61,15 +80,16 @@ module Memoizable
61
80
 
62
81
  # Assert the method arity is zero
63
82
  #
83
+ # @param [Integer] arity
84
+ #
64
85
  # @return [undefined]
65
86
  #
66
87
  # @raise [InvalidArityError]
67
88
  #
68
89
  # @api private
69
- def assert_zero_arity
70
- arity = @original_method.arity
90
+ def assert_arity(arity)
71
91
  if arity.nonzero?
72
- raise InvalidArityError.new(@descendant, @method_name, arity)
92
+ fail InvalidArityError.new(@descendant, @method_name, arity)
73
93
  end
74
94
  end
75
95
 
@@ -88,9 +108,12 @@ module Memoizable
88
108
  #
89
109
  # @api private
90
110
  def create_memoized_method
91
- descendant_exec(@method_name, @original_method) do |name, method|
92
- define_method(name) do ||
93
- memoized_method_cache.fetch(name, &method.bind(self))
111
+ descendant_exec(@method_name, @original_method, @freezer) do |name, method, freezer|
112
+ define_method(name) do |&block|
113
+ fail BlockNotAllowedError.new(self.class, name) if block
114
+ memoized_method_cache.fetch(name) do
115
+ freezer.call(method.bind(self).call)
116
+ end
94
117
  end
95
118
  end
96
119
  end
@@ -101,9 +124,7 @@ module Memoizable
101
124
  #
102
125
  # @api private
103
126
  def set_method_visibility
104
- descendant_exec(@method_name, @original_visibility) do |name, visibility|
105
- send(visibility, name)
106
- end
127
+ @descendant.send(@original_visibility, @method_name)
107
128
  end
108
129
 
109
130
  # Get the visibility of the original method
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Memoizable
2
4
 
3
5
  # Methods mixed in to memoizable singleton classes
@@ -8,7 +10,6 @@ module Memoizable
8
10
  # @return [#call]
9
11
  #
10
12
  # @api private
11
- #
12
13
  def freezer
13
14
  Freezer
14
15
  end
@@ -82,18 +83,33 @@ module Memoizable
82
83
 
83
84
  private
84
85
 
86
+ # Hook called when module is included
87
+ #
88
+ # @param [Module] descendant
89
+ # the module including ModuleMethods
90
+ #
91
+ # @return [self]
92
+ #
93
+ # @api private
94
+ def included(descendant)
95
+ super
96
+ descendant.module_eval { include Memoizable }
97
+ end
98
+
85
99
  # Memoize the named method
86
100
  #
87
101
  # @param [Symbol] method_name
88
102
  # a method name to memoize
89
- # @param [#call] freezer
90
- # a freezer for memoized values
91
103
  #
92
104
  # @return [undefined]
93
105
  #
94
106
  # @api private
95
107
  def memoize_method(method_name)
96
- memoized_methods[method_name] = MethodBuilder.new(self, method_name).call
108
+ memoized_methods[method_name] = MethodBuilder.new(
109
+ self,
110
+ method_name,
111
+ freezer
112
+ ).call
97
113
  end
98
114
 
99
115
  # Return method builder registry
@@ -101,9 +117,8 @@ module Memoizable
101
117
  # @return [Hash<Symbol, MethodBuilder>]
102
118
  #
103
119
  # @api private
104
- #
105
120
  def memoized_methods
106
- @_memoized_methods ||= Memory.new(freezer)
121
+ @_memoized_methods ||= Memory.new
107
122
  end
108
123
 
109
124
  end # ModuleMethods
@@ -1,6 +1,8 @@
1
+ # encoding: utf-8
2
+
1
3
  module Memoizable
2
4
 
3
5
  # Gem version
4
- VERSION = '0.2.0'.freeze
6
+ VERSION = '0.3.0'.freeze
5
7
 
6
8
  end # Memoizable
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require File.expand_path('../lib/memoizable/version', __FILE__)
2
4
 
3
5
  Gem::Specification.new do |gem|
@@ -12,10 +14,9 @@ Gem::Specification.new do |gem|
12
14
 
13
15
  gem.require_paths = %w[lib]
14
16
  gem.files = %w[CONTRIBUTING.md LICENSE.md README.md Rakefile memoizable.gemspec]
15
- gem.files += Dir.glob('lib/**/*.rb')
16
- gem.files += Dir.glob('spec/**/*')
17
- gem.test_files = Dir.glob('spec/**/*')
18
- gem.extra_rdoc_files = %w[LICENSE.md README.md CONTRIBUTING.md]
17
+ gem.files += Dir.glob('{lib,spec}/**/*.rb')
18
+ gem.test_files = Dir.glob('spec/{unit,integration}/**/*.rb')
19
+ gem.extra_rdoc_files = Dir.glob('**/*.md')
19
20
 
20
21
  gem.add_runtime_dependency('thread_safe', '~> 0.1.3')
21
22
 
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples 'it calls super' do |method|
4
+ around do |example|
5
+ # Restore original method after each example
6
+ original = "original_#{method}"
7
+ superclass.class_eval do
8
+ alias_method original, method
9
+ example.call
10
+ undef_method method
11
+ alias_method method, original
12
+ end
13
+ end
14
+
15
+ it "delegates to the superclass ##{method} method" do
16
+ # This is the most succinct approach I could think of to test whether the
17
+ # superclass method is called. All of the built-in rspec helpers did not
18
+ # seem to work for this.
19
+ called = false
20
+ superclass.class_eval { define_method(method) { |_| called = true } }
21
+ expect { subject }.to change { called }.from(false).to(true)
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a command method' do
4
+ it 'returns self' do
5
+ should equal(object)
6
+ end
7
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'simplecov'
2
4
  require 'coveralls'
3
5
 
@@ -12,12 +14,17 @@ SimpleCov.start do
12
14
  add_filter 'spec'
13
15
  add_filter 'vendor'
14
16
 
15
- minimum_coverage 89.8
17
+ minimum_coverage 100
16
18
  end
17
19
 
18
20
  require 'memoizable'
19
21
  require 'rspec'
20
22
 
23
+ # Require spec support files and shared behavior
24
+ Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each do |file|
25
+ require file.chomp('.rb')
26
+ end
27
+
21
28
  RSpec.configure do |config|
22
29
  config.expect_with :rspec do |expect_with|
23
30
  expect_with.syntax = :expect
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Memoizable, '.included' do
6
+ subject { object.class_eval { include Memoizable } }
7
+
8
+ let(:object) { Class.new }
9
+ let(:superclass) { Module }
10
+
11
+ it_behaves_like 'it calls super', :included
12
+
13
+ it 'extends the descendant with module methods' do
14
+ subject
15
+ extended_modules = class << object; included_modules end
16
+ expect(extended_modules).to include(Memoizable::ModuleMethods)
17
+ end
18
+ end
@@ -1,4 +1,6 @@
1
- module MemoizableSpecs
1
+ # encoding: utf-8
2
+
3
+ module Fixture
2
4
  class Object
3
5
  include Memoizable
4
6
 
@@ -12,6 +14,13 @@ module MemoizableSpecs
12
14
  'test'
13
15
  end
14
16
 
17
+ def zero_arity
18
+ caller
19
+ end
20
+
21
+ def one_arity(arg)
22
+ end
23
+
15
24
  def public_method
16
25
  caller
17
26
  end
@@ -29,4 +38,4 @@ module MemoizableSpecs
29
38
  end
30
39
 
31
40
  end # class Object
32
- end # module MemoizableSpecs
41
+ end # module Fixture
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../../fixtures/classes', __FILE__)
5
+
6
+ describe Memoizable::InstanceMethods, '#freeze' do
7
+ subject { object.freeze }
8
+
9
+ let(:described_class) { Class.new(Fixture::Object) }
10
+
11
+ before do
12
+ described_class.memoize(:test)
13
+ end
14
+
15
+ let(:object) { described_class.allocate }
16
+
17
+ it_should_behave_like 'a command method'
18
+
19
+ it 'freezes the object' do
20
+ expect { subject }.to change(object, :frozen?).from(false).to(true)
21
+ end
22
+
23
+ it 'allows methods not yet called to be memoized' do
24
+ subject
25
+ expect(object.test).to be(object.test)
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../../fixtures/classes', __FILE__)
5
+
6
+ describe Memoizable::InstanceMethods, '#memoize' do
7
+ subject { object.memoize(method => value) }
8
+
9
+ let(:described_class) { Class.new(Fixture::Object) }
10
+ let(:object) { described_class.new }
11
+ let(:method) { :test }
12
+
13
+ before do
14
+ described_class.memoize(method)
15
+ end
16
+
17
+ context 'when the method is not memoized' do
18
+ let(:value) { String.new }
19
+
20
+ it 'sets the memoized value for the method to the value' do
21
+ subject
22
+ expect(object.send(method)).to be(value)
23
+ end
24
+
25
+ it_should_behave_like 'a command method'
26
+ end
27
+
28
+ context 'when the method is already memoized' do
29
+ let(:value) { double }
30
+ let(:original) { nil }
31
+
32
+ before do
33
+ object.memoize(method => original)
34
+ end
35
+
36
+ it 'raises an exception' do
37
+ expect { subject }.to raise_error(ArgumentError)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,93 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../../fixtures/classes', __FILE__)
5
+
6
+ describe Memoizable::MethodBuilder, '#call' do
7
+ subject { object.call }
8
+
9
+ let(:object) { described_class.new(descendant, method_name, freezer) }
10
+ let(:freezer) { lambda { |object| object.freeze } }
11
+ let(:instance) { descendant.new }
12
+
13
+ let(:descendant) do
14
+ Class.new do
15
+ include Memoizable
16
+
17
+ def public_method
18
+ __method__.to_s
19
+ end
20
+
21
+ def protected_method
22
+ __method__.to_s
23
+ end
24
+ protected :protected_method
25
+
26
+ def private_method
27
+ __method__.to_s
28
+ end
29
+ private :private_method
30
+ end
31
+ end
32
+
33
+ shared_examples_for 'Memoizable::MethodBuilder#call' do
34
+ it_should_behave_like 'a command method'
35
+
36
+ it 'creates a method that is memoized' do
37
+ subject
38
+ expect(instance.send(method_name)).to be(instance.send(method_name))
39
+ end
40
+
41
+ it 'creates a method that returns the expected value' do
42
+ subject
43
+ expect(instance.send(method_name)).to eql(method_name.to_s)
44
+ end
45
+
46
+ it 'creates a method that returns a frozen value' do
47
+ subject
48
+ expect(descendant.new.send(method_name)).to be_frozen
49
+ end
50
+
51
+ it 'creates a method that does not accept a block' do
52
+ subject
53
+ expect { descendant.new.send(method_name) {} }.to raise_error(
54
+ described_class::BlockNotAllowedError,
55
+ "Cannot pass a block to #{descendant}##{method_name}, it is memoized"
56
+ )
57
+ end
58
+ end
59
+
60
+ context 'public method' do
61
+ let(:method_name) { :public_method }
62
+
63
+ it_should_behave_like 'Memoizable::MethodBuilder#call'
64
+
65
+ it 'creates a public memoized method' do
66
+ subject
67
+ expect(descendant).to be_public_method_defined(method_name)
68
+ end
69
+ end
70
+
71
+ context 'protected method' do
72
+ let(:method_name) { :protected_method }
73
+
74
+ it_should_behave_like 'Memoizable::MethodBuilder#call'
75
+
76
+ it 'creates a protected memoized method' do
77
+ subject
78
+ expect(descendant).to be_protected_method_defined(method_name)
79
+ end
80
+
81
+ end
82
+
83
+ context 'private method' do
84
+ let(:method_name) { :private_method }
85
+
86
+ it_should_behave_like 'Memoizable::MethodBuilder#call'
87
+
88
+ it 'creates a private memoized method' do
89
+ subject
90
+ expect(descendant).to be_private_method_defined(method_name)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../../../fixtures/classes', __FILE__)
5
+
6
+ describe Memoizable::MethodBuilder, '.new' do
7
+ subject { described_class.new(descendant, method_name, freezer) }
8
+
9
+ let(:descendant) { Fixture::Object }
10
+ let(:freezer) { lambda { |object| object.freeze } }
11
+
12
+ context 'with a zero arity method' do
13
+ let(:method_name) { :zero_arity }
14
+
15
+ it { should be_instance_of(described_class) }
16
+
17
+ it 'sets the original method' do
18
+ # original method is not memoized
19
+ method = subject.original_method.bind(descendant.new)
20
+ expect(method.call).to_not be(method.call)
21
+ end
22
+ end
23
+
24
+ context 'with a one arity method' do
25
+ let(:method_name) { :one_arity }
26
+
27
+ it 'raises an exception' do
28
+ expect { subject }.to raise_error(
29
+ described_class::InvalidArityError,
30
+ 'Cannot memoize Fixture::Object#one_arity, its arity is 1'
31
+ )
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Memoizable::MethodBuilder, '#original_method' do
6
+ subject { object.original_method }
7
+
8
+ let(:object) { described_class.new(descendant, method_name, freezer) }
9
+ let(:method_name) { :foo }
10
+ let(:freezer) { lambda { |object| object.freeze } }
11
+
12
+ let(:descendant) do
13
+ Class.new do
14
+ def initialize
15
+ @foo = 0
16
+ end
17
+
18
+ def foo
19
+ @foo += 1
20
+ end
21
+ end
22
+ end
23
+
24
+ it { should be_instance_of(UnboundMethod) }
25
+
26
+ it 'returns the original method' do
27
+ # original method is not memoized
28
+ method = subject.bind(descendant.new)
29
+ expect(method.call).to_not be(method.call)
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Memoizable::ModuleMethods, '#included' do
6
+ subject { descendant.instance_exec(object) { |mod| include mod } }
7
+
8
+ let(:object) { Module.new.extend(described_class) }
9
+ let(:descendant) { Class.new }
10
+ let(:superclass) { Module }
11
+
12
+ before do
13
+ # Prevent Module.included from being called through inheritance
14
+ Memoizable.stub(:included)
15
+ end
16
+
17
+ it_behaves_like 'it calls super', :included
18
+
19
+ it 'includes Memoizable into the descendant' do
20
+ subject
21
+ expect(descendant.included_modules).to include(Memoizable)
22
+ end
23
+ end
@@ -1,5 +1,7 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
- require File.expand_path('../fixtures/classes', __FILE__)
4
+ require File.expand_path('../../fixtures/classes', __FILE__)
3
5
 
4
6
  shared_examples_for 'memoizes method' do
5
7
  it 'memoizes the instance method' do
@@ -8,13 +10,7 @@ shared_examples_for 'memoizes method' do
8
10
  expect(instance.send(method)).to be(instance.send(method))
9
11
  end
10
12
 
11
- it 'creates a method that returns a same value' do
12
- subject
13
- instance = object.new
14
- expect(instance.send(method)).to be(instance.send(method))
15
- end
16
-
17
- it 'creates a method with an arity of 0' do
13
+ it 'creates a zero arity method', :unless => RUBY_VERSION == '1.8.7' do
18
14
  subject
19
15
  expect(object.new.method(method).arity).to be_zero
20
16
  end
@@ -32,17 +28,11 @@ shared_examples_for 'memoizes method' do
32
28
  end
33
29
  end
34
30
 
35
- shared_examples_for 'a command method' do
36
- it 'returns self' do
37
- should equal(object)
38
- end
39
- end
40
-
41
31
  describe Memoizable::ModuleMethods, '#memoize' do
42
32
  subject { object.memoize(method) }
43
33
 
44
34
  let(:object) do
45
- stub_const 'TestClass', Class.new(MemoizableSpecs::Object) {
35
+ stub_const 'TestClass', Class.new(Fixture::Object) {
46
36
  def some_state
47
37
  Object.new
48
38
  end
@@ -55,7 +45,7 @@ describe Memoizable::ModuleMethods, '#memoize' do
55
45
  it 'should raise error' do
56
46
  expect { subject }.to raise_error(
57
47
  Memoizable::MethodBuilder::InvalidArityError,
58
- "Cannot memoize TestClass#required_arguments, it's arity is 1"
48
+ 'Cannot memoize TestClass#required_arguments, its arity is 1'
59
49
  )
60
50
  end
61
51
  end
@@ -66,7 +56,7 @@ describe Memoizable::ModuleMethods, '#memoize' do
66
56
  it 'should raise error' do
67
57
  expect { subject }.to raise_error(
68
58
  Memoizable::MethodBuilder::InvalidArityError,
69
- "Cannot memoize TestClass#optional_arguments, it's arity is -1"
59
+ 'Cannot memoize TestClass#optional_arguments, its arity is -1'
70
60
  )
71
61
  end
72
62
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Memoizable::ModuleMethods, '#memoized?' do
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Memoizable::ModuleMethods, '#unmemoized_instance_method' do
6
+ subject { object.unmemoized_instance_method(name) }
7
+
8
+ let(:object) do
9
+ Class.new do
10
+ include Memoizable
11
+
12
+ def initialize
13
+ @foo = 0
14
+ end
15
+
16
+ def foo
17
+ @foo += 1
18
+ end
19
+
20
+ memoize :foo
21
+ end
22
+ end
23
+
24
+ context 'when the method was memoized' do
25
+ let(:name) { :foo }
26
+
27
+ it { should be_instance_of(UnboundMethod) }
28
+
29
+ it 'returns the original method' do
30
+ # original method is not memoized
31
+ method = subject.bind(object.new)
32
+ expect(method.call).to_not be(method.call)
33
+ end
34
+ end
35
+
36
+ context 'when the method was not memoized' do
37
+ let(:name) { :bar }
38
+
39
+ it 'raises an exception' do
40
+ expect { subject }.to raise_error(NameError, 'No method bar is memoized')
41
+ end
42
+ end
43
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memoizable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-18 00:00:00.000000000 Z
12
+ date: 2013-12-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thread_safe
@@ -54,9 +54,9 @@ email: dan.kubb@gmail.com
54
54
  executables: []
55
55
  extensions: []
56
56
  extra_rdoc_files:
57
+ - CONTRIBUTING.md
57
58
  - LICENSE.md
58
59
  - README.md
59
- - CONTRIBUTING.md
60
60
  files:
61
61
  - CONTRIBUTING.md
62
62
  - LICENSE.md
@@ -69,10 +69,20 @@ files:
69
69
  - lib/memoizable/module_methods.rb
70
70
  - lib/memoizable/version.rb
71
71
  - lib/memoizable.rb
72
- - spec/fixtures/classes.rb
73
- - spec/memoize_spec.rb
74
- - spec/memoized_predicate_spec.rb
72
+ - spec/shared/call_super_shared_spec.rb
73
+ - spec/shared/command_method_behavior.rb
75
74
  - spec/spec_helper.rb
75
+ - spec/unit/memoizable/class_methods/included_spec.rb
76
+ - spec/unit/memoizable/fixtures/classes.rb
77
+ - spec/unit/memoizable/instance_methods/freeze_spec.rb
78
+ - spec/unit/memoizable/instance_methods/memoize_spec.rb
79
+ - spec/unit/memoizable/method_builder/call_spec.rb
80
+ - spec/unit/memoizable/method_builder/class_methods/new_spec.rb
81
+ - spec/unit/memoizable/method_builder/original_method_spec.rb
82
+ - spec/unit/memoizable/module_methods/included_spec.rb
83
+ - spec/unit/memoizable/module_methods/memoize_spec.rb
84
+ - spec/unit/memoizable/module_methods/memoized_predicate_spec.rb
85
+ - spec/unit/memoizable/module_methods/unmemoized_instance_method_spec.rb
76
86
  homepage: https://github.com/dkubb/memoizable
77
87
  licenses:
78
88
  - MIT
@@ -99,8 +109,15 @@ signing_key:
99
109
  specification_version: 3
100
110
  summary: Memoize method return values
101
111
  test_files:
102
- - spec/fixtures/classes.rb
103
- - spec/memoize_spec.rb
104
- - spec/memoized_predicate_spec.rb
105
- - spec/spec_helper.rb
112
+ - spec/unit/memoizable/class_methods/included_spec.rb
113
+ - spec/unit/memoizable/fixtures/classes.rb
114
+ - spec/unit/memoizable/instance_methods/freeze_spec.rb
115
+ - spec/unit/memoizable/instance_methods/memoize_spec.rb
116
+ - spec/unit/memoizable/method_builder/call_spec.rb
117
+ - spec/unit/memoizable/method_builder/class_methods/new_spec.rb
118
+ - spec/unit/memoizable/method_builder/original_method_spec.rb
119
+ - spec/unit/memoizable/module_methods/included_spec.rb
120
+ - spec/unit/memoizable/module_methods/memoize_spec.rb
121
+ - spec/unit/memoizable/module_methods/memoized_predicate_spec.rb
122
+ - spec/unit/memoizable/module_methods/unmemoized_instance_method_spec.rb
106
123
  has_rdoc: