memery 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91c69224d38dc7b680a40c666d20e36ba65383235e70cec68b8b54b5e42f61ef
4
- data.tar.gz: 8340fb04f65f8f57a5ee177d6affcb3183d892f65fae04153fb5c2e74d3ee2cf
3
+ metadata.gz: b422cc5336406f390272a02aea253b991470519a786844cb72b4cc0c05ef3b56
4
+ data.tar.gz: 12c2329a8539305d85dcb99b30604e55aa04b0f75153fc6a173ae73875f423c8
5
5
  SHA512:
6
- metadata.gz: 1acd168dd8d4bc989ac51ca5c37eb1229cf86c8378aac3ef0fef716e4ba81bb50e85975712ac4ea1997ded3c58db43647ffe342f186d6f3a12d7bfc87e646293
7
- data.tar.gz: 219204fb9a75cd005dcd3102740ba81a67302b5b15609bfceb4c0fe0484698c5a0f1467ce737f7d30067ad85a7b2d9be68df4d489d56d11d6f6789536c7b0533
6
+ metadata.gz: 94b69a032837511e10b368ab6db8212bf6b48c8b835c5f76732484b05dea8910af17712797a42fa334ee0529973f8c61d69ea052c1949619411bc1ae7c0897fa
7
+ data.tar.gz: fd17f734226f87487223034bf07afae259ed54ca5b216bfc11322b38adcdf1dd3898bf0f238ab8dcb846492c7f93baa8e51d909584bd1ec1e8b07774d4cc3693
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --format documentation
2
2
  --color
3
3
  --require spec_helper
4
+ --warnings
@@ -11,15 +11,7 @@ rvm:
11
11
 
12
12
  before_install: gem install bundler
13
13
 
14
- env: SUITE="rspec"
15
-
16
- script: bundle exec $SUITE
17
-
18
14
  matrix:
19
15
  fast_finish: true
20
- # Only run RuboCop on the latest Ruby
21
- include:
22
- - rvm: 2.6
23
- env: SUITE="rubocop"
24
16
  allow_failures:
25
17
  - rvm: ruby-head
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.2.0] - 2019-10-19
4
+ ### Added
5
+ - Add `:ttl` option for `memoize` method ([@AlexWayfer]) [#11]
6
+ - Add benchmark script ([@AlexWayfer]) [#14]
7
+ - Add `.memoized?` method ([@AlexWayfer]) [#17]
8
+
3
9
  ## [1.1.0] - 2019-08-05
4
10
  ### Fixed
5
11
  - Optimize speed and memory for cached values returns. ([@AlexWayfer]) [#10]
@@ -20,7 +26,8 @@
20
26
  [0.6.0]: https://github.com/tycooon/memery/compare/v0.5.0...v0.6.0
21
27
  [1.0.0]: https://github.com/tycooon/memery/compare/v0.6.0...v1.0.0
22
28
  [1.1.0]: https://github.com/tycooon/memery/compare/v1.0.0...v1.1.0
23
- [Unreleased]: https://github.com/tycooon/memery/compare/v1.1.0...HEAD
29
+ [1.2.0]: https://github.com/tycooon/memery/compare/v1.1.0...v1.2.0
30
+ [Unreleased]: https://github.com/tycooon/memery/compare/v1.2.0...HEAD
24
31
 
25
32
  [@tycooon]: https://github.com/tycooon
26
33
  [@AlexWayfer]: https://github.com/AlexWayfer
@@ -28,3 +35,6 @@
28
35
  [#3]: https://github.com/tycooon/memery/pull/3
29
36
  [#7]: https://github.com/tycooon/memery/pull/7
30
37
  [#10]: https://github.com/tycooon/memery/pull/10
38
+ [#11]: https://github.com/tycooon/memery/pull/11
39
+ [#14]: https://github.com/tycooon/memery/pull/14
40
+ [#17]: https://github.com/tycooon/memery/pull/17
data/Gemfile CHANGED
@@ -2,5 +2,3 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
  gemspec
5
-
6
- gem "rubocop-config-umbrellio", github: "umbrellio/code-style"
data/README.md CHANGED
@@ -62,6 +62,25 @@ a.call { 1 } # => 42
62
62
  # Will print because passing a block disables memoization
63
63
  ```
64
64
 
65
+ Methods with arguments are supported and the memoization will be done based on arguments using an internal hash. So this will work as expected:
66
+
67
+ ```ruby
68
+ class A
69
+ include Memery
70
+
71
+ memoize def call(arg1, arg2)
72
+ puts "calculating"
73
+ arg1 + arg2
74
+ end
75
+ end
76
+
77
+ a = A.new
78
+ a.call(1, 5) # => 6
79
+ a.call(2, 15) # => 17
80
+ a.call(1, 5) # => 6
81
+ # Text will be printed only twice, once per unique argument list.
82
+ ```
83
+
65
84
  For class methods:
66
85
 
67
86
  ```ruby
@@ -117,6 +136,60 @@ a.call # => 42
117
136
  # with `true` result of condition block.
118
137
  ```
119
138
 
139
+ For memoization with time-to-live:
140
+
141
+ ```ruby
142
+ class A
143
+ include Memery
144
+
145
+ def call
146
+ puts "calculating"
147
+ 42
148
+ end
149
+
150
+ memoize :call, ttl: 3 # seconds
151
+ end
152
+
153
+ a = A.new
154
+ a.call # => 42
155
+ # calculating
156
+ a.call # => 42
157
+ a.call # => 42
158
+ # Text will be printed again only after 3 seconds of time-to-live.
159
+ # 3 seconds later...
160
+ a.call # => 42
161
+ # calculating
162
+ a.call # => 42
163
+ a.call # => 42
164
+ # another 3 seconds later...
165
+ a.call # => 42
166
+ # calculating
167
+ a.call # => 42
168
+ a.call # => 42
169
+ ```
170
+
171
+ Check if method is memoized:
172
+
173
+ ```ruby
174
+ class A
175
+ include Memery
176
+
177
+ memoize def call
178
+ puts "calculating"
179
+ 42
180
+ end
181
+
182
+ def execute
183
+ puts "non-memoized"
184
+ end
185
+ end
186
+
187
+ a = A.new
188
+
189
+ a.memoized?(:call) # => true
190
+ a.memoized?(:execute) # => false
191
+ ```
192
+
120
193
  ## Difference with other gems
121
194
  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 (for example you are able to inspect the original method body using `method(:x).super_method.source`) 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:
122
195
 
data/Rakefile CHANGED
@@ -2,7 +2,13 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
5
6
 
6
7
  RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new(:lint)
7
9
 
8
- task default: :spec
10
+ task default: %i[lint spec]
11
+
12
+ task :benchmark do
13
+ require_relative "./benchmark"
14
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ Bundler.setup
5
+
6
+ require "benchmark"
7
+ require "benchmark/ips"
8
+ require "benchmark/memory"
9
+
10
+ puts "```ruby"
11
+ puts File.read(__FILE__)
12
+ puts "```"
13
+ puts
14
+ puts "### Output"
15
+ puts
16
+ puts "```"
17
+
18
+ require_relative "lib/memery"
19
+
20
+ class Foo
21
+ class << self
22
+ include Memery
23
+
24
+ def base_find(char)
25
+ ("a".."k").find { |letter| letter == char }
26
+ end
27
+
28
+ memoize def find_new(char)
29
+ base_find(char)
30
+ end
31
+ end
32
+ end
33
+
34
+ def test_memery
35
+ Foo.find_new("d")
36
+ end
37
+
38
+ Benchmark.ips do |x|
39
+ x.report("test_memery") { test_memery }
40
+ end
41
+
42
+ Benchmark.memory do |x|
43
+ x.report("test_memery") { 100.times { test_memery } }
44
+ end
45
+
46
+ puts "```"
@@ -3,26 +3,39 @@
3
3
  require "memery/version"
4
4
 
5
5
  module Memery
6
- def self.included(base)
7
- base.extend(ClassMethods)
8
- base.include(InstanceMethods)
9
- end
6
+ class << self
7
+ def included(base)
8
+ base.extend(ClassMethods)
9
+ base.include(InstanceMethods)
10
+ end
11
+
12
+ def method_visibility(klass, method_name)
13
+ case
14
+ when klass.private_method_defined?(method_name)
15
+ :private
16
+ when klass.protected_method_defined?(method_name)
17
+ :protected
18
+ when klass.public_method_defined?(method_name)
19
+ :public
20
+ end
21
+ end
10
22
 
11
- def self.method_visibility(klass, method_name)
12
- case
13
- when klass.private_method_defined?(method_name)
14
- :private
15
- when klass.protected_method_defined?(method_name)
16
- :protected
17
- when klass.public_method_defined?(method_name)
18
- :public
23
+ def monotonic_clock
24
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
25
  end
20
26
  end
21
27
 
22
28
  module ClassMethods
23
- def memoize(method_name, condition: nil)
29
+ def memoize(method_name, condition: nil, ttl: nil)
24
30
  prepend_memery_module!
25
- define_memoized_method!(method_name, condition: condition)
31
+ define_memoized_method!(method_name, condition: condition, ttl: ttl)
32
+ end
33
+
34
+ def memoized?(method_name)
35
+ return false unless defined?(@_memery_module)
36
+
37
+ @_memery_module.method_defined?(method_name) ||
38
+ @_memery_module.private_method_defined?(method_name)
26
39
  end
27
40
 
28
41
  private
@@ -33,7 +46,7 @@ module Memery
33
46
  prepend @_memery_module
34
47
  end
35
48
 
36
- def define_memoized_method!(method_name, condition: nil)
49
+ def define_memoized_method!(method_name, condition: nil, ttl: nil)
37
50
  mod_id = @_memery_module.object_id
38
51
  visibility = Memery.method_visibility(self, method_name)
39
52
  raise ArgumentError, "Method #{method_name} is not defined on #{self}" unless visibility
@@ -46,13 +59,15 @@ module Memery
46
59
 
47
60
  key = "#{method_name}_#{mod_id}"
48
61
 
49
- store = @_memery_memoized_values&.[](key)
62
+ store = (@_memery_memoized_values ||= {})[key] ||= {}
50
63
 
51
- return store[args] if store&.key?(args)
64
+ if store.key?(args) && (ttl.nil? || Memery.monotonic_clock <= store[args][:time] + ttl)
65
+ return store[args][:result]
66
+ end
52
67
 
53
- @_memery_memoized_values ||= {}
54
- @_memery_memoized_values[key] ||= {}
55
- @_memery_memoized_values[key][args] = super(*args)
68
+ result = super(*args)
69
+ @_memery_memoized_values[key][args] = { result: result, time: Memery.monotonic_clock }
70
+ result
56
71
  end
57
72
 
58
73
  send(visibility, method_name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Memery
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -20,10 +20,13 @@ Gem::Specification.new do |spec|
20
20
  end
21
21
  spec.require_paths = ["lib"]
22
22
 
23
+ spec.add_development_dependency "benchmark-ips"
24
+ spec.add_development_dependency "benchmark-memory"
23
25
  spec.add_development_dependency "bundler"
24
26
  spec.add_development_dependency "coveralls"
25
27
  spec.add_development_dependency "pry"
26
28
  spec.add_development_dependency "rake"
27
29
  spec.add_development_dependency "rspec"
30
+ spec.add_development_dependency "rubocop-config-umbrellio"
28
31
  spec.add_development_dependency "simplecov"
29
32
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memery
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Smirnov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-05 00:00:00.000000000 Z
11
+ date: 2019-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: benchmark-ips
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: benchmark-memory
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +108,20 @@ dependencies:
80
108
  - - ">="
81
109
  - !ruby/object:Gem::Version
82
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-config-umbrellio
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  - !ruby/object:Gem::Dependency
84
126
  name: simplecov
85
127
  requirement: !ruby/object:Gem::Requirement
@@ -111,6 +153,7 @@ files:
111
153
  - LICENSE.txt
112
154
  - README.md
113
155
  - Rakefile
156
+ - benchmark.rb
114
157
  - lib/memery.rb
115
158
  - lib/memery/version.rb
116
159
  - memery.gemspec