contextual_memery 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +146 -0
- data/Rakefile +10 -0
- data/lib/contextual_memery/version.rb +5 -0
- data/lib/contextual_memery.rb +117 -0
- data/sig/contextual_memery.rbs +4 -0
- metadata +66 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3a011f6e427ef5596ab389ba2139429244b850f68b1040b4f96ceb3cf086153e
|
|
4
|
+
data.tar.gz: 5e3cffb9f6b7c3cc4f0f1fc416ef2ca77968bb89f74584bbaed0ff901e686183
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1be657f8eac292d36590671e9985395854e4a32304e2428dc849d605bcd5a0578709ece3be968410db2b8003def19edc16e54c1999bc7c80f8af9760265f3a29
|
|
7
|
+
data.tar.gz: ffe195070b5c659e989273e62ded24dcbf51e0be999296f165e42c551621415bf90c4bcddfce77008ae10f969aad11c8355a189c44d6447cf7d038071348d9ce
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RobL
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# ContextualMemery
|
|
2
|
+
|
|
3
|
+
`ContextualMemery` builds on the [`memery`](https://github.com/DmitryTsepelev/memery) gem to
|
|
4
|
+
let you turn memoization on and off *at runtime*, scoped to a block, for one or more methods,
|
|
5
|
+
on either an instance or a class.
|
|
6
|
+
|
|
7
|
+
Where `memery` memoizes a method for the lifetime of the object, `ContextualMemery` memoizes a
|
|
8
|
+
method only for the duration of a block:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
instance.banana_time # not memoized
|
|
12
|
+
instance.banana_time # not memoized, recomputed
|
|
13
|
+
|
|
14
|
+
instance.memoized(:banana_time) do
|
|
15
|
+
instance.banana_time # computed
|
|
16
|
+
instance.banana_time # cached
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
instance.banana_time # not memoized again, cache has been cleared
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This is handy when you want to memoize a call for the duration of a request, a job, a loop
|
|
23
|
+
iteration, etc., without permanently memoizing it for the object's whole lifetime.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bundle add contextual_memery
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Include `ContextualMemery` in your class:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
class Thing
|
|
39
|
+
include ContextualMemery
|
|
40
|
+
|
|
41
|
+
def banana_time(*)
|
|
42
|
+
...
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def handyman
|
|
46
|
+
...
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.take_back(*)
|
|
50
|
+
...
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This gives both instances *and* the class a `memoized` method.
|
|
56
|
+
|
|
57
|
+
### On an instance
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
instance = Thing.new
|
|
61
|
+
|
|
62
|
+
instance.memoized(:banana_time) do
|
|
63
|
+
instance.banana_time
|
|
64
|
+
instance.banana_time # cached
|
|
65
|
+
instance.handyman # not memoized, still recomputed every call
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Pass several method names to memoize more than one method at once:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
instance.memoized(:banana_time, :douglas) do
|
|
73
|
+
...
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Pass `:all`, or no arguments at all, to memoize every method defined directly on the object
|
|
78
|
+
(public, protected and private) for the duration of the block:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
instance.memoized(:all) do
|
|
82
|
+
...
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
instance.memoized do
|
|
86
|
+
...
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### On a class
|
|
91
|
+
|
|
92
|
+
The exact same API is available on the class itself, for memoizing class (singleton) methods:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
Thing.memoized(:take_back) do
|
|
96
|
+
Thing.take_back
|
|
97
|
+
Thing.take_back # cached
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Scoped to the block, including indirect calls
|
|
102
|
+
|
|
103
|
+
Memoization is active for any call made from within the block, even indirectly through other
|
|
104
|
+
methods — it isn't limited to calls written literally inside the `do...end`:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
def do_thing(onion)
|
|
108
|
+
Thing.take_back(onion)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
Thing.memoized(:take_back) do
|
|
112
|
+
foods.each do |food|
|
|
113
|
+
do_thing(food.skin) # each unique `food.skin` is only computed once
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Scoping and nesting
|
|
119
|
+
|
|
120
|
+
- Memoization is scoped per object: memoizing `instance_a` never affects `instance_b`, and
|
|
121
|
+
memoizing an instance never affects the class (or vice versa).
|
|
122
|
+
- Arguments are still taken into account, exactly like plain `memery`: `banana_time(1)` and
|
|
123
|
+
`banana_time(2)` are cached separately.
|
|
124
|
+
- The cache is cleared as soon as the outermost `memoized` block for that object finishes, so
|
|
125
|
+
nesting `memoized` blocks on the same object is safe and won't clear the cache out from under
|
|
126
|
+
an enclosing block.
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to
|
|
131
|
+
run the tests. You can also run `bin/console` for an interactive prompt that will allow you to
|
|
132
|
+
experiment.
|
|
133
|
+
|
|
134
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new
|
|
135
|
+
version, update the version number in `version.rb`, and then run `bundle exec rake release`,
|
|
136
|
+
which will create a git tag for the version, push git commits and the created tag, and push the
|
|
137
|
+
`.gem` file to [rubygems.org](https://rubygems.org).
|
|
138
|
+
|
|
139
|
+
## Contributing
|
|
140
|
+
|
|
141
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
142
|
+
https://github.com/braindeaf/contextual_memery.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "memery"
|
|
4
|
+
require_relative "contextual_memery/version"
|
|
5
|
+
|
|
6
|
+
# Mix `ContextualMemery` into a class to gain a `memoized` method that turns on
|
|
7
|
+
# Memery memoization for the duration of a block only, e.g.
|
|
8
|
+
#
|
|
9
|
+
# class Thing
|
|
10
|
+
# include ContextualMemery
|
|
11
|
+
#
|
|
12
|
+
# def banana_time
|
|
13
|
+
# ...
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# instance = Thing.new
|
|
18
|
+
# instance.memoized(:banana_time) do
|
|
19
|
+
# instance.banana_time # computed
|
|
20
|
+
# instance.banana_time # cached
|
|
21
|
+
# end
|
|
22
|
+
# instance.banana_time # computed again, cache is gone
|
|
23
|
+
#
|
|
24
|
+
# `include`-ing this module also `extend`s the including class with it, so the
|
|
25
|
+
# same `memoized` method is available on the class itself, letting you scope
|
|
26
|
+
# memoization to class (singleton) methods too:
|
|
27
|
+
#
|
|
28
|
+
# Thing.memoized(:take_back) do
|
|
29
|
+
# Thing.take_back
|
|
30
|
+
# Thing.take_back
|
|
31
|
+
# end
|
|
32
|
+
module ContextualMemery
|
|
33
|
+
class Error < StandardError; end
|
|
34
|
+
|
|
35
|
+
# Thread-local nesting depth per memoized target (a singleton class), so
|
|
36
|
+
# that nested #memoized calls on the same object don't clear the cache out
|
|
37
|
+
# from under an outer, still-active block.
|
|
38
|
+
THREAD_KEY = :contextual_memery_depths
|
|
39
|
+
|
|
40
|
+
# Methods we never want swept up by the implicit "memoize everything" mode.
|
|
41
|
+
EXCLUDED_METHOD_NAMES = %i[memoized initialize].freeze
|
|
42
|
+
|
|
43
|
+
def self.included(base)
|
|
44
|
+
super
|
|
45
|
+
base.extend(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Turns on memoization for +method_names+ (or every method defined
|
|
49
|
+
# directly on the receiver when none are given, or +:all+ is given) for the
|
|
50
|
+
# duration of the block. Works the same whether called on an instance or on
|
|
51
|
+
# a class/module.
|
|
52
|
+
def memoized(*method_names)
|
|
53
|
+
raise ArgumentError, "memoized requires a block" unless block_given?
|
|
54
|
+
|
|
55
|
+
target = singleton_class
|
|
56
|
+
target.include(Memery) unless target.include?(Memery)
|
|
57
|
+
|
|
58
|
+
memoize_methods!(target, method_names_to_memoize(method_names))
|
|
59
|
+
|
|
60
|
+
ContextualMemery.enter(target)
|
|
61
|
+
begin
|
|
62
|
+
yield
|
|
63
|
+
ensure
|
|
64
|
+
clear_memery_cache! if ContextualMemery.leave(target)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def memoize_methods!(target, names)
|
|
71
|
+
names.each do |name|
|
|
72
|
+
next if target.memoized?(name)
|
|
73
|
+
|
|
74
|
+
target.memoize(name, condition: -> { ContextualMemery.active?(target) })
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def method_names_to_memoize(method_names)
|
|
79
|
+
names = (method_names.empty? || method_names == [:all]) ? own_method_names : method_names
|
|
80
|
+
names.map(&:to_sym) - EXCLUDED_METHOD_NAMES
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def own_method_names
|
|
84
|
+
owner = is_a?(Module) ? singleton_class : self.class
|
|
85
|
+
owner.instance_methods(false) +
|
|
86
|
+
owner.private_instance_methods(false) +
|
|
87
|
+
owner.protected_instance_methods(false)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class << self
|
|
91
|
+
def active?(target)
|
|
92
|
+
depths.fetch(target, 0) > 0
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def enter(target)
|
|
96
|
+
depths[target] = depths.fetch(target, 0) + 1
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Returns true when this call closed the outermost #memoized block for
|
|
100
|
+
# +target+, i.e. it's now safe to clear its memoized cache.
|
|
101
|
+
def leave(target)
|
|
102
|
+
depths[target] -= 1
|
|
103
|
+
if depths[target] <= 0
|
|
104
|
+
depths.delete(target)
|
|
105
|
+
true
|
|
106
|
+
else
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def depths
|
|
114
|
+
Thread.current[THREAD_KEY] ||= {}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: contextual_memery
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- RobL
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: memery
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
description: ContextualMemery wraps the memery gem to let you turn memoization on
|
|
27
|
+
and off at runtime, for specific methods, scoped to a block, on either a class or
|
|
28
|
+
an instance.
|
|
29
|
+
email:
|
|
30
|
+
- contact@robl.me
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- CHANGELOG.md
|
|
36
|
+
- LICENSE.txt
|
|
37
|
+
- README.md
|
|
38
|
+
- Rakefile
|
|
39
|
+
- lib/contextual_memery.rb
|
|
40
|
+
- lib/contextual_memery/version.rb
|
|
41
|
+
- sig/contextual_memery.rbs
|
|
42
|
+
homepage: https://github.com/braindeaf/contextual_memery
|
|
43
|
+
licenses:
|
|
44
|
+
- MIT
|
|
45
|
+
metadata:
|
|
46
|
+
homepage_uri: https://github.com/braindeaf/contextual_memery
|
|
47
|
+
source_code_uri: https://github.com/braindeaf/contextual_memery.git
|
|
48
|
+
changelog_uri: https://github.com/braindeaf/contextual_memery/blob/main/CHANGELOG.md
|
|
49
|
+
rdoc_options: []
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: 3.2.0
|
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubygems_version: 4.0.3
|
|
64
|
+
specification_version: 4
|
|
65
|
+
summary: Dynamically scope Memery memoization to a block, on a class or an instance.
|
|
66
|
+
test_files: []
|