alt_memery 2.1.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 +18 -1
  3. data/README.md +69 -30
  4. data/lib/memery/version.rb +1 -1
  5. data/lib/memery.rb +45 -33
  6. metadata +12 -191
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 858f46520931a8e2991134fa17882da328cc3ccc5c386e1c304e0edcb9fc3c90
4
- data.tar.gz: 8613844f42351da6d38d0222307857ebc2b5ccd47458b371fc08ceb48707c1f2
3
+ metadata.gz: 7b659d8221bbe5243f570b214b4207ccea2a1e48d33fb05d4513ae75e6898f73
4
+ data.tar.gz: 0fd69f2d6f473846c69642bc136d81a825efc76c5acfb1bbd666c1af8724c365
5
5
  SHA512:
6
- metadata.gz: 8935dc68e268a35e23e7eab016f28fe6e97c6e84bcaba5db7f0195e425b58bc6320d75204f886d5137bfbfc83355476b576c67eddd4d2ce8cb0abd6c8ed84bf2
7
- data.tar.gz: 147cdaf45147816c6dd708e0fe843a4b952a7dcc3b0a8bcc96627a5defc3361c0bb200a3e1f0afb8634d5ca20dff8f78080fc56f47945929179d311762b8f55d
6
+ metadata.gz: d73f760d8be6ba95ae6cb767081ea0dafc9e427d68ba5cdcc00d50f808c31946ab4140449abe68db0b028f8d08d0616f5492dcd2e59d8ecf46df91803aa4c6c9
7
+ data.tar.gz: 859421191c2cac2889ee155ff4980e0c1f2873fb0435f55d9b51c67570c0d20cd960f3f9c530f6d61423933a4af24604765c121a9b8540385df9707d4b4225d3
data/CHANGELOG.md CHANGED
@@ -1,6 +1,23 @@
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.
4
21
 
5
22
  ## 2.1.0 (2021-02-10)
6
23
 
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Alt Memery
2
2
 
3
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/master.svg?style=flat-square)](https://codecov.io/gh/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
5
  [![Code Climate](https://img.shields.io/codeclimate/maintainability/AlexWayfer/alt_memery.svg?style=flat-square)](https://codeclimate.com/github/AlexWayfer/alt_memery)
6
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=master)](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/master/LICENSE.txt)
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
9
  [![Gem](https://img.shields.io/gem/v/alt_memery.svg?style=flat-square)](https://rubygems.org/gems/alt_memery)
10
10
 
11
11
  Alt Memery allows to memoize methods return values.
@@ -28,6 +28,20 @@ def user
28
28
  end
29
29
  ```
30
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
+
31
45
  But with memoization gems, like this one, you can simplify your code:
32
46
 
33
47
  ```ruby
@@ -50,6 +64,8 @@ So, this fork uses `UnboundMethod` as I've suggested in the PR above.
50
64
 
51
65
  ## Difference with other gems
52
66
 
67
+ ### Memoizaion method and ancestors
68
+
53
69
  Such gems like [Memoist](https://github.com/matthewrudy/memoist) override methods.
54
70
  So, if you want to memoize a method in a child class with the same named memoized method
55
71
  in a parent class — you have to use something like awkward `identifier: ` argument.
@@ -57,9 +73,11 @@ This gem allows you to just memoize methods when you want to.
57
73
 
58
74
  Note how both method's return values are cached separately and don't interfere with each other.
59
75
 
60
- The other key difference is that it doesn't change method's arguments
61
- (no extra param like `reload`). If you need to get unmemoize result of method —
62
- 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:
63
81
 
64
82
  ```ruby
65
83
  a.clear_memery_cache! :foo, :bar
@@ -67,27 +85,32 @@ a.clear_memery_cache! :foo, :bar
67
85
 
68
86
  Without arguments, `#clear_memery_cache!` will clear the whole instance's cache.
69
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
+
70
93
  ## Installation
71
94
 
72
- Add this line to your application's Gemfile:
95
+ Add `gem 'alt_memery'` to your Gemfile.
73
96
 
74
- ```ruby
75
- gem 'alt_memery'
76
- ```
97
+ ## Usage
77
98
 
78
- And then execute:
99
+ ### Requirement
79
100
 
80
- ```shell
81
- bundle install
101
+ Since it's a fork of `memery` gem, require with:
102
+
103
+ ```ruby
104
+ require 'memery'
82
105
  ```
83
106
 
84
- Or install it yourself as:
107
+ Or from the `Gemfile`:
85
108
 
86
- ```shell
87
- gem install alt_memery
109
+ ```ruby
110
+ gem 'alt_memery', require: 'memery'
88
111
  ```
89
112
 
90
- ## Usage
113
+ ### Code
91
114
 
92
115
  ```ruby
93
116
  class A
@@ -97,22 +120,30 @@ class A
97
120
  puts "calculating"
98
121
  42
99
122
  end
100
-
101
- # or:
102
- # def call
103
- # ...
104
- # end
105
- # memoize :call
106
123
  end
107
124
 
108
125
  a = A.new
109
126
  a.call # => 42
110
127
  a.call # => 42
111
128
  a.call # => 42
112
- # Text will be printed only once.
129
+ # "calculating" will only be printed once.
113
130
 
114
131
  a.call { 1 } # => 42
115
- # 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
116
147
  ```
117
148
 
118
149
  Methods with arguments are supported and the memoization will be done based on arguments
@@ -132,7 +163,7 @@ a = A.new
132
163
  a.call(1, 5) # => 6
133
164
  a.call(2, 15) # => 17
134
165
  a.call(1, 5) # => 6
135
- # Text will be printed only twice, once per unique argument list.
166
+ # "calculating" will be printed twice, once for each unique argument list.
136
167
  ```
137
168
 
138
169
  For class methods:
@@ -152,10 +183,10 @@ end
152
183
  B.call # => 42
153
184
  B.call # => 42
154
185
  B.call # => 42
155
- # Text will be printed only once.
186
+ # "calculating" will only be printed once.
156
187
  ```
157
188
 
158
- For conditional memoization:
189
+ ### Conditional Memoization
159
190
 
160
191
  ```ruby
161
192
  class A
@@ -190,7 +221,7 @@ a.call # => 42
190
221
  # with `true` result of condition block.
191
222
  ```
192
223
 
193
- For memoization with time-to-live:
224
+ ### Memoization with Time-to-Live (TTL)
194
225
 
195
226
  ```ruby
196
227
  class A
@@ -222,7 +253,7 @@ a.call # => 42
222
253
  a.call # => 42
223
254
  ```
224
255
 
225
- Check if method is memoized:
256
+ ### Checking if a Method is Memoized
226
257
 
227
258
  ```ruby
228
259
  class A
@@ -299,6 +330,14 @@ puts C.instance_method(:foo).super_method.owner.memoized_methods[:foo].source
299
330
  # end
300
331
  ```
301
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
+
302
341
  ## Development
303
342
 
304
343
  After checking out the repo, run `bundle install` to install dependencies.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Memery
4
- VERSION = '2.1.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,51 +32,48 @@ 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
- # rubocop:disable Metrics/PerceivedComplexity
41
- def memoize(method_name, condition: nil, ttl: nil)
42
- 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
43
39
 
44
- original_method = memoized_methods[method_name] = instance_method(method_name)
40
+ method_names.size > 1 ? method_names : method_names.first
41
+ end
45
42
 
46
- undef_method method_name
43
+ def memoized?(method_name)
44
+ memoized_methods.key?(method_name)
45
+ end
47
46
 
48
- define_method method_name do |*args, &block|
49
- if block || (condition && !instance_exec(&condition))
50
- return original_method.bind(self).call(*args, &block)
51
- end
47
+ private
52
48
 
53
- method_object_id = original_method.object_id
49
+ def memoize_method(method_name, condition:, ttl:)
50
+ original_visibility = Memery.method_visibility(self, method_name)
54
51
 
55
- store =
56
- ((@_memery_memoized_values ||= {})[method_name] ||= {})[method_object_id] ||= {}
52
+ memoized_methods[method_name] = instance_method(method_name)
57
53
 
58
- if store.key?(args) && (ttl.nil? || Memery.monotonic_clock <= store[args][:time] + ttl)
59
- return store[args][:result]
60
- end
54
+ undef_method method_name
61
55
 
62
- result = original_method.bind(self).call(*args)
63
- @_memery_memoized_values[method_name][method_object_id][args] =
64
- { result: result, time: Memery.monotonic_clock }
65
- result
66
- end
56
+ define_memoized_method(method_name, condition:, ttl:)
67
57
 
68
58
  ruby2_keywords method_name
69
59
 
70
60
  send original_visibility, method_name
71
-
72
- method_name
73
61
  end
74
- # rubocop:enable Metrics/MethodLength
75
- # rubocop:enable Metrics/AbcSize
76
- # rubocop:enable Metrics/CyclomaticComplexity
77
- # rubocop:enable Metrics/PerceivedComplexity
78
62
 
79
- def memoized?(method_name)
80
- 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
81
77
  end
82
78
  end
83
79
 
@@ -90,4 +86,20 @@ module Memery
90
86
  @_memery_memoized_values.clear
91
87
  end
92
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
93
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.1.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: 2021-02-09 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,188 +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.2.0
105
- type: :development
106
- prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- requirements:
109
- - - "~>"
110
- - !ruby/object:Gem::Version
111
- version: 0.2.0
112
- - !ruby/object:Gem::Dependency
113
- name: rspec
114
- requirement: !ruby/object:Gem::Requirement
115
- requirements:
116
- - - "~>"
117
- - !ruby/object:Gem::Version
118
- version: '3.0'
119
- type: :development
120
- prerelease: false
121
- version_requirements: !ruby/object:Gem::Requirement
122
- requirements:
123
- - - "~>"
124
- - !ruby/object:Gem::Version
125
- version: '3.0'
126
- - !ruby/object:Gem::Dependency
127
- name: simplecov
128
- requirement: !ruby/object:Gem::Requirement
129
- requirements:
130
- - - "~>"
131
- - !ruby/object:Gem::Version
132
- version: 0.20.0
133
- type: :development
134
- prerelease: false
135
- version_requirements: !ruby/object:Gem::Requirement
136
- requirements:
137
- - - "~>"
138
- - !ruby/object:Gem::Version
139
- version: 0.20.0
140
- - !ruby/object:Gem::Dependency
141
- name: rubocop
142
- requirement: !ruby/object:Gem::Requirement
143
- requirements:
144
- - - "~>"
145
- - !ruby/object:Gem::Version
146
- version: '1.0'
147
- type: :development
148
- prerelease: false
149
- version_requirements: !ruby/object:Gem::Requirement
150
- requirements:
151
- - - "~>"
152
- - !ruby/object:Gem::Version
153
- version: '1.0'
154
- - !ruby/object:Gem::Dependency
155
- name: rubocop-performance
156
- requirement: !ruby/object:Gem::Requirement
157
- requirements:
158
- - - "~>"
159
- - !ruby/object:Gem::Version
160
- version: '1.5'
161
- type: :development
162
- prerelease: false
163
- version_requirements: !ruby/object:Gem::Requirement
164
- requirements:
165
- - - "~>"
166
- - !ruby/object:Gem::Version
167
- version: '1.5'
168
- - !ruby/object:Gem::Dependency
169
- name: rubocop-rspec
170
- requirement: !ruby/object:Gem::Requirement
171
- requirements:
172
- - - "~>"
173
- - !ruby/object:Gem::Version
174
- version: '2.0'
175
- type: :development
176
- prerelease: false
177
- version_requirements: !ruby/object:Gem::Requirement
178
- requirements:
179
- - - "~>"
180
- - !ruby/object:Gem::Version
181
- version: '2.0'
182
- - !ruby/object:Gem::Dependency
183
- name: gem_toys
184
- requirement: !ruby/object:Gem::Requirement
185
- requirements:
186
- - - "~>"
187
- - !ruby/object:Gem::Version
188
- version: 0.5.0
189
- type: :development
190
- prerelease: false
191
- version_requirements: !ruby/object:Gem::Requirement
192
- requirements:
193
- - - "~>"
194
- - !ruby/object:Gem::Version
195
- version: 0.5.0
196
- - !ruby/object:Gem::Dependency
197
- name: toys
198
- requirement: !ruby/object:Gem::Requirement
199
- requirements:
200
- - - "~>"
201
- - !ruby/object:Gem::Version
202
- version: 0.11.0
203
- type: :development
204
- prerelease: false
205
- version_requirements: !ruby/object:Gem::Requirement
206
- requirements:
207
- - - "~>"
208
- - !ruby/object:Gem::Version
209
- version: 0.11.0
210
27
  description: |
211
28
  Alt Memery is a gem for memoization.
212
29
  It's a fork of Memery with implementation via `UnboundMethod` instead of `prepend Module`.
@@ -224,8 +41,13 @@ files:
224
41
  homepage: https://github.com/AlexWayfer/alt_memery
225
42
  licenses:
226
43
  - MIT
227
- metadata: {}
228
- 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
229
51
  rdoc_options: []
230
52
  require_paths:
231
53
  - lib
@@ -233,18 +55,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
233
55
  requirements:
234
56
  - - ">="
235
57
  - !ruby/object:Gem::Version
236
- version: '2.5'
58
+ version: '3.4'
237
59
  - - "<"
238
60
  - !ruby/object:Gem::Version
239
- version: '4'
61
+ version: '5'
240
62
  required_rubygems_version: !ruby/object:Gem::Requirement
241
63
  requirements:
242
64
  - - ">="
243
65
  - !ruby/object:Gem::Version
244
66
  version: '0'
245
67
  requirements: []
246
- rubygems_version: 3.2.3
247
- signing_key:
68
+ rubygems_version: 4.0.3
248
69
  specification_version: 4
249
70
  summary: A gem for memoization.
250
71
  test_files: []