alt_memery 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 34a090f8bb82efcf602abd829780cf4ad2999a5a4ef2832c739438e05ffde120
4
+ data.tar.gz: 205faeba1520ae7fc16877b00260450db78dbf425904e2140bdc8733dac8e1db
5
+ SHA512:
6
+ metadata.gz: 2d5d4380c23de08c5501e2875debfc1eb64db87e2df49c07dca6a1f71a1fd702a205284971a2266a176bcea6de65be0897bfd35f0a13e43ada5996d6cd2e48d3
7
+ data.tar.gz: 6f0f56fc4abb3277d49235a086933228e86ad30917978c6ac994732cb511bfb9537dab641a7b9baf9085f250decd803cc51e234aa089a4d76ceb719d070466f1
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ ## master (unreleased)
4
+
5
+ ## 2.0.0 (2020-07-09)
6
+
7
+ * Rename gem.
8
+ * Rewrite implementation from `prepend Module` to `UnboundMethod`.
9
+ See discussion here: <https://github.com/tycooon/memery/pull/1>.
10
+ * Delete `Gemfile.lock` and lock dependencies versions in `gemspec`.
11
+ * Update dependencies.
12
+ Unmaintained `coveralls` replaced with `codecov`.
13
+ `bundler` dependency dropped with `Rakefile`.
14
+ * Replace `require` with `require_relative`.
15
+ * Replace Umbrella styles with standard RuboCop.
16
+ * Improve README.
17
+ * Replace Travis CI with Cirrus CI.
18
+ You can see discussion here: <https://github.com/tycooon/memery/issues/28>.
19
+ * Add `remark` CI task for linting Markdown.
20
+ * Delete `benchmark.rb`.
21
+ You can find improving example here: <https://gist.github.com/AlexWayfer/37ebb8b9f3429650b86fb4cea7ae3693>.
22
+
23
+ ### Unreleased Memery
24
+
25
+ * Fix compatibility with `ActiveSupport::Concern`.
26
+
27
+ ## 1.3.0 (2020-02-10)
28
+
29
+ * Allow memoization after including module with Memery.
30
+ * Make `memoize` return the method name to allow chaining.
31
+ * Fix warnings in Ruby 2.7.
32
+
33
+ ## 1.2.0 (2019-10-19)
34
+
35
+ * Add `:ttl` option for `memoize` method.
36
+ * Add benchmark script.
37
+ * Add `.memoized?` method.
38
+
39
+ ## 1.1.0 (2019-08-05)
40
+
41
+ * Optimize speed and memory for cached values returns.
42
+
43
+ ## 1.0.0 (2018-08-31)
44
+
45
+ * Add `:condition` option for `.memoize` method.
46
+
47
+ ## 0.6.0 (2018-04-20)
48
+
49
+ * Add example of class methods memoization into README.
50
+ * Memery raises `ArgumentError` if method is not defined when you call `memoize`.
51
+
52
+ ## 0.5.0 (2017-06-12)
53
+
54
+ * Initial public version.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Yuri Smirnov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,300 @@
1
+ # Alt Memery
2
+
3
+ Alt Memery allows to memoize methods return values.
4
+
5
+ The native simplest memoization in Ruby looks like this:
6
+
7
+ ```ruby
8
+ def user
9
+ @user ||= User.find(some_id)
10
+ end
11
+ ```
12
+
13
+ But if you want to memoize falsy values — you have to use `defined?` instead of `||=`:
14
+
15
+ ```ruby
16
+ def user
17
+ return @user if defined?(@user)
18
+
19
+ @user = User.find(some_id)
20
+ end
21
+ ```
22
+
23
+ But with memoization gems, like this one, you can simplify your code:
24
+
25
+ ```ruby
26
+ memoize def user
27
+ User.find(some_id)
28
+ end
29
+ ```
30
+
31
+ Also, you're getting additional features, like conditions of memoization, time-to-live,
32
+ handy memoized values flushing, etc.
33
+
34
+ ## Alt?
35
+
36
+ It's a fork of [Memery gem](https://github.com/tycooon/memery).
37
+ Original Memery uses `prepend Module.new` with memoized methods, not touching original ones.
38
+ This approach has advantages, but also has problems, see discussion here:
39
+ <https://github.com/tycooon/memery/pull/1>
40
+
41
+ So, this fork uses `UnboundMethod` as I've suggested in the PR above.
42
+
43
+ ## Difference with other gems
44
+
45
+ Such gems like [Memoist](https://github.com/matthewrudy/memoist) override methods.
46
+ So, if you want to memoize a method in a child class with the same named memoized method
47
+ in a parent class — you have to use something like awkward `identifier: ` argument.
48
+ This gem allows you to just memoize methods when you want to.
49
+
50
+ Note how both method's return values are cached separately and don't interfere with each other.
51
+
52
+ The other key difference is that it doesn't change method's arguments
53
+ (no extra param like `reload`). If you need to get unmemoize result of method —
54
+ just call the `#clear_memery_cache!` method with needed memoized method names:
55
+
56
+ ```ruby
57
+ a.clear_memery_cache! :foo, :bar
58
+ ```
59
+
60
+ Without arguments, `#clear_memery_cache!` will clear the whole instance's cache.
61
+
62
+ ## Installation
63
+
64
+ Add this line to your application's Gemfile:
65
+
66
+ ```ruby
67
+ gem 'alt_memery'
68
+ ```
69
+
70
+ And then execute:
71
+
72
+ ```shell
73
+ bundle install
74
+ ```
75
+
76
+ Or install it yourself as:
77
+
78
+ ```shell
79
+ gem install alt_memery
80
+ ```
81
+
82
+ ## Usage
83
+
84
+ ```ruby
85
+ class A
86
+ include Memery
87
+
88
+ memoize def call
89
+ puts "calculating"
90
+ 42
91
+ end
92
+
93
+ # or:
94
+ # def call
95
+ # ...
96
+ # end
97
+ # memoize :call
98
+ end
99
+
100
+ a = A.new
101
+ a.call # => 42
102
+ a.call # => 42
103
+ a.call # => 42
104
+ # Text will be printed only once.
105
+
106
+ a.call { 1 } # => 42
107
+ # Will print because passing a block disables memoization
108
+ ```
109
+
110
+ Methods with arguments are supported and the memoization will be done based on arguments
111
+ using an internal hash. So this will work as expected:
112
+
113
+ ```ruby
114
+ class A
115
+ include Memery
116
+
117
+ memoize def call(arg1, arg2)
118
+ puts "calculating"
119
+ arg1 + arg2
120
+ end
121
+ end
122
+
123
+ a = A.new
124
+ a.call(1, 5) # => 6
125
+ a.call(2, 15) # => 17
126
+ a.call(1, 5) # => 6
127
+ # Text will be printed only twice, once per unique argument list.
128
+ ```
129
+
130
+ For class methods:
131
+
132
+ ```ruby
133
+ class B
134
+ class << self
135
+ include Memery
136
+
137
+ memoize def call
138
+ puts "calculating"
139
+ 42
140
+ end
141
+ end
142
+ end
143
+
144
+ B.call # => 42
145
+ B.call # => 42
146
+ B.call # => 42
147
+ # Text will be printed only once.
148
+ ```
149
+
150
+ For conditional memoization:
151
+
152
+ ```ruby
153
+ class A
154
+ include Memery
155
+
156
+ attr_accessor :environment
157
+
158
+ def call
159
+ puts "calculating"
160
+ 42
161
+ end
162
+
163
+ memoize :call, condition: -> { environment == 'production' }
164
+ end
165
+
166
+ a = A.new
167
+ a.environment = 'development'
168
+ a.call # => 42
169
+ # calculating
170
+ a.call # => 42
171
+ # calculating
172
+ a.call # => 42
173
+ # calculating
174
+ # Text will be printed every time because result of condition block is `false`.
175
+
176
+ a.environment = 'production'
177
+ a.call # => 42
178
+ # calculating
179
+ a.call # => 42
180
+ a.call # => 42
181
+ # Text will be printed only once because there is memoization
182
+ # with `true` result of condition block.
183
+ ```
184
+
185
+ For memoization with time-to-live:
186
+
187
+ ```ruby
188
+ class A
189
+ include Memery
190
+
191
+ def call
192
+ puts "calculating"
193
+ 42
194
+ end
195
+
196
+ memoize :call, ttl: 3 # seconds
197
+ end
198
+
199
+ a = A.new
200
+ a.call # => 42
201
+ # calculating
202
+ a.call # => 42
203
+ a.call # => 42
204
+ # Text will be printed again only after 3 seconds of time-to-live.
205
+ # 3 seconds later...
206
+ a.call # => 42
207
+ # calculating
208
+ a.call # => 42
209
+ a.call # => 42
210
+ # another 3 seconds later...
211
+ a.call # => 42
212
+ # calculating
213
+ a.call # => 42
214
+ a.call # => 42
215
+ ```
216
+
217
+ Check if method is memoized:
218
+
219
+ ```ruby
220
+ class A
221
+ include Memery
222
+
223
+ memoize def call
224
+ puts "calculating"
225
+ 42
226
+ end
227
+
228
+ def execute
229
+ puts "non-memoized"
230
+ end
231
+ end
232
+
233
+ a = A.new
234
+
235
+ a.memoized?(:call) # => true
236
+ a.memoized?(:execute) # => false
237
+ ```
238
+
239
+ If you want to see memoized method source:
240
+
241
+ ```ruby
242
+ class A
243
+ include Memery
244
+
245
+ memoize def call
246
+ puts "calculating"
247
+ 42
248
+ end
249
+ end
250
+
251
+ # This will print memoization logic, don't use it.
252
+ # The same for `show-source A#call` in `pry`.
253
+ puts A.instance_method(:call).source
254
+
255
+ # And this will work correctly.
256
+ puts A.memoized_methods[:call].source
257
+ ```
258
+
259
+ But if a memoized method has been defined in an included module — it'd be a bit harder:
260
+
261
+ ```ruby
262
+ module A
263
+ include Memery
264
+
265
+ memoize def foo
266
+ 'source'
267
+ end
268
+ end
269
+
270
+ module B
271
+ include Memery
272
+ include A
273
+
274
+ memoize def foo
275
+ "Get this #{super}!"
276
+ end
277
+ end
278
+
279
+ class C
280
+ include B
281
+ end
282
+
283
+ puts C.instance_method(:foo).owner.memoized_methods[:foo].source
284
+ # memoize def foo
285
+ # "Get this #{super}!"
286
+ # end
287
+
288
+ puts C.instance_method(:foo).super_method.owner.memoized_methods[:foo].source
289
+ # memoize def foo
290
+ # 'source'
291
+ # end
292
+ ```
293
+
294
+ ## Contributing
295
+
296
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/AlexWayfer/alt_memery>.
297
+
298
+ ## License
299
+
300
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'module_methods'
4
+ require 'ruby2_keywords'
5
+
6
+ require_relative 'memery/version'
7
+
8
+ ## Module for memoization
9
+ module Memery
10
+ extend ::ModuleMethods::Extension
11
+
12
+ class << self
13
+ def monotonic_clock
14
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
15
+ end
16
+
17
+ def method_visibility(klass, method_name)
18
+ if klass.private_method_defined?(method_name)
19
+ :private
20
+ elsif klass.protected_method_defined?(method_name)
21
+ :protected
22
+ elsif klass.public_method_defined?(method_name)
23
+ :public
24
+ else
25
+ raise ArgumentError, "Method #{method_name} is not defined on #{klass}"
26
+ end
27
+ end
28
+ end
29
+
30
+ ## Module for class methods
31
+ module ClassMethods
32
+ def memoized_methods
33
+ @memoized_methods ||= {}
34
+ end
35
+
36
+ ## TODO: Resolve this
37
+ # rubocop:disable Metrics/MethodLength
38
+ # rubocop:disable Metrics/AbcSize
39
+ # rubocop:disable Metrics/CyclomaticComplexity
40
+ def memoize(method_name, condition: nil, ttl: nil)
41
+ original_visibility = Memery.method_visibility(self, method_name)
42
+
43
+ original_method = memoized_methods[method_name] = instance_method(method_name)
44
+
45
+ undef_method method_name
46
+
47
+ define_method method_name do |*args, &block|
48
+ if block || (condition && !instance_exec(&condition))
49
+ return original_method.bind(self).call(*args, &block)
50
+ end
51
+
52
+ method_object_id = original_method.object_id
53
+
54
+ store =
55
+ ((@_memery_memoized_values ||= {})[method_name] ||= {})[method_object_id] ||= {}
56
+
57
+ if store.key?(args) && (ttl.nil? || Memery.monotonic_clock <= store[args][:time] + ttl)
58
+ return store[args][:result]
59
+ end
60
+
61
+ result = original_method.bind(self).call(*args)
62
+ @_memery_memoized_values[method_name][method_object_id][args] =
63
+ { result: result, time: Memery.monotonic_clock }
64
+ result
65
+ end
66
+
67
+ ruby2_keywords method_name
68
+
69
+ send original_visibility, method_name
70
+
71
+ method_name
72
+ end
73
+ # rubocop:enable Metrics/MethodLength
74
+ # rubocop:enable Metrics/AbcSize
75
+ # rubocop:enable Metrics/CyclomaticComplexity
76
+
77
+ def memoized?(method_name)
78
+ memoized_methods.key?(method_name)
79
+ end
80
+ end
81
+
82
+ def clear_memery_cache!(*method_names)
83
+ if method_names.any?
84
+ method_names.each { |method_name| @_memery_memoized_values[method_name]&.clear }
85
+ elsif defined? @_memery_memoized_values
86
+ @_memery_memoized_values.clear
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Memery
4
+ VERSION = '2.0.0'
5
+ end
metadata ADDED
@@ -0,0 +1,253 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alt_memery
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Yuri Smirnov
8
+ - Alexander Popov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2020-07-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: module_methods
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.1.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.1.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: ruby2_keywords
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 0.0.2
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 0.0.2
42
+ - !ruby/object:Gem::Dependency
43
+ name: activesupport
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '6.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '6.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry-byebug
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.9'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.9'
70
+ - !ruby/object:Gem::Dependency
71
+ name: benchmark-ips
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '2.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: benchmark-memory
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 0.1.0
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 0.1.0
98
+ - !ruby/object:Gem::Dependency
99
+ name: codecov
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: 0.1.0
105
+ - - "<"
106
+ - !ruby/object:Gem::Version
107
+ version: 0.1.18
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: 0.1.0
115
+ - - "<"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.1.18
118
+ - !ruby/object:Gem::Dependency
119
+ name: rspec
120
+ requirement: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ type: :development
126
+ prerelease: false
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.0'
132
+ - !ruby/object:Gem::Dependency
133
+ name: simplecov
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.18.0
139
+ type: :development
140
+ prerelease: false
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.18.0
146
+ - !ruby/object:Gem::Dependency
147
+ name: rubocop
148
+ requirement: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.87.0
153
+ type: :development
154
+ prerelease: false
155
+ version_requirements: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.87.0
160
+ - !ruby/object:Gem::Dependency
161
+ name: rubocop-performance
162
+ requirement: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.5'
167
+ type: :development
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '1.5'
174
+ - !ruby/object:Gem::Dependency
175
+ name: rubocop-rspec
176
+ requirement: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '1.38'
181
+ type: :development
182
+ prerelease: false
183
+ version_requirements: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '1.38'
188
+ - !ruby/object:Gem::Dependency
189
+ name: gem_toys
190
+ requirement: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.2.0
195
+ type: :development
196
+ prerelease: false
197
+ version_requirements: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 0.2.0
202
+ - !ruby/object:Gem::Dependency
203
+ name: toys
204
+ requirement: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 0.10.0
209
+ type: :development
210
+ prerelease: false
211
+ version_requirements: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: 0.10.0
216
+ description: |
217
+ Alt Memery is a gem for memoization.
218
+ It's a fork of Memery with implementation via `UnboundMethod` instead of `prepend Module`.
219
+ email:
220
+ - alex.wayfer@gmail.com
221
+ executables: []
222
+ extensions: []
223
+ extra_rdoc_files: []
224
+ files:
225
+ - CHANGELOG.md
226
+ - LICENSE.txt
227
+ - README.md
228
+ - lib/memery.rb
229
+ - lib/memery/version.rb
230
+ homepage: https://github.com/AlexWayfer/alt_memery
231
+ licenses:
232
+ - MIT
233
+ metadata: {}
234
+ post_install_message:
235
+ rdoc_options: []
236
+ require_paths:
237
+ - lib
238
+ required_ruby_version: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: '0'
243
+ required_rubygems_version: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - ">="
246
+ - !ruby/object:Gem::Version
247
+ version: '0'
248
+ requirements: []
249
+ rubygems_version: 3.1.2
250
+ signing_key:
251
+ specification_version: 4
252
+ summary: A gem for memoization.
253
+ test_files: []