mize 0.6.1 → 0.7.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 +4 -4
- data/.all_images.yml +20 -14
- data/.github/workflows/static.yml +51 -0
- data/.rspec +1 -0
- data/CHANGES.md +62 -0
- data/README.md +144 -42
- data/Rakefile +10 -2
- data/VERSION +1 -1
- data/lib/mize/cache_methods.rb +42 -1
- data/lib/mize/cache_protocol.rb +19 -0
- data/lib/mize/configure.rb +30 -2
- data/lib/mize/default_cache.rb +45 -12
- data/lib/mize/global_clear.rb +15 -1
- data/lib/mize/memoize.rb +65 -9
- data/lib/mize/railtie.rb +7 -0
- data/lib/mize/reload.rb +16 -3
- data/lib/mize/version.rb +7 -2
- data/lib/mize.rb +63 -0
- data/mize.gemspec +11 -10
- data/spec/mize/global_clear_spec.rb +0 -2
- data/spec/mize/memoize_method_spec.rb +0 -2
- data/spec/mize/memoize_reload_spec.rb +0 -2
- data/spec/mize/version_spec.rb +0 -2
- data/spec/mize_spec.rb +13 -0
- data/spec/spec_helper.rb +4 -5
- metadata +15 -17
- data/.utilsrc +0 -26
- data/config/broken_config.yml +0 -1
- data/config/config.yml +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '04349dcaaaec8f657183fc213be81b6c49f182d0687441b7cdd004e561a008a4'
|
|
4
|
+
data.tar.gz: 89f29be0ec81a2d25c4418d818e89afa40e4699529f4d04feccffabb4a0225dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e4ea031b167f232db72a768d892884ea6516b4d1f68603b2e992c95bfed16509838e7df6fc64142944cc5009fff8472a08ede1f88b4767e25e1df9fa2cc980b
|
|
7
|
+
data.tar.gz: a0790fa35f0ac32bd68679670cbdb45efb4173ef1140865ac64f2e891edc8ca49ad6ceb502dedebe87b7897eb64606cbfd7817eebe6f6033f56b693338d27564
|
data/.all_images.yml
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
dockerfile: |-
|
|
2
|
-
RUN apk add --no-cache build-base git
|
|
3
|
-
RUN bash <<NUR
|
|
4
|
-
if [[ "$(ruby -e 'print RUBY_VERSION')" > '3' ]]
|
|
5
|
-
then
|
|
6
|
-
gem update --system
|
|
7
|
-
gem install gem_hadar bundler
|
|
8
|
-
else
|
|
9
|
-
gem install gem_hadar
|
|
10
|
-
gem install bundler -v 2.4.22
|
|
11
|
-
fi
|
|
12
|
-
NUR
|
|
2
|
+
RUN apk add --no-cache build-base yaml-dev git openssl-dev
|
|
13
3
|
|
|
14
4
|
script: &script |-
|
|
15
5
|
echo -e "\e[1m"
|
|
16
6
|
ruby -v
|
|
17
7
|
echo -e "\e[0m"
|
|
18
|
-
bundle install --
|
|
8
|
+
bundle install --jobs=$(getconf _NPROCESSORS_ONLN)
|
|
19
9
|
bundle exec rake spec
|
|
20
10
|
|
|
21
|
-
|
|
11
|
+
before: |-
|
|
12
|
+
echo 🏁 Preparing…
|
|
13
|
+
echo Deleting Gemfile.lock
|
|
14
|
+
rm -f Gemfile.lock
|
|
15
|
+
|
|
16
|
+
after: |-
|
|
17
|
+
if [[ "$RESULT" = "1" ]]
|
|
18
|
+
then
|
|
19
|
+
echo 😵 Some tests failed!
|
|
20
|
+
else
|
|
21
|
+
echo 🥳 All tests passed!
|
|
22
|
+
fi
|
|
23
|
+
echo Deleting Gemfile.lock
|
|
24
|
+
rm -f Gemfile.lock
|
|
25
|
+
|
|
26
|
+
fail_fast: true
|
|
22
27
|
|
|
23
28
|
images:
|
|
29
|
+
ruby:4.0-alpine: *script
|
|
30
|
+
ruby:3.4-alpine: *script
|
|
24
31
|
ruby:3.3-alpine: *script
|
|
25
32
|
ruby:3.2-alpine: *script
|
|
26
33
|
ruby:3.1-alpine: *script
|
|
27
|
-
ruby:3.0-alpine: *script
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Simple workflow for deploying static content to GitHub Pages
|
|
2
|
+
name: Deploy static content to Pages
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
# Runs on pushes targeting the default branch
|
|
6
|
+
push:
|
|
7
|
+
branches: [ "master" ]
|
|
8
|
+
|
|
9
|
+
# Allows you to run this workflow manually from the Actions tab
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
pages: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
|
19
|
+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
|
20
|
+
concurrency:
|
|
21
|
+
group: "pages"
|
|
22
|
+
cancel-in-progress: false
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
# Single deploy job since we're just deploying
|
|
26
|
+
deploy:
|
|
27
|
+
environment:
|
|
28
|
+
name: github-pages
|
|
29
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout
|
|
33
|
+
uses: actions/checkout@v6
|
|
34
|
+
- name: Setup Pages
|
|
35
|
+
uses: actions/configure-pages@v6
|
|
36
|
+
- name: Setup Ruby
|
|
37
|
+
uses: ruby/setup-ruby@v1
|
|
38
|
+
with:
|
|
39
|
+
ruby-version: '4.0'
|
|
40
|
+
- name: Generate Documentation
|
|
41
|
+
run: |
|
|
42
|
+
gem install gem_hadar
|
|
43
|
+
bundle install
|
|
44
|
+
rake doc
|
|
45
|
+
- name: Upload artifact
|
|
46
|
+
uses: actions/upload-pages-artifact@v5
|
|
47
|
+
with:
|
|
48
|
+
path: 'doc'
|
|
49
|
+
- name: Deploy to GitHub Pages
|
|
50
|
+
id: deployment
|
|
51
|
+
uses: actions/deploy-pages@v5
|
data/.rspec
CHANGED
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-10-18 v0.7.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
- Removed support for Ruby **3.0** from the CI test matrix.
|
|
7
|
+
- Removed the vestigial `MonitorMixin` inclusion from `Mize::DefaultCache`, as
|
|
8
|
+
the class is now a plain `Hash` wrapper relying on the GVL for atomicity.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
- Added `Mize.synchronize` API to expose the internal `MUTEX` for atomic
|
|
12
|
+
multi-step operations, with specific handling for `ThreadError` in trap
|
|
13
|
+
contexts.
|
|
14
|
+
- Added a GitHub Actions workflow (`static.yml`) to generate YARD documentation
|
|
15
|
+
and deploy it to GitHub Pages.
|
|
16
|
+
- Added `changelog` configuration to the `Rakefile` to enable automated
|
|
17
|
+
generation of `CHANGES.md`.
|
|
18
|
+
- Added `source_code_uri` metadata to the gemspec.
|
|
19
|
+
|
|
20
|
+
### Improvements
|
|
21
|
+
- Added comprehensive YARD documentation to all library modules (`Mize`,
|
|
22
|
+
`Mize::Memoize`, `Mize::CacheMethods`, `Mize::CacheProtocol`,
|
|
23
|
+
`Mize::Configure`, `Mize::DefaultCache`, `Mize::GlobalClear`,
|
|
24
|
+
`Mize::Railtie`, and `Mize::Reload`).
|
|
25
|
+
- Improved the generated `lib/mize/version.rb` template with module-level and
|
|
26
|
+
constant-level YARD comments.
|
|
27
|
+
- Replaced manual `SimpleCov` initialization in `spec/spec_helper.rb` with
|
|
28
|
+
`gem_hadar/simplecov` and added error handling for missing dependencies.
|
|
29
|
+
- Migrated internal `Mize::MUTEX.synchronize` calls in `global_clear.rb`,
|
|
30
|
+
`memoize.rb`, and `reload.rb` to use the new `Mize.synchronize` API.
|
|
31
|
+
- Removed the obsolete `.utilsrc` configuration file.
|
|
32
|
+
- Removed unused `config/config.yml` and `config/broken_config.yml` files.
|
|
33
|
+
- Corrected the typo "memoziation" to "memoization" in the gem summary and
|
|
34
|
+
description.
|
|
35
|
+
|
|
36
|
+
### Build & CI
|
|
37
|
+
- Updated CI configuration to support Ruby **4.0** by adding `yaml-dev` and
|
|
38
|
+
`openssl-dev` dependencies and updating the bundler setup.
|
|
39
|
+
- Aligned `.all_images.yml` with standard multi-image configuration, including
|
|
40
|
+
`before`/`after` hooks for `Gemfile.lock` cleanup and result echoing.
|
|
41
|
+
- Updated `gem_hadar` development dependency to **2.20.1**.
|
|
42
|
+
- Updated `rubygems_version` in the gemspec to **4.0.22**.
|
|
43
|
+
- Removed `bundle update --all` from the CI script in favor of `bundle install
|
|
44
|
+
--jobs=$(getconf _NPROCESSORS_ONLN)`.
|
|
45
|
+
|
|
46
|
+
### Testing
|
|
47
|
+
- Added `spec/mize_spec.rb` to verify `Mize.synchronize` block return values
|
|
48
|
+
and lock identity.
|
|
49
|
+
- Added `-rspec_helper` to `.rspec` and removed explicit `require
|
|
50
|
+
'spec_helper'` statements from spec files.
|
|
51
|
+
|
|
52
|
+
### Documentation
|
|
53
|
+
- Restructured `README.md` to cover the full API surface, including options
|
|
54
|
+
(`freeze:`, `store_nil:`), keyword arguments, targeted cache clearing, global
|
|
55
|
+
cache, custom cache backends, and Rails integration.
|
|
56
|
+
- Replaced the introductory text with a concise description and added proper
|
|
57
|
+
fenced code blocks with language tags.
|
|
58
|
+
- Removed the redundant "Download" section, folding the repository link into
|
|
59
|
+
the "Repository" section.
|
|
60
|
+
- Added a "Documentation" section to `README.md` linking to the hosted API
|
|
61
|
+
documentation.
|
|
62
|
+
- Updated `README.md` to reflect the removal of `MonitorMixin` from the default
|
|
63
|
+
cache backend description.
|
|
64
|
+
|
|
3
65
|
## 2024-10-17 v0.6.1
|
|
4
66
|
|
|
5
67
|
* Remove deprecated Ruby versions:
|
data/README.md
CHANGED
|
@@ -1,39 +1,31 @@
|
|
|
1
1
|
# Mize
|
|
2
2
|
|
|
3
|
+
Lightweight memoization for Ruby. Annotate any method or function with a
|
|
4
|
+
single `memoize` call and Mize handles result caching, invalidation, and
|
|
5
|
+
Rails-aware reload for you.
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Memoization is a powerful technique for improving the performance of
|
|
7
|
-
computationally expensive functions or methods by caching and reusing
|
|
8
|
-
previously computed results. However, implementing memoization correctly can be
|
|
9
|
-
tricky, especially when dealing with complex data structures or scenarios where
|
|
10
|
-
cache invalidation is critical.
|
|
11
|
-
|
|
12
|
-
## Description
|
|
13
|
-
|
|
14
|
-
The `mize` library provides a simple and flexible way to memoize methods and
|
|
15
|
-
functions in Ruby, making it easier for developers to write high-performance
|
|
16
|
-
code without the need for manual caching implementations. In this README.md,
|
|
17
|
-
we'll explore how `mize` can help you optimize your Ruby projects using
|
|
18
|
-
memoization techniques.
|
|
7
|
+
Works with Ruby 3.1 – 4.0 (tested via `.all_images.yml`).
|
|
19
8
|
|
|
20
9
|
## Installation
|
|
21
10
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
You can also put this line into your `Gemfile`
|
|
11
|
+
```sh
|
|
12
|
+
gem install mize
|
|
13
|
+
```
|
|
27
14
|
|
|
28
|
-
|
|
15
|
+
or add to your `Gemfile`:
|
|
29
16
|
|
|
30
|
-
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'mize'
|
|
19
|
+
```
|
|
31
20
|
|
|
32
21
|
## Usage
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
### Memoize a method
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
Results are cached **per receiver**. Two different objects get independent
|
|
26
|
+
caches even for identical arguments:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
37
29
|
class A
|
|
38
30
|
@@c = 0
|
|
39
31
|
|
|
@@ -45,19 +37,19 @@ end
|
|
|
45
37
|
|
|
46
38
|
a1 = A.new
|
|
47
39
|
a1.foo(23) # => "foo 23 1"
|
|
48
|
-
a1.foo(23) # => "foo 23 1"
|
|
40
|
+
a1.foo(23) # => "foo 23 1" (cached)
|
|
41
|
+
|
|
49
42
|
a2 = A.new
|
|
50
|
-
a2.foo(23) # => "foo 23 2"
|
|
51
|
-
a2.foo(23) # => "foo 23 2"
|
|
52
|
-
a2.mize_cache_clear
|
|
53
|
-
a2.foo(23) # => "foo 23 3"
|
|
54
|
-
a1.foo(23) # => "foo 23 1"
|
|
43
|
+
a2.foo(23) # => "foo 23 2" (different receiver)
|
|
44
|
+
a2.foo(23) # => "foo 23 2" (cached)
|
|
55
45
|
```
|
|
56
46
|
|
|
57
|
-
|
|
58
|
-
this:
|
|
47
|
+
### Memoize a function
|
|
59
48
|
|
|
60
|
-
|
|
49
|
+
Results are cached **globally** — the receiver is ignored. All instances
|
|
50
|
+
share one cache entry per argument set:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
61
53
|
class B
|
|
62
54
|
@@c = 0
|
|
63
55
|
|
|
@@ -69,18 +61,128 @@ end
|
|
|
69
61
|
|
|
70
62
|
b1 = B.new
|
|
71
63
|
b1.foo(23) # => "foo 23 1"
|
|
72
|
-
b1.foo(23) # => "foo 23 1"
|
|
73
64
|
b2 = B.new
|
|
74
|
-
b2.foo(23) # => "foo 23 1"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
b2.foo(23) # => "foo 23 1" (same cache as b1)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Options
|
|
69
|
+
|
|
70
|
+
| Option | Default | Effect |
|
|
71
|
+
|:-------------|:--------|:--------------------------------------|
|
|
72
|
+
| `freeze:` | `false` | Freeze the cached result if possible |
|
|
73
|
+
| `store_nil:` | `true` | Cache `nil` results; `false` recomputes |
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
memoize method:, freeze: true, store_nil: false
|
|
77
|
+
def expensive_query
|
|
78
|
+
# ...
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Keyword arguments are supported and included in the cache key:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
memoize method:
|
|
86
|
+
def lookup(name, limit: 10)
|
|
87
|
+
# ...
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
lookup("flori") # => computes
|
|
91
|
+
lookup("flori") # => cached
|
|
92
|
+
lookup("flori", limit: 5) # => separate cache entry
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Clearing the cache
|
|
96
|
+
|
|
97
|
+
Clear a single method's cache entries by name:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
a1.mize_cache_clear_name(:foo)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Clear **all** cached entries for a receiver (method memoization) or for the
|
|
104
|
+
class (function memoization):
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
a1.mize_cache_clear # method: only a1's cache
|
|
108
|
+
B.mize_cache_clear # function: global shared cache
|
|
79
109
|
```
|
|
80
110
|
|
|
81
|
-
##
|
|
111
|
+
## Global cache clearing
|
|
112
|
+
|
|
113
|
+
`Mize.cache_clear` walks the entire object space for anything including
|
|
114
|
+
`Mize::CacheProtocol` and clears it. Use this in test setups or teardown:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
Mize.cache_clear
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Rails integration
|
|
121
|
+
|
|
122
|
+
When Rails is loaded, Mize registers a railtie that calls
|
|
123
|
+
`Mize.cache_clear` on every `to_prepare` block (i.e. on each code reload in
|
|
124
|
+
development). If your model or class defines a `#reload` method, Mize
|
|
125
|
+
prepends its own `reload` that delegates to `super` and then clears
|
|
126
|
+
memoized entries automatically.
|
|
127
|
+
|
|
128
|
+
## Custom cache backend
|
|
129
|
+
|
|
130
|
+
The default backend is a plain in-memory `Hash`. Swap in your own
|
|
131
|
+
(e.g. Redis, Memcached) by implementing the
|
|
132
|
+
`Mize::CacheProtocol` interface:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
class RedisCache
|
|
136
|
+
include Mize::CacheProtocol
|
|
137
|
+
|
|
138
|
+
def initialize
|
|
139
|
+
@r = Redis.new
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def read(name, options = nil)
|
|
143
|
+
@r.get("mize:#{name}")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def write(name, value, options = nil)
|
|
147
|
+
@r.set("mize:#{name}", Marshal.dump(value))
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def exist?(name, options = nil)
|
|
151
|
+
!@r.get("mize:#{name}").nil?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def delete(name, options = nil)
|
|
155
|
+
@r.del("mize:#{name}")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def clear(options = nil)
|
|
159
|
+
# delete matching keys or flushdb
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def each_name(&block)
|
|
163
|
+
# yield each key
|
|
164
|
+
self
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def prototype
|
|
168
|
+
dup
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
Mize.configure do
|
|
173
|
+
cache RedisCache.new
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Any object that `include Mize::CacheProtocol` and implements `read`,
|
|
178
|
+
`write`, `exist?`, `delete`, `clear`, `each_name`, and `prototype` will
|
|
179
|
+
work.
|
|
180
|
+
|
|
181
|
+
## Documentation
|
|
182
|
+
|
|
183
|
+
Complete API documentation is available at: [GitHub.io](https://flori.github.io/mize/)
|
|
82
184
|
|
|
83
|
-
|
|
185
|
+
## Repository
|
|
84
186
|
|
|
85
187
|
* https://github.com/flori/mize
|
|
86
188
|
|
|
@@ -90,4 +192,4 @@ The homepage of this library is located at
|
|
|
90
192
|
|
|
91
193
|
## License
|
|
92
194
|
|
|
93
|
-
|
|
195
|
+
MIT
|
data/Rakefile
CHANGED
|
@@ -7,11 +7,20 @@ GemHadar do
|
|
|
7
7
|
author 'Florian Frank'
|
|
8
8
|
email 'flori@ping.de'
|
|
9
9
|
homepage "https://github.com/flori/#{name}"
|
|
10
|
-
|
|
10
|
+
metadata 'source_code_uri', homepage
|
|
11
|
+
summary 'Library that provides memoization for methods and functions'
|
|
11
12
|
description "#{summary} for Ruby."
|
|
12
13
|
readme 'README.md'
|
|
13
14
|
licenses << 'MIT'
|
|
14
15
|
|
|
16
|
+
changelog do
|
|
17
|
+
filename 'CHANGES.md'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
github_workflows(
|
|
21
|
+
'static.yml' => { }
|
|
22
|
+
)
|
|
23
|
+
|
|
15
24
|
test_dir 'spec'
|
|
16
25
|
yard_dir 'doc'
|
|
17
26
|
|
|
@@ -19,7 +28,6 @@ GemHadar do
|
|
|
19
28
|
'.AppleDouble', 'tags', '.byebug_history', '.yard*', 'yard', 'doc',
|
|
20
29
|
'errors.lst'
|
|
21
30
|
|
|
22
|
-
|
|
23
31
|
development_dependency 'rake'
|
|
24
32
|
development_dependency 'simplecov'
|
|
25
33
|
development_dependency 'rspec'
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.7.0
|
data/lib/mize/cache_methods.rb
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
require 'mize/cache_protocol'
|
|
2
2
|
require 'mize/default_cache'
|
|
3
3
|
|
|
4
|
+
# Per-object memoization bookkeeping.
|
|
5
|
+
#
|
|
6
|
+
# Mixed into any module that calls +memoize+. Each *object* gets its own
|
|
7
|
+
# lazily-created cache instance (a copy of +Mize.default_cache+), and the
|
|
8
|
+
# key-building helpers below produce stable, argument-sensitive cache keys.
|
|
9
|
+
#
|
|
10
|
+
# State is deliberately kept per-object: two instances of the same memoized
|
|
11
|
+
# class do not share a cache, which is the expected semantics for instance
|
|
12
|
+
# methods.
|
|
4
13
|
module Mize::CacheMethods
|
|
5
14
|
# Clear cached values for all methods/functions of this object.
|
|
15
|
+
#
|
|
16
|
+
# @return [self] The receiver, for chaining.
|
|
6
17
|
def mize_cache_clear
|
|
7
18
|
__mize_cache__.clear
|
|
8
19
|
self
|
|
9
20
|
end
|
|
10
21
|
|
|
11
22
|
# Clear all cached results for the method/function +name+.
|
|
23
|
+
#
|
|
24
|
+
# @param name [String, Symbol] The name of the memoized method/function.
|
|
25
|
+
# @return [self] The receiver, for chaining.
|
|
12
26
|
def mize_cache_clear_name(name)
|
|
13
27
|
name = build_key_prefix(name)
|
|
14
28
|
__mize_cache__.each_name do |n|
|
|
@@ -20,11 +34,19 @@ module Mize::CacheMethods
|
|
|
20
34
|
private
|
|
21
35
|
|
|
22
36
|
# Set the cache object to +cache+.
|
|
37
|
+
#
|
|
38
|
+
# @param cache [Mize::CacheProtocol] The cache backend to store in.
|
|
39
|
+
# @return [Mize::CacheProtocol] The cache backend now stored in +self+.
|
|
23
40
|
def __mize_cache__=(cache)
|
|
24
41
|
@__mize_cache__ = cache
|
|
25
42
|
end
|
|
26
43
|
|
|
27
|
-
# Return the cache object.
|
|
44
|
+
# Return the cache object, creating one on first use.
|
|
45
|
+
#
|
|
46
|
+
# The first access makes a fresh copy of +Mize.default_cache+ (via
|
|
47
|
+
# +prototype+ / "dup") so that each object holds an independent store.
|
|
48
|
+
#
|
|
49
|
+
# @return [Mize::CacheProtocol] This object's cache backend.
|
|
28
50
|
def __mize_cache__
|
|
29
51
|
if defined?(@__mize_cache__)
|
|
30
52
|
@__mize_cache__
|
|
@@ -34,16 +56,35 @@ module Mize::CacheMethods
|
|
|
34
56
|
end
|
|
35
57
|
|
|
36
58
|
# Build a key prefix for +name+.
|
|
59
|
+
#
|
|
60
|
+
# @param name [String, Symbol] The method/function name.
|
|
61
|
+
# @return [String] The namespaced prefix, e.g. +"mize/foo"+.
|
|
37
62
|
def build_key_prefix(name)
|
|
38
63
|
"mize/#{name}"
|
|
39
64
|
end
|
|
40
65
|
|
|
41
66
|
# Build a +name+ prefixed key for the arguments +args+.
|
|
67
|
+
#
|
|
68
|
+
# The argument list is +Marshal#+d so structurally-identical arguments
|
|
69
|
+
# always collide on the same key regardless of object identity.
|
|
70
|
+
#
|
|
71
|
+
# @param name [String, Symbol] The method/function name.
|
|
72
|
+
# @param args [Array] The arguments to key on (positional, then splatted
|
|
73
|
+
# keywords).
|
|
74
|
+
# @return [String] The full cache key.
|
|
42
75
|
def build_key(name, *args)
|
|
43
76
|
"#{build_key_prefix(name)}/#{Marshal.dump(args)}"
|
|
44
77
|
end
|
|
45
78
|
|
|
46
79
|
# Apply the visibility of method +id+ to the wrapper method of this method.
|
|
80
|
+
#
|
|
81
|
+
# The block is expected to (re)define +id+ as +public+; afterwards the
|
|
82
|
+
# original visibility is re-applied in the +ensure+ clause so a memoized
|
|
83
|
+
# +private+ method stays +private+.
|
|
84
|
+
#
|
|
85
|
+
# @param id [String, Symbol] The name of the method whose visibility to
|
|
86
|
+
# mirror.
|
|
87
|
+
# @yield The block that performs the +define_method+ of +id+.
|
|
47
88
|
def memoize_apply_visibility(id)
|
|
48
89
|
visibility = instance_eval do
|
|
49
90
|
case
|
data/lib/mize/cache_protocol.rb
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
|
+
# Marker interface for Mize-compatible cache backends.
|
|
2
|
+
#
|
|
3
|
+
# A cache backend "is a" +Mize::CacheProtocol+ simply by including this
|
|
4
|
+
# module. No methods are mandated — the module exists solely as a type tag
|
|
5
|
+
# that Mize can match against.
|
|
6
|
+
#
|
|
7
|
+
# The primary consumer is +Mize.cache_clear+ (see +Mize::GlobalClear+), which
|
|
8
|
+
# walks the heap with +ObjectSpace.each_object(Mize::CacheProtocol)+ to locate
|
|
9
|
+
# every live cache instance and clear it. This is what turns a per-object
|
|
10
|
+
# cache into a globally clearable one without an explicit registry.
|
|
11
|
+
#
|
|
12
|
+
# @example A minimal compliant backend
|
|
13
|
+
# class MyCache
|
|
14
|
+
# include Mize::CacheProtocol
|
|
15
|
+
#
|
|
16
|
+
# def read(name, options = nil) ... end
|
|
17
|
+
# def write(name, value, options = nil) ... end
|
|
18
|
+
# def clear(options = nil) ... end
|
|
19
|
+
# end
|
|
1
20
|
module Mize::CacheProtocol
|
|
2
21
|
end
|
data/lib/mize/configure.rb
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
|
+
# Configuration helpers mixed in / extended onto the top-level +Mize+ object.
|
|
2
|
+
#
|
|
3
|
+
# Provides the block-based +Mize.configure+ DSL and the +default_cache+
|
|
4
|
+
# accessor so users can swap the global cache backend.
|
|
1
5
|
module Mize::Configure
|
|
6
|
+
# The global default cache backend.
|
|
7
|
+
#
|
|
8
|
+
# Every memoized object that has not been given an explicit cache
|
|
9
|
+
# instantiates a fresh copy (via +prototype+ / "dup") of this backend.
|
|
10
|
+
# Reassign it to route all new memoized calls through a different store
|
|
11
|
+
# (Redis, Memcached, ...).
|
|
12
|
+
#
|
|
13
|
+
# @return [Mize::CacheProtocol] The current global default cache backend.
|
|
2
14
|
attr_accessor :default_cache
|
|
3
15
|
|
|
4
16
|
# Set the default cache object to +cache+.
|
|
17
|
+
#
|
|
18
|
+
# Intended for use inside a +Mize.configure {}+ block.
|
|
19
|
+
#
|
|
20
|
+
# @param cache [Mize::CacheProtocol] The backend to install as the default.
|
|
21
|
+
# @return [Mize::CacheProtocol] The backend now installed.
|
|
5
22
|
def cache(cache)
|
|
6
23
|
self.default_cache = cache
|
|
7
24
|
end
|
|
8
25
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
26
|
+
# Configure Mize by evaluating +block+ in the context of the +Mize+ object.
|
|
27
|
+
#
|
|
28
|
+
# Inside the block, +cache+ (and the +default_cache+ accessor) are called
|
|
29
|
+
# directly on +Mize+, so no receiver is required.
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# Mize.configure do
|
|
33
|
+
# cache MyRedisCache.new
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# @yield The configuration block, evaluated via +instance_eval+.
|
|
37
|
+
# @return [Object] The return value of +block+.
|
|
11
38
|
def configure(&block)
|
|
12
39
|
instance_eval(&block)
|
|
13
40
|
end
|
|
@@ -17,4 +44,5 @@ module Mize
|
|
|
17
44
|
extend Mize::Configure
|
|
18
45
|
end
|
|
19
46
|
|
|
47
|
+
# Install the built-in in-memory backend as the global default.
|
|
20
48
|
Mize.default_cache = Mize::DefaultCache.new
|
data/lib/mize/default_cache.rb
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# The default in-memory cache backend.
|
|
2
|
+
#
|
|
3
|
+
# A plain +Hash+ wrapper that implements just enough of the Mize cache
|
|
4
|
+
# protocol to back memoization out of the box: +read+ / +write+ /
|
|
5
|
+
# +exist?+ / +delete+ / +clear+ / +each_name+.
|
|
6
|
+
#
|
|
7
|
+
# Individual operations are atomic under the GVL. For multi-step
|
|
8
|
+
# atomicity (e.g. check→compute→write), wrap the call in
|
|
9
|
+
# +Mize.synchronize+.
|
|
10
|
+
#
|
|
11
|
+
# Because it is backed by a plain +Hash+, entries are never evicted until you
|
|
12
|
+
# clear the cache explicitly (or the object is garbage-collected). For
|
|
13
|
+
# persistent or evicting behaviour, swap in a different
|
|
14
|
+
# +Mize::CacheProtocol+ implementation via +Mize.configure+.
|
|
3
15
|
class Mize::DefaultCache
|
|
4
16
|
include Mize::CacheProtocol
|
|
5
|
-
include MonitorMixin
|
|
6
17
|
|
|
18
|
+
# Create an empty cache.
|
|
19
|
+
#
|
|
20
|
+
# @return [Mize::DefaultCache] A new, empty cache instance.
|
|
7
21
|
def initialize
|
|
8
22
|
@data = {}
|
|
9
23
|
end
|
|
10
24
|
|
|
11
|
-
# Clear the cache by removing all entries from the cache
|
|
25
|
+
# Clear the cache by removing all entries from the cache.
|
|
26
|
+
#
|
|
27
|
+
# @return [self] The receiver, for chaining.
|
|
12
28
|
def clear(options = nil)
|
|
13
29
|
@data.clear
|
|
14
30
|
self
|
|
@@ -17,16 +33,18 @@ class Mize::DefaultCache
|
|
|
17
33
|
# Determine whether a cache entry exists in this cache.
|
|
18
34
|
#
|
|
19
35
|
# @param name [String] The name of the cache entry to check.
|
|
36
|
+
# @param options [Hash, nil] Optional backend-specific options (ignored).
|
|
20
37
|
# @return [Boolean] Whether or not the cache entry exists.
|
|
21
38
|
def exist?(name, options = nil)
|
|
22
39
|
@data.key?(name)
|
|
23
40
|
end
|
|
24
41
|
|
|
25
42
|
# Read a value from the cache by name. If the entry does not exist in the
|
|
26
|
-
# cache, it will return nil
|
|
43
|
+
# cache, it will return +nil+.
|
|
27
44
|
#
|
|
28
45
|
# @param name [String] The name of the cache entry to read.
|
|
29
|
-
# @
|
|
46
|
+
# @param options [Hash, nil] Optional backend-specific options (ignored).
|
|
47
|
+
# @return [Object, nil] The value stored in the cache for +name+, or +nil+.
|
|
30
48
|
def read(name, options = nil)
|
|
31
49
|
@data.fetch(name, nil)
|
|
32
50
|
end
|
|
@@ -35,34 +53,49 @@ class Mize::DefaultCache
|
|
|
35
53
|
# exists in the cache, it will be overwritten.
|
|
36
54
|
#
|
|
37
55
|
# @param name [String] The name of the cache entry to write.
|
|
38
|
-
# @param value [Object] The value to store in the cache for
|
|
39
|
-
# @
|
|
56
|
+
# @param value [Object] The value to store in the cache for +name+.
|
|
57
|
+
# @param options [Hash, nil] Optional backend-specific options (ignored).
|
|
58
|
+
# @return [Object] The value now stored in the cache.
|
|
40
59
|
def write(name, value, options = nil)
|
|
41
60
|
@data.store(name, value)
|
|
42
61
|
end
|
|
43
62
|
|
|
44
|
-
# Delete a cache entry by name. If
|
|
63
|
+
# Delete a cache entry by name. If an entry does not exist in the cache, it
|
|
45
64
|
# will do nothing.
|
|
46
65
|
#
|
|
47
66
|
# @param name [String] The name of the cache entry to delete.
|
|
48
|
-
# @
|
|
67
|
+
# @param options [Hash, nil] Optional backend-specific options (ignored).
|
|
68
|
+
# @return [Object, nil] The value stored in the cache before deletion, or
|
|
69
|
+
# +nil+ if the entry was absent.
|
|
49
70
|
def delete(name, options = nil)
|
|
50
71
|
@data.delete(name)
|
|
51
72
|
end
|
|
52
73
|
|
|
53
74
|
# Each name of the cache is yielded to the block.
|
|
54
|
-
#
|
|
75
|
+
#
|
|
76
|
+
# @yieldparam name [String] Each cache entry name, in turn.
|
|
77
|
+
# @return [self] The receiver, for chaining.
|
|
55
78
|
def each_name(&block)
|
|
56
79
|
@data.each_key(&block)
|
|
57
80
|
self
|
|
58
81
|
end
|
|
59
82
|
|
|
60
83
|
# Initialize a duplicate of this cache.
|
|
61
|
-
#
|
|
84
|
+
#
|
|
85
|
+
# The underlying store is shallow-copied so the original and the duplicate
|
|
86
|
+
# diverge immediately (this is what +prototype+ relies on to hand each
|
|
87
|
+
# memoized object its own independent cache).
|
|
88
|
+
#
|
|
89
|
+
# @param other [Mize::DefaultCache] The cache to initialize a duplicate of.
|
|
90
|
+
# @return [Mize::DefaultCache] +self+, now a copy of +other+.
|
|
62
91
|
def initialize_dup(other)
|
|
63
92
|
super
|
|
64
93
|
other.instance_variable_set :@data, @data.dup
|
|
65
94
|
end
|
|
66
95
|
|
|
96
|
+
# Create a shallow copy of this cache; the public name for +dup+.
|
|
97
|
+
#
|
|
98
|
+
# @return [Mize::DefaultCache] A new cache with an independent copy of the
|
|
99
|
+
# entries.
|
|
67
100
|
alias prototype dup
|
|
68
101
|
end
|
data/lib/mize/global_clear.rb
CHANGED
|
@@ -2,14 +2,28 @@ module Mize
|
|
|
2
2
|
class << self
|
|
3
3
|
|
|
4
4
|
# Clear all memoization caches at once.
|
|
5
|
+
#
|
|
6
|
+
# Walks every live +Mize::CacheProtocol+ instance in the process (see
|
|
7
|
+
# +each_cache+) and invokes +clear+ on each, inside +Mize.synchronize+ so
|
|
8
|
+
# the sweep is atomic with respect to concurrent reads/writes.
|
|
9
|
+
#
|
|
10
|
+
# @return [Mize] The +Mize+ module itself.
|
|
5
11
|
def cache_clear
|
|
6
12
|
each_cache(&:clear)
|
|
7
13
|
end
|
|
8
14
|
|
|
9
15
|
private
|
|
10
16
|
|
|
17
|
+
# Yield every live +Mize::CacheProtocol+ object to the block.
|
|
18
|
+
#
|
|
19
|
+
# The sweep runs under +Mize.synchronize+ so it cannot observe a
|
|
20
|
+
# half-cleared cache. Objects only appear here because they
|
|
21
|
+
# +include Mize::CacheProtocol+.
|
|
22
|
+
#
|
|
23
|
+
# @yield [cache] Yields each live cache backend in turn.
|
|
24
|
+
# @return [Mize] The +Mize+ module itself.
|
|
11
25
|
def each_cache
|
|
12
|
-
Mize
|
|
26
|
+
Mize.synchronize do
|
|
13
27
|
for cache in ObjectSpace.each_object(Mize::CacheProtocol)
|
|
14
28
|
yield cache
|
|
15
29
|
end
|
data/lib/mize/memoize.rb
CHANGED
|
@@ -2,18 +2,43 @@ require 'mize/cache_methods'
|
|
|
2
2
|
require 'mize/reload'
|
|
3
3
|
|
|
4
4
|
module Mize
|
|
5
|
+
# Memoization entry point, prepended into every +::Module+ by +mize.rb+.
|
|
6
|
+
#
|
|
7
|
+
# Including this module (or relying on the global +prepend+) exposes
|
|
8
|
+
# +memoize+, +mize_cache_clear+ and +mize_cache_clear_name+ on the
|
|
9
|
+
# receiver. The heavy lifting is delegated to +Mize::CacheMethods+ for
|
|
10
|
+
# key/state management and +Mize::Reload+ for cache invalidation.
|
|
5
11
|
module Memoize
|
|
6
12
|
include CacheMethods
|
|
7
13
|
|
|
8
|
-
# Memoize either a +method+ or a +function+.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
14
|
+
# Memoize either a +method+ or a +function+.
|
|
15
|
+
#
|
|
16
|
+
# In the +method+ case the memoized results do NOT ONLY depend on the
|
|
17
|
+
# arguments, but ALSO on the object the method is called on (each object
|
|
18
|
+
# keeps its own cache). In the +function+ case the memoized results ONLY
|
|
19
|
+
# depend on the arguments, and a single shared cache backs every call.
|
|
20
|
+
#
|
|
21
|
+
# You must specify exactly one of +method:+ or +function:+; passing both
|
|
22
|
+
# or neither raises +ArgumentError+.
|
|
23
|
+
#
|
|
24
|
+
# @param method [String, Symbol, nil] The instance method to wrap.
|
|
25
|
+
# @param function [String, Symbol, nil] The bare/stateless function to wrap.
|
|
26
|
+
# @param freeze [true, false] When true, successfully computed results are
|
|
27
|
+
# frozen (if they respond to +freeze+) to make them immutable.
|
|
28
|
+
# @param store_nil [true, false] When false, +nil+ results are not stored
|
|
29
|
+
# and are recomputed every call; defaults to +true+ (store +nil+).
|
|
30
|
+
# @raise [ArgumentError] If both or neither of +method:+/+function:+ are
|
|
31
|
+
# given.
|
|
32
|
+
# @return [Symbol] The (stringified) symbol of the wrapped method id.
|
|
33
|
+
# @example Memoizing an instance method
|
|
34
|
+
# class Foo
|
|
35
|
+
# include Mize::Memoize
|
|
36
|
+
#
|
|
37
|
+
# def expensive(x) x * 2 end
|
|
38
|
+
# memoize method: :expensive, freeze: true
|
|
39
|
+
# end
|
|
15
40
|
def memoize(method: nil, function: nil, freeze: false, store_nil: true)
|
|
16
|
-
Mize
|
|
41
|
+
Mize.synchronize do
|
|
17
42
|
if method && function
|
|
18
43
|
raise ArgumentError, 'memoize a method xor a function'
|
|
19
44
|
elsif method
|
|
@@ -31,6 +56,19 @@ module Mize
|
|
|
31
56
|
class << self
|
|
32
57
|
private
|
|
33
58
|
|
|
59
|
+
# Compute the real result of a memoized call.
|
|
60
|
+
#
|
|
61
|
+
# Binds +orig_method+ to +context+ and invokes it with +args+; when
|
|
62
|
+
# running under +$DEBUG+ a diagnostic line is emitted to +STDERR+.
|
|
63
|
+
#
|
|
64
|
+
# @param method_id [Symbol] The wrapped method name (diagnostics only).
|
|
65
|
+
# @param orig_method [UnboundMethod] The original, unwrapped method.
|
|
66
|
+
# @param key [String] The cache key for this call (diagnostics only).
|
|
67
|
+
# @param context [Object] The object the method is bound to.
|
|
68
|
+
# @param args [Array] The positional arguments.
|
|
69
|
+
# @param kargs [Hash] The keyword arguments.
|
|
70
|
+
# @param freeze [true, false] Whether to freeze the resulting value.
|
|
71
|
+
# @return [Object] The (possibly frozen) computed result.
|
|
34
72
|
def compute_result(method_id, orig_method, key, context, args, kargs, freeze)
|
|
35
73
|
result = orig_method.bind(context).call(*args)
|
|
36
74
|
if $DEBUG
|
|
@@ -43,7 +81,25 @@ module Mize
|
|
|
43
81
|
end
|
|
44
82
|
end
|
|
45
83
|
|
|
46
|
-
|
|
84
|
+
# Wrap +method_id+ with a memoizing closure.
|
|
85
|
+
#
|
|
86
|
+
# Idempotency: a +[self, method_id, function]+ target already present in
|
|
87
|
+
# +Mize.wrapped+ with identical +freeze+ / +store_nil+ options is a
|
|
88
|
+
# no-op; conflicting options raise +ArgumentError+.
|
|
89
|
+
#
|
|
90
|
+
# For instance methods (+function: false+) the receiver gets its own
|
|
91
|
+
# per-object cache via +Mize::CacheMethods+ and +Mize::Reload+ is
|
|
92
|
+
# prepended so +reload+ invalidates entries. For bare functions a single
|
|
93
|
+
# shared cache is used instead.
|
|
94
|
+
#
|
|
95
|
+
# @param method_id [String, Symbol] The name of the method to wrap.
|
|
96
|
+
# @param freeze [true, false] Forwarded to the wrapper's result handling.
|
|
97
|
+
# @param function [true, false] Whether +method_id+ is a bare function.
|
|
98
|
+
# @param store_nil [true, false] Whether +nil+ results should be cached.
|
|
99
|
+
# @raise [ArgumentError] If the target is already wrapped with different
|
|
100
|
+
# +freeze+ / +store_nil+ options.
|
|
101
|
+
# @return [Symbol] The stringified symbol of the wrapped method id.
|
|
102
|
+
def wrap_method(method_id, freeze: false, function: false, store_nil: true)
|
|
47
103
|
verbose, $VERBOSE = $VERBOSE, nil
|
|
48
104
|
if already_wrapped = Mize.wrapped[ [ self, method_id, function ] ]
|
|
49
105
|
if already_wrapped == [ freeze, store_nil ]
|
data/lib/mize/railtie.rb
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
module Mize
|
|
2
|
+
# Rails integration.
|
|
3
|
+
#
|
|
4
|
+
# Hooks into the Rails preparation lifecycle so that every code reload
|
|
5
|
+
# clears all Mize caches via +Mize.cache_clear+, preventing stale memoized
|
|
6
|
+
# results from surviving a development-environment class reload.
|
|
7
|
+
#
|
|
8
|
+
# Loaded automatically by +mize.rb+ only when +Rails+ is defined.
|
|
2
9
|
class Railtie < Rails::Railtie
|
|
3
10
|
config.to_prepare do
|
|
4
11
|
Mize.cache_clear
|
data/lib/mize/reload.rb
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
# Reload/invalidation hook, prepended into any module that memoizes an
|
|
2
|
+
# instance method.
|
|
3
|
+
#
|
|
4
|
+
# When a class defines (or inherits) its own +reload+, Mize wraps it so the
|
|
5
|
+
# cache is guaranteed to be cleared alongside whatever the user's +reload+
|
|
6
|
+
# does, keeping memoized state consistent after a full object reload.
|
|
1
7
|
module Mize::Reload
|
|
2
|
-
#
|
|
3
|
-
#
|
|
8
|
+
# Run the parent +reload+ (if any), then clear Mize's bookkeeping and the
|
|
9
|
+
# object's memoization cache.
|
|
10
|
+
#
|
|
11
|
+
# The whole sequence is serialized on +Mize.synchronize+. +Mize.wrapped+ is
|
|
12
|
+
# reset so a re-memoization with different options is allowed, and
|
|
13
|
+
# +mize_cache_clear+ drops every cached value for this object.
|
|
14
|
+
#
|
|
15
|
+
# @return [Object] The result of the parent +reload+ if one was defined,
|
|
16
|
+
# otherwise +self+.
|
|
4
17
|
def reload(*)
|
|
5
|
-
Mize
|
|
18
|
+
Mize.synchronize do
|
|
6
19
|
result = self
|
|
7
20
|
if defined? super
|
|
8
21
|
result = super
|
data/lib/mize/version.rb
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
# Version information for the Mize gem.
|
|
2
|
+
#
|
|
3
|
+
# This module is auto-generated by GemHadar and exposes the
|
|
4
|
+
# current release version as a set of constants. Do not edit it
|
|
5
|
+
# by hand; it is rewritten on every `rake version` invocation.
|
|
1
6
|
module Mize
|
|
2
|
-
# Mize
|
|
3
|
-
VERSION = '0.
|
|
7
|
+
# The current version of the Mize gem as a String.
|
|
8
|
+
VERSION = '0.7.0'
|
|
4
9
|
VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
|
|
5
10
|
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
|
6
11
|
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
data/lib/mize.rb
CHANGED
|
@@ -1,10 +1,71 @@
|
|
|
1
1
|
require 'thread'
|
|
2
2
|
|
|
3
|
+
# Mize — a minimal, dependency-free memoization toolkit for Ruby.
|
|
4
|
+
#
|
|
5
|
+
# Mize lets you wrap any method (or a bare, stateless "function") so that its
|
|
6
|
+
# result is cached and reused for identical arguments. It ships with an
|
|
7
|
+
# in-memory +Mize::DefaultCache+ but accepts any object that includes
|
|
8
|
+
# +Mize::CacheProtocol+, so you can plug in Redis, Memcached, and friends.
|
|
9
|
+
#
|
|
10
|
+
# @example Wrapping a method
|
|
11
|
+
# class Greeter
|
|
12
|
+
# include Mize::Memoize
|
|
13
|
+
#
|
|
14
|
+
# def expensive(name)
|
|
15
|
+
# # ...
|
|
16
|
+
# end
|
|
17
|
+
# memoize method: :expensive
|
|
18
|
+
# end
|
|
3
19
|
module Mize
|
|
20
|
+
# Process-wide lock guarding all cache and wrapping mutations.
|
|
21
|
+
#
|
|
22
|
+
# Every operation that mutates shared Mize state (+memoize+,
|
|
23
|
+
# +Mize.cache_clear+, +Mize::Reload#reload+) serialises on this mutex, so
|
|
24
|
+
# the toolkit is safe to use from multiple threads.
|
|
4
25
|
MUTEX = Mutex.new
|
|
5
26
|
|
|
6
27
|
class << self
|
|
28
|
+
# Bookkeeping of already-wrapped targets.
|
|
29
|
+
#
|
|
30
|
+
# Maps +[module, method_id, function]+ => +[freeze, store_nil]+ so that
|
|
31
|
+
# duplicate +memoize+ calls can be detected and conflicting options
|
|
32
|
+
# rejected. Cleared on +Mize::Reload#reload+.
|
|
7
33
|
attr_accessor :wrapped
|
|
34
|
+
|
|
35
|
+
# Acquire the global Mize mutex for the duration of +block+.
|
|
36
|
+
#
|
|
37
|
+
# Exposes the internal +MUTEX+ under a stable API so callers can make
|
|
38
|
+
# a sequence of memoized calls atomic with respect to other threads:
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# Mize.synchronize do
|
|
42
|
+
# obj.foo(42)
|
|
43
|
+
# obj.bar(42)
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# The hot path (individual memoized calls) is lock-free by design; use
|
|
47
|
+
# this when you need the check→compute→write cycle to be indivisible.
|
|
48
|
+
#
|
|
49
|
+
# MRI forbids +Mutex#synchronize+ from a trap context (signal handlers
|
|
50
|
+
# and GC finalizers), raising +ThreadError: can't be called from trap
|
|
51
|
+
# context+. This is an intentional, still-current design decision (see
|
|
52
|
+
# DevMeeting-2025-07-10 on
|
|
53
|
+
# https://bugs.ruby-lang.org/issues/19473), not a bug being patched, so
|
|
54
|
+
# this method rescues that specific error and runs +block+ without the
|
|
55
|
+
# lock rather than raising into a finalizer. Other +ThreadError+s (e.g.
|
|
56
|
+
# recursive locking) are re-raised.
|
|
57
|
+
#
|
|
58
|
+
# @yield Executes the block while holding the mutex.
|
|
59
|
+
# @return [Object] The return value of +block+.
|
|
60
|
+
def synchronize(&block)
|
|
61
|
+
MUTEX.synchronize(&block)
|
|
62
|
+
rescue ThreadError => e
|
|
63
|
+
if e.message.include?("trap context")
|
|
64
|
+
block.()
|
|
65
|
+
else
|
|
66
|
+
raise e
|
|
67
|
+
end
|
|
68
|
+
end
|
|
8
69
|
end
|
|
9
70
|
self.wrapped = {}
|
|
10
71
|
end
|
|
@@ -15,6 +76,8 @@ require 'mize/configure'
|
|
|
15
76
|
require 'mize/global_clear'
|
|
16
77
|
require 'mize/railtie' if defined? Rails
|
|
17
78
|
|
|
79
|
+
# Inject +memoize+ into every module so any class can opt in with a single
|
|
80
|
+
# +include Mize::Memoize+.
|
|
18
81
|
class ::Module
|
|
19
82
|
prepend Mize::Memoize
|
|
20
83
|
end
|
data/mize.gemspec
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: mize 0.
|
|
2
|
+
# stub: mize 0.7.0 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "mize".freeze
|
|
6
|
-
s.version = "0.
|
|
6
|
+
s.version = "0.7.0".freeze
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
|
+
s.metadata = { "source_code_uri" => "https://github.com/flori/mize" } if s.respond_to? :metadata=
|
|
9
10
|
s.require_paths = ["lib".freeze]
|
|
10
11
|
s.authors = ["Florian Frank".freeze]
|
|
11
|
-
s.date = "
|
|
12
|
-
s.description = "Library that provides
|
|
12
|
+
s.date = "1980-01-02"
|
|
13
|
+
s.description = "Library that provides memoization for methods and functions for Ruby.".freeze
|
|
13
14
|
s.email = "flori@ping.de".freeze
|
|
14
15
|
s.extra_rdoc_files = ["README.md".freeze, "lib/mize.rb".freeze, "lib/mize/cache_methods.rb".freeze, "lib/mize/cache_protocol.rb".freeze, "lib/mize/configure.rb".freeze, "lib/mize/default_cache.rb".freeze, "lib/mize/global_clear.rb".freeze, "lib/mize/memoize.rb".freeze, "lib/mize/railtie.rb".freeze, "lib/mize/reload.rb".freeze, "lib/mize/version.rb".freeze]
|
|
15
|
-
s.files = [".all_images.yml".freeze, ".
|
|
16
|
+
s.files = [".all_images.yml".freeze, ".github/workflows/static.yml".freeze, ".gitignore".freeze, ".rspec".freeze, ".travis.yml".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "lib/mize.rb".freeze, "lib/mize/cache_methods.rb".freeze, "lib/mize/cache_protocol.rb".freeze, "lib/mize/configure.rb".freeze, "lib/mize/default_cache.rb".freeze, "lib/mize/global_clear.rb".freeze, "lib/mize/memoize.rb".freeze, "lib/mize/railtie.rb".freeze, "lib/mize/reload.rb".freeze, "lib/mize/version.rb".freeze, "mize.gemspec".freeze, "spec/mize/global_clear_spec.rb".freeze, "spec/mize/memoize_method_spec.rb".freeze, "spec/mize/memoize_reload_spec.rb".freeze, "spec/mize/version_spec.rb".freeze, "spec/mize_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
16
17
|
s.homepage = "https://github.com/flori/mize".freeze
|
|
17
18
|
s.licenses = ["MIT".freeze]
|
|
18
|
-
s.rdoc_options = ["--title".freeze, "Mize - Library that provides
|
|
19
|
+
s.rdoc_options = ["--title".freeze, "Mize - Library that provides memoization for methods and functions".freeze, "--main".freeze, "README.md".freeze]
|
|
19
20
|
s.required_ruby_version = Gem::Requirement.new(">= 2".freeze)
|
|
20
|
-
s.rubygems_version = "
|
|
21
|
-
s.summary = "Library that provides
|
|
22
|
-
s.test_files = ["spec/mize/global_clear_spec.rb".freeze, "spec/mize/memoize_method_spec.rb".freeze, "spec/mize/memoize_reload_spec.rb".freeze, "spec/mize/version_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
21
|
+
s.rubygems_version = "4.0.22".freeze
|
|
22
|
+
s.summary = "Library that provides memoization for methods and functions".freeze
|
|
23
|
+
s.test_files = ["spec/mize/global_clear_spec.rb".freeze, "spec/mize/memoize_method_spec.rb".freeze, "spec/mize/memoize_reload_spec.rb".freeze, "spec/mize/version_spec.rb".freeze, "spec/mize_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
23
24
|
|
|
24
25
|
s.specification_version = 4
|
|
25
26
|
|
|
26
|
-
s.add_development_dependency(%q<gem_hadar>.freeze, ["
|
|
27
|
+
s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.20.1".freeze])
|
|
27
28
|
s.add_development_dependency(%q<rake>.freeze, [">= 0".freeze])
|
|
28
29
|
s.add_development_dependency(%q<simplecov>.freeze, [">= 0".freeze])
|
|
29
30
|
s.add_development_dependency(%q<rspec>.freeze, [">= 0".freeze])
|
data/spec/mize/version_spec.rb
CHANGED
data/spec/mize_spec.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
describe Mize do
|
|
2
|
+
describe '.synchronize' do
|
|
3
|
+
it 'executes the block and returns its value' do
|
|
4
|
+
expect(described_class.synchronize { 42 }).to eq(42)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
it 'is the same lock object as Mize::MUTEX' do
|
|
8
|
+
expect {
|
|
9
|
+
Mize::MUTEX.synchronize { Mize.synchronize { :x } }
|
|
10
|
+
}.to raise_error(ThreadError)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
require 'simplecov'
|
|
3
|
-
SimpleCov.start
|
|
4
|
-
|
|
5
|
-
end
|
|
1
|
+
begin
|
|
2
|
+
require 'gem_hadar/simplecov'
|
|
3
|
+
GemHadar::SimpleCov.start
|
|
4
|
+
rescue LoadError
|
|
6
5
|
end
|
|
7
6
|
require 'rspec'
|
|
8
7
|
require 'mize'
|
metadata
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Florian Frank
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: gem_hadar
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
|
-
- - "
|
|
16
|
+
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: 2.20.1
|
|
20
19
|
type: :development
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
|
-
- - "
|
|
23
|
+
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
25
|
+
version: 2.20.1
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: rake
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,7 +107,7 @@ dependencies:
|
|
|
108
107
|
- - ">="
|
|
109
108
|
- !ruby/object:Gem::Version
|
|
110
109
|
version: '0'
|
|
111
|
-
description: Library that provides
|
|
110
|
+
description: Library that provides memoization for methods and functions for Ruby.
|
|
112
111
|
email: flori@ping.de
|
|
113
112
|
executables: []
|
|
114
113
|
extensions: []
|
|
@@ -126,18 +125,16 @@ extra_rdoc_files:
|
|
|
126
125
|
- lib/mize/version.rb
|
|
127
126
|
files:
|
|
128
127
|
- ".all_images.yml"
|
|
128
|
+
- ".github/workflows/static.yml"
|
|
129
129
|
- ".gitignore"
|
|
130
130
|
- ".rspec"
|
|
131
131
|
- ".travis.yml"
|
|
132
|
-
- ".utilsrc"
|
|
133
132
|
- CHANGES.md
|
|
134
133
|
- Gemfile
|
|
135
134
|
- LICENSE
|
|
136
135
|
- README.md
|
|
137
136
|
- Rakefile
|
|
138
137
|
- VERSION
|
|
139
|
-
- config/broken_config.yml
|
|
140
|
-
- config/config.yml
|
|
141
138
|
- lib/mize.rb
|
|
142
139
|
- lib/mize/cache_methods.rb
|
|
143
140
|
- lib/mize/cache_protocol.rb
|
|
@@ -153,15 +150,16 @@ files:
|
|
|
153
150
|
- spec/mize/memoize_method_spec.rb
|
|
154
151
|
- spec/mize/memoize_reload_spec.rb
|
|
155
152
|
- spec/mize/version_spec.rb
|
|
153
|
+
- spec/mize_spec.rb
|
|
156
154
|
- spec/spec_helper.rb
|
|
157
155
|
homepage: https://github.com/flori/mize
|
|
158
156
|
licenses:
|
|
159
157
|
- MIT
|
|
160
|
-
metadata:
|
|
161
|
-
|
|
158
|
+
metadata:
|
|
159
|
+
source_code_uri: https://github.com/flori/mize
|
|
162
160
|
rdoc_options:
|
|
163
161
|
- "--title"
|
|
164
|
-
- Mize - Library that provides
|
|
162
|
+
- Mize - Library that provides memoization for methods and functions
|
|
165
163
|
- "--main"
|
|
166
164
|
- README.md
|
|
167
165
|
require_paths:
|
|
@@ -177,13 +175,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
177
175
|
- !ruby/object:Gem::Version
|
|
178
176
|
version: '0'
|
|
179
177
|
requirements: []
|
|
180
|
-
rubygems_version:
|
|
181
|
-
signing_key:
|
|
178
|
+
rubygems_version: 4.0.22
|
|
182
179
|
specification_version: 4
|
|
183
|
-
summary: Library that provides
|
|
180
|
+
summary: Library that provides memoization for methods and functions
|
|
184
181
|
test_files:
|
|
185
182
|
- spec/mize/global_clear_spec.rb
|
|
186
183
|
- spec/mize/memoize_method_spec.rb
|
|
187
184
|
- spec/mize/memoize_reload_spec.rb
|
|
188
185
|
- spec/mize/version_spec.rb
|
|
186
|
+
- spec/mize_spec.rb
|
|
189
187
|
- spec/spec_helper.rb
|
data/.utilsrc
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# vim: set ft=ruby:
|
|
2
|
-
|
|
3
|
-
search do
|
|
4
|
-
prune_dirs /\A(\.svn|\.git|CVS|tmp|tags|coverage|doc|pkg)\z/
|
|
5
|
-
skip_files /(\A\.|\.sw[pon]\z|\.(log|fnm|jpg|jpeg|png|pdf|svg)\z|tags|~\z)/i
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
discover do
|
|
9
|
-
prune_dirs /\A(\.svn|\.git|CVS|tmp|tags|coverage|doc|pkg)\z/
|
|
10
|
-
skip_files /(\A\.|\.sw[pon]\z|\.log\z|~\z)/
|
|
11
|
-
binary false
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
strip_spaces do
|
|
15
|
-
prune_dirs /\A(\..*|CVS|pkg)\z/
|
|
16
|
-
skip_files /(\A\.|\.sw[pon]\z|\.log\z|~\z)/
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
probe do
|
|
20
|
-
test_framework :rspec
|
|
21
|
-
#include_dirs 'features'
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
ssh_tunnel do
|
|
25
|
-
terminal_multiplexer :tmux
|
|
26
|
-
end
|
data/config/broken_config.yml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
,kaput
|