alt_memery 2.0.0 → 3.0.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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -1
  3. data/README.md +85 -27
  4. data/lib/memery/version.rb +1 -1
  5. data/lib/memery.rb +48 -32
  6. metadata +14 -196
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34a090f8bb82efcf602abd829780cf4ad2999a5a4ef2832c739438e05ffde120
4
- data.tar.gz: 205faeba1520ae7fc16877b00260450db78dbf425904e2140bdc8733dac8e1db
3
+ metadata.gz: 7b659d8221bbe5243f570b214b4207ccea2a1e48d33fb05d4513ae75e6898f73
4
+ data.tar.gz: 0fd69f2d6f473846c69642bc136d81a825efc76c5acfb1bbd666c1af8724c365
5
5
  SHA512:
6
- metadata.gz: 2d5d4380c23de08c5501e2875debfc1eb64db87e2df49c07dca6a1f71a1fd702a205284971a2266a176bcea6de65be0897bfd35f0a13e43ada5996d6cd2e48d3
7
- data.tar.gz: 6f0f56fc4abb3277d49235a086933228e86ad30917978c6ac994732cb511bfb9537dab641a7b9baf9085f250decd803cc51e234aa089a4d76ceb719d070466f1
6
+ metadata.gz: d73f760d8be6ba95ae6cb767081ea0dafc9e427d68ba5cdcc00d50f808c31946ab4140449abe68db0b028f8d08d0616f5492dcd2e59d8ecf46df91803aa4c6c9
7
+ data.tar.gz: 859421191c2cac2889ee155ff4980e0c1f2873fb0435f55d9b51c67570c0d20cd960f3f9c530f6d61423933a4af24604765c121a9b8540385df9707d4b4225d3
data/CHANGELOG.md CHANGED
@@ -1,6 +1,31 @@
1
1
  # Changelog
2
2
 
3
- ## master (unreleased)
3
+ ## Unreleased
4
+
5
+ ## 3.0.0 (2026-01-08)
6
+
7
+ * Allow to memoize multiple method names.
8
+ * Performance improvements.
9
+ * Enshure that marshalizing works correctly.
10
+ * Add specs for double memoization.
11
+ * Add Ruby 3.4 and 4.0 support.
12
+ * Drop Ruby 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and 3.3 support.
13
+ * Improve README.
14
+ * Actualize gem specs metadata.
15
+ * Add `bundle-audit` CI task.
16
+ * Improve Cirrus CI config.
17
+ * Improve RuboCop config.
18
+ * Resolve new RuboCop offenses.
19
+ * Lock RuboCop version better.
20
+ * Update development dependencies.
21
+
22
+ ## 2.1.0 (2021-02-10)
23
+
24
+ * Support Ruby 3.
25
+ * Fix error of `#clear_memery_cache!` with specific methods and without existing cache.
26
+ * Improve specs.
27
+ * Update development dependencies.
28
+ * Update documentation.
4
29
 
5
30
  ## 2.0.0 (2020-07-09)
6
31
 
data/README.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Alt Memery
2
2
 
3
+ [![Cirrus CI - Base Branch Build Status](https://img.shields.io/cirrus/github/AlexWayfer/alt_memery?style=flat-square)](https://cirrus-ci.com/github/AlexWayfer/alt_memery)
4
+ [![Codecov branch](https://img.shields.io/codecov/c/github/AlexWayfer/alt_memery/main.svg?style=flat-square)](https://codecov.io/gh/AlexWayfer/alt_memery)
5
+ [![Code Climate](https://img.shields.io/codeclimate/maintainability/AlexWayfer/alt_memery.svg?style=flat-square)](https://codeclimate.com/github/AlexWayfer/alt_memery)
6
+ [![Depfu](https://img.shields.io/depfu/AlexWayfer/benchmark_toys?style=flat-square)](https://depfu.com/repos/github/AlexWayfer/alt_memery)
7
+ [![Inline docs](https://inch-ci.org/github/AlexWayfer/alt_memery.svg?branch=main)](https://inch-ci.org/github/AlexWayfer/alt_memery)
8
+ [![license](https://img.shields.io/github/license/AlexWayfer/alt_memery.svg?style=flat-square)](https://github.com/AlexWayfer/alt_memery/blob/main/LICENSE.txt)
9
+ [![Gem](https://img.shields.io/gem/v/alt_memery.svg?style=flat-square)](https://rubygems.org/gems/alt_memery)
10
+
3
11
  Alt Memery allows to memoize methods return values.
4
12
 
5
13
  The native simplest memoization in Ruby looks like this:
@@ -20,6 +28,20 @@ def user
20
28
  end
21
29
  ```
22
30
 
31
+ Even worse: if your calculations require multiple lines, you have to use `begin`/`end` block:
32
+
33
+ ```ruby
34
+ def user
35
+ @user ||= begin
36
+ some_id = calculate_id
37
+ klass = calculate_klass
38
+ klass.find(some_id)
39
+ end
40
+ end
41
+ ```
42
+
43
+ And there is no elegant escape if your methods accept arguments.
44
+
23
45
  But with memoization gems, like this one, you can simplify your code:
24
46
 
25
47
  ```ruby
@@ -42,6 +64,8 @@ So, this fork uses `UnboundMethod` as I've suggested in the PR above.
42
64
 
43
65
  ## Difference with other gems
44
66
 
67
+ ### Memoizaion method and ancestors
68
+
45
69
  Such gems like [Memoist](https://github.com/matthewrudy/memoist) override methods.
46
70
  So, if you want to memoize a method in a child class with the same named memoized method
47
71
  in a parent class — you have to use something like awkward `identifier: ` argument.
@@ -49,9 +73,11 @@ This gem allows you to just memoize methods when you want to.
49
73
 
50
74
  Note how both method's return values are cached separately and don't interfere with each other.
51
75
 
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:
76
+ ### Memoized method arguments and clearing cache
77
+
78
+ This gem doesn't change method's arguments (no extra param like `reload`).
79
+ If you need to get unmemoize result of method — just call the `#clear_memery_cache!` method
80
+ with needed memoized method names:
55
81
 
56
82
  ```ruby
57
83
  a.clear_memery_cache! :foo, :bar
@@ -59,27 +85,32 @@ a.clear_memery_cache! :foo, :bar
59
85
 
60
86
  Without arguments, `#clear_memery_cache!` will clear the whole instance's cache.
61
87
 
88
+ ### Marshal compatibility
89
+
90
+ This gem doesn't require any global options, it works well with marshalized objects
91
+ in different proccesses with hashed arguments without performance impact.
92
+
62
93
  ## Installation
63
94
 
64
- Add this line to your application's Gemfile:
95
+ Add `gem 'alt_memery'` to your Gemfile.
65
96
 
66
- ```ruby
67
- gem 'alt_memery'
68
- ```
97
+ ## Usage
98
+
99
+ ### Requirement
69
100
 
70
- And then execute:
101
+ Since it's a fork of `memery` gem, require with:
71
102
 
72
- ```shell
73
- bundle install
103
+ ```ruby
104
+ require 'memery'
74
105
  ```
75
106
 
76
- Or install it yourself as:
107
+ Or from the `Gemfile`:
77
108
 
78
- ```shell
79
- gem install alt_memery
109
+ ```ruby
110
+ gem 'alt_memery', require: 'memery'
80
111
  ```
81
112
 
82
- ## Usage
113
+ ### Code
83
114
 
84
115
  ```ruby
85
116
  class A
@@ -89,22 +120,30 @@ class A
89
120
  puts "calculating"
90
121
  42
91
122
  end
92
-
93
- # or:
94
- # def call
95
- # ...
96
- # end
97
- # memoize :call
98
123
  end
99
124
 
100
125
  a = A.new
101
126
  a.call # => 42
102
127
  a.call # => 42
103
128
  a.call # => 42
104
- # Text will be printed only once.
129
+ # "calculating" will only be printed once.
105
130
 
106
131
  a.call { 1 } # => 42
107
- # Will print because passing a block disables memoization
132
+ # "calculating" will be printed again because passing a block disables memoization.
133
+ ```
134
+
135
+ Alternatively:
136
+
137
+ ```ruby
138
+ class A
139
+ include Memery
140
+
141
+ def call
142
+ puts "calculating"
143
+ 42
144
+ end
145
+ memoize :call
146
+ end
108
147
  ```
109
148
 
110
149
  Methods with arguments are supported and the memoization will be done based on arguments
@@ -124,7 +163,7 @@ a = A.new
124
163
  a.call(1, 5) # => 6
125
164
  a.call(2, 15) # => 17
126
165
  a.call(1, 5) # => 6
127
- # Text will be printed only twice, once per unique argument list.
166
+ # "calculating" will be printed twice, once for each unique argument list.
128
167
  ```
129
168
 
130
169
  For class methods:
@@ -144,10 +183,10 @@ end
144
183
  B.call # => 42
145
184
  B.call # => 42
146
185
  B.call # => 42
147
- # Text will be printed only once.
186
+ # "calculating" will only be printed once.
148
187
  ```
149
188
 
150
- For conditional memoization:
189
+ ### Conditional Memoization
151
190
 
152
191
  ```ruby
153
192
  class A
@@ -182,7 +221,7 @@ a.call # => 42
182
221
  # with `true` result of condition block.
183
222
  ```
184
223
 
185
- For memoization with time-to-live:
224
+ ### Memoization with Time-to-Live (TTL)
186
225
 
187
226
  ```ruby
188
227
  class A
@@ -214,7 +253,7 @@ a.call # => 42
214
253
  a.call # => 42
215
254
  ```
216
255
 
217
- Check if method is memoized:
256
+ ### Checking if a Method is Memoized
218
257
 
219
258
  ```ruby
220
259
  class A
@@ -291,6 +330,25 @@ puts C.instance_method(:foo).super_method.owner.memoized_methods[:foo].source
291
330
  # end
292
331
  ```
293
332
 
333
+ ### Object Shape Optimization
334
+
335
+ In Ruby 3.2, a new optimization called "object shape" was introduced,
336
+ which can have negative interactions with dynamically added instance variables.
337
+ Alt Memery minimizes this impact by introducing only one new instance variable
338
+ after initialization (`@_memery_memoized_values`). If you need to ensure a specific object shape,
339
+ you can call `clear_memery_cache!` in your initializer to set the instance variable ahead of time.
340
+
341
+ ## Development
342
+
343
+ After checking out the repo, run `bundle install` to install dependencies.
344
+
345
+ Then, run `toys rspec` to run the tests.
346
+
347
+ To install this gem onto your local machine, run `toys gem install`.
348
+
349
+ To release a new version, run `toys gem release %version%`.
350
+ See how it works [here](https://github.com/AlexWayfer/gem_toys#release).
351
+
294
352
  ## Contributing
295
353
 
296
354
  Bug reports and pull requests are welcome on GitHub at <https://github.com/AlexWayfer/alt_memery>.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Memery
4
- VERSION = '2.0.0'
4
+ VERSION = '3.0.0'
5
5
  end
data/lib/memery.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'module_methods'
4
- require 'ruby2_keywords'
5
4
 
6
5
  require_relative 'memery/version'
7
6
 
@@ -33,57 +32,74 @@ module Memery
33
32
  @memoized_methods ||= {}
34
33
  end
35
34
 
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)
35
+ def memoize(*method_names, condition: nil, ttl: nil)
36
+ method_names.each do |method_name|
37
+ memoize_method(method_name, condition:, ttl:)
38
+ end
42
39
 
43
- original_method = memoized_methods[method_name] = instance_method(method_name)
40
+ method_names.size > 1 ? method_names : method_names.first
41
+ end
44
42
 
45
- undef_method method_name
43
+ def memoized?(method_name)
44
+ memoized_methods.key?(method_name)
45
+ end
46
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
47
+ private
51
48
 
52
- method_object_id = original_method.object_id
49
+ def memoize_method(method_name, condition:, ttl:)
50
+ original_visibility = Memery.method_visibility(self, method_name)
53
51
 
54
- store =
55
- ((@_memery_memoized_values ||= {})[method_name] ||= {})[method_object_id] ||= {}
52
+ memoized_methods[method_name] = instance_method(method_name)
56
53
 
57
- if store.key?(args) && (ttl.nil? || Memery.monotonic_clock <= store[args][:time] + ttl)
58
- return store[args][:result]
59
- end
54
+ undef_method method_name
60
55
 
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
56
+ define_memoized_method(method_name, condition:, ttl:)
66
57
 
67
58
  ruby2_keywords method_name
68
59
 
69
60
  send original_visibility, method_name
70
-
71
- method_name
72
61
  end
73
- # rubocop:enable Metrics/MethodLength
74
- # rubocop:enable Metrics/AbcSize
75
- # rubocop:enable Metrics/CyclomaticComplexity
76
62
 
77
- def memoized?(method_name)
78
- memoized_methods.key?(method_name)
63
+ def define_memoized_method(method_name, condition:, ttl:)
64
+ original_method = memoized_methods[method_name]
65
+
66
+ define_method method_name do |*args, &block|
67
+ if block || (condition && !instance_exec(&condition))
68
+ return original_method.bind_call(self, *args, &block)
69
+ end
70
+
71
+ store = memery_store method_name
72
+
73
+ return store[args.hash][:result] if memoized_result_actual?(store, args, ttl: ttl)
74
+
75
+ call_original_and_memoize original_method, args, store
76
+ end
79
77
  end
80
78
  end
81
79
 
82
80
  def clear_memery_cache!(*method_names)
81
+ return unless defined? @_memery_memoized_values
82
+
83
83
  if method_names.any?
84
84
  method_names.each { |method_name| @_memery_memoized_values[method_name]&.clear }
85
- elsif defined? @_memery_memoized_values
85
+ else
86
86
  @_memery_memoized_values.clear
87
87
  end
88
88
  end
89
+
90
+ private
91
+
92
+ def memery_store(method_name)
93
+ ((@_memery_memoized_values ||= {})[method_name] ||= {})[self.class] ||= {}
94
+ end
95
+
96
+ def memoized_result_actual?(store, args, ttl:)
97
+ store.key?(args.hash) && (ttl.nil? || Memery.monotonic_clock <= store[args.hash][:time] + ttl)
98
+ end
99
+
100
+ def call_original_and_memoize(original_method, args, store)
101
+ result = original_method.bind_call(self, *args)
102
+ store[args.hash] = { result: result, time: Memery.monotonic_clock }
103
+ result
104
+ end
89
105
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alt_memery
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Smirnov
8
8
  - Alexander Popov
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2020-07-08 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: module_methods
@@ -25,194 +24,6 @@ dependencies:
25
24
  - - "~>"
26
25
  - !ruby/object:Gem::Version
27
26
  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
27
  description: |
217
28
  Alt Memery is a gem for memoization.
218
29
  It's a fork of Memery with implementation via `UnboundMethod` instead of `prepend Module`.
@@ -230,8 +41,13 @@ files:
230
41
  homepage: https://github.com/AlexWayfer/alt_memery
231
42
  licenses:
232
43
  - MIT
233
- metadata: {}
234
- post_install_message:
44
+ metadata:
45
+ bug_tracker_uri: https://github.com/AlexWayfer/alt_memery/issues
46
+ changelog_uri: https://github.com/AlexWayfer/alt_memery/blob/v3.0.0/CHANGELOG.md
47
+ documentation_uri: http://www.rubydoc.info/gems/alt_memery/3.0.0
48
+ homepage_uri: https://github.com/AlexWayfer/alt_memery
49
+ rubygems_mfa_required: 'true'
50
+ source_code_uri: https://github.com/AlexWayfer/alt_memery
235
51
  rdoc_options: []
236
52
  require_paths:
237
53
  - lib
@@ -239,15 +55,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
239
55
  requirements:
240
56
  - - ">="
241
57
  - !ruby/object:Gem::Version
242
- version: '0'
58
+ version: '3.4'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '5'
243
62
  required_rubygems_version: !ruby/object:Gem::Requirement
244
63
  requirements:
245
64
  - - ">="
246
65
  - !ruby/object:Gem::Version
247
66
  version: '0'
248
67
  requirements: []
249
- rubygems_version: 3.1.2
250
- signing_key:
68
+ rubygems_version: 4.0.3
251
69
  specification_version: 4
252
70
  summary: A gem for memoization.
253
71
  test_files: []