memoizable 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: