rails_query 0.2.0 → 0.3.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/CHANGELOG.md +9 -0
- data/README.md +37 -1
- data/lib/rails_query/client.rb +55 -7
- data/lib/rails_query/configuration.rb +13 -2
- data/lib/rails_query/query.rb +10 -1
- data/lib/rails_query/version.rb +1 -1
- metadata +46 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e8ca91e8274e6e24acc0f4101ad2ef02b18a4aeef9e2adafdc65f28188c80fc5
|
|
4
|
+
data.tar.gz: 54d293281ead95744f0970457f91fe0367c84bb8b623f54a2a2fe607900023d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c16e22500a124ca6f03e7e8d37761457c6aadf69e5bccb4fd6eda48fea1859ed911899fb93ac6b17b8b7f9dc22016bc61391bda6a4809a39171adf56982645a
|
|
7
|
+
data.tar.gz: b6de1573128e8c403af465e0927af4b9402e319897cbe6759069cd6b18b6f4cd348fb4294464b9e94f91af98135c2ce13a9cfa66c9ed12a0fa6a2ac360bc24ea
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-04-02
|
|
4
|
+
* Add support stale time by @ilsonlasmar in https://github.com/ilsonlasmar/rails_query/pull/7
|
|
5
|
+
* Support more ruby version by @ilsonlasmar in https://github.com/ilsonlasmar/rails_query/pull/8
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2026-04-02
|
|
8
|
+
* Fix badge version in README by @ilsonlasmar in https://github.com/ilsonlasmar/rails_query/pull/4
|
|
9
|
+
* Add dynamic values by context by @ilsonlasmar in https://github.com/ilsonlasmar/rails_query/pull/5
|
|
10
|
+
* change version 0.2.0 by @ilsonlasmar in https://github.com/ilsonlasmar/rails_query/pull/6
|
|
11
|
+
|
|
3
12
|
## [0.1.0] - 2026-04-29
|
|
4
13
|
|
|
5
14
|
🚀 [FEATURE] Initial release of Rails Query
|
data/README.md
CHANGED
|
@@ -50,6 +50,7 @@ app/providers/
|
|
|
50
50
|
- [Generators](#generators)
|
|
51
51
|
- [Caching](#caching)
|
|
52
52
|
- [Invalidation](#invalidation)
|
|
53
|
+
- [Stale Time](#stale-time)
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
## Installation
|
|
@@ -141,7 +142,7 @@ UserProvider
|
|
|
141
142
|
|
|
142
143
|
```ruby
|
|
143
144
|
class UserProvider
|
|
144
|
-
include RailsQuery::
|
|
145
|
+
include RailsQuery::Adapter
|
|
145
146
|
|
|
146
147
|
base_url ENV["USER_API_URL"]
|
|
147
148
|
|
|
@@ -196,6 +197,41 @@ Mutations invalidate cache after execution.
|
|
|
196
197
|
invalidates "UserProvider"
|
|
197
198
|
```
|
|
198
199
|
|
|
200
|
+
# Stale Time
|
|
201
|
+
`stale` defines how long cached data is considered **fresh**.
|
|
202
|
+
|
|
203
|
+
#### How it works
|
|
204
|
+
- While data is **fresh**:
|
|
205
|
+
- RailsQuery will **not trigger new requests**
|
|
206
|
+
- Cached data is returned immediately
|
|
207
|
+
- No background refetch occurs
|
|
208
|
+
|
|
209
|
+
- When data becomes **stale**:
|
|
210
|
+
- Data remains in cache
|
|
211
|
+
- RailsQuery may **refetch in the background** when a trigger occurs
|
|
212
|
+
|
|
213
|
+
### Default behavior
|
|
214
|
+
Stale behavior is disabled by default
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
stale nil
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Obs.:
|
|
221
|
+
Never configure `stale` to be greater than your cache expiration (TTL or equivalent).
|
|
222
|
+
|
|
223
|
+
If data is considered fresh for longer than it actually exists in cache, RailsQuery will be forced to fetch everything again from the source, losing the benefit of freshness.
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
### Stale vs Cache Expiration (TTL)
|
|
227
|
+
|
|
228
|
+
| Feature | Stale | Cache expiration (TTL) |
|
|
229
|
+
|--------|--------|------------------------|
|
|
230
|
+
| **What it controls** | When data is considered outdated and eligible for refetch | How long data remains stored in cache |
|
|
231
|
+
| **Does the data still exist?** | Yes, but marked as stale | Yes, until it expires and is removed |
|
|
232
|
+
| **Triggers new request?** | No while fresh; yes when stale (on next call/trigger) | Not directly; only after cache is gone |
|
|
233
|
+
| **Default** | nil (disabled) | 0 (not cache store) |
|
|
234
|
+
| **Example** | 0 (revalidate always) | 5 minutes (after disuse) |
|
|
199
235
|
|
|
200
236
|
## Development
|
|
201
237
|
|
data/lib/rails_query/client.rb
CHANGED
|
@@ -4,16 +4,34 @@ module RailsQuery
|
|
|
4
4
|
# Internal client class responsible for cache interactions
|
|
5
5
|
class Client
|
|
6
6
|
def initialize(config)
|
|
7
|
-
@cache
|
|
8
|
-
@default_ttl
|
|
9
|
-
@
|
|
10
|
-
@
|
|
7
|
+
@cache = config.cache_store
|
|
8
|
+
@default_ttl = config.default_ttl
|
|
9
|
+
@default_stale = config.default_stale
|
|
10
|
+
@executor = config.executor
|
|
11
|
+
@namespace = config.namespace
|
|
12
|
+
@logger = config.logger
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
def fetch(key, ttl: @default_ttl, provider: nil, &block)
|
|
14
|
-
store_index(provider, key) if provider
|
|
15
|
+
def fetch(key, ttl: @default_ttl, stale: @default_stale, provider: nil, &block)
|
|
15
16
|
namespaced = namespaced_key(key)
|
|
16
|
-
@cache.
|
|
17
|
+
entry = @cache.read(namespaced)
|
|
18
|
+
|
|
19
|
+
if entry
|
|
20
|
+
age = Time.now - entry[:fetched_at]
|
|
21
|
+
async_refetch(namespaced, ttl, provider, &block) if stale && stale < age
|
|
22
|
+
return entry[:data]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
data = block.call
|
|
26
|
+
write(namespaced, data, ttl, provider: provider)
|
|
27
|
+
|
|
28
|
+
data
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def write(key, data, ttl, provider: nil)
|
|
32
|
+
store_index(provider, key) if provider
|
|
33
|
+
|
|
34
|
+
@cache.write(key, { data: data, fetched_at: Time.now }, expires_in: ttl)
|
|
17
35
|
end
|
|
18
36
|
|
|
19
37
|
def store_index(provider, key)
|
|
@@ -45,6 +63,36 @@ module RailsQuery
|
|
|
45
63
|
|
|
46
64
|
private
|
|
47
65
|
|
|
66
|
+
def in_lock?(key)
|
|
67
|
+
lock_key = "lock:#{key}"
|
|
68
|
+
@cache.exist?(lock_key)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def write_lock(key)
|
|
72
|
+
lock_key = "lock:#{key}"
|
|
73
|
+
@cache.write(lock_key, true, expires_in: 10.seconds)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def delete_lock(key)
|
|
77
|
+
lock_key = "lock:#{key}"
|
|
78
|
+
@cache.delete(lock_key)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def async_refetch(key, ttl, provider, &block)
|
|
82
|
+
return if in_lock?(key)
|
|
83
|
+
|
|
84
|
+
write_lock(key)
|
|
85
|
+
|
|
86
|
+
@executor.post do
|
|
87
|
+
data = block.call
|
|
88
|
+
write(key, data, ttl, provider: provider)
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
@logger.error("[RailsQuery] SWR refetch failed: #{e.message}")
|
|
91
|
+
ensure
|
|
92
|
+
delete_lock(key)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
48
96
|
def namespaced_key(key)
|
|
49
97
|
"#{@namespace}:#{Array(key).join(":")}"
|
|
50
98
|
end
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
module RailsQuery
|
|
4
4
|
# Configuration class for RailsQuery
|
|
5
5
|
class Configuration
|
|
6
|
-
attr_accessor :cache_store, :default_ttl, :namespace, :logger
|
|
6
|
+
attr_accessor :cache_store, :default_ttl, :default_stale, :namespace, :executor, :logger
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
9
|
@cache_store = default_cache_store
|
|
10
|
-
@
|
|
10
|
+
@executor = default_executor
|
|
11
|
+
@default_ttl = 0
|
|
12
|
+
@default_stale = nil
|
|
11
13
|
@namespace = "rails_query"
|
|
12
14
|
@logger = default_logger
|
|
13
15
|
end
|
|
@@ -22,6 +24,15 @@ module RailsQuery
|
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
|
|
27
|
+
def default_executor
|
|
28
|
+
Concurrent::ThreadPoolExecutor.new(
|
|
29
|
+
min_threads: 2,
|
|
30
|
+
max_threads: Concurrent.processor_count * 2,
|
|
31
|
+
max_queue: 100,
|
|
32
|
+
fallback_policy: :discard
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
25
36
|
def default_logger
|
|
26
37
|
if defined?(Rails) && Rails.respond_to?(:logger)
|
|
27
38
|
Rails.logger
|
data/lib/rails_query/query.rb
CHANGED
|
@@ -13,6 +13,11 @@ module RailsQuery
|
|
|
13
13
|
@ttl
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
def stale(value = nil)
|
|
17
|
+
@stale = value if value
|
|
18
|
+
@stale
|
|
19
|
+
end
|
|
20
|
+
|
|
16
21
|
def key(&block)
|
|
17
22
|
@key_block = block if block
|
|
18
23
|
@key_block
|
|
@@ -22,7 +27,7 @@ module RailsQuery
|
|
|
22
27
|
def call(*args, **opts)
|
|
23
28
|
key = build_key(*args, **opts)
|
|
24
29
|
provider_class = opts[:provider] || self.class.name
|
|
25
|
-
RailsQuery.client.fetch(key, ttl: ttl, provider: provider_class) do
|
|
30
|
+
RailsQuery.client.fetch(key, ttl: ttl, stale: stale, provider: provider_class) do
|
|
26
31
|
kwargs? ? resolve(*args, **opts) : resolve(*args)
|
|
27
32
|
end
|
|
28
33
|
end
|
|
@@ -31,6 +36,10 @@ module RailsQuery
|
|
|
31
36
|
self.class.ttl || RailsQuery.configuration.default_ttl
|
|
32
37
|
end
|
|
33
38
|
|
|
39
|
+
def stale
|
|
40
|
+
self.class.stale || RailsQuery.configuration.default_stale
|
|
41
|
+
end
|
|
42
|
+
|
|
34
43
|
def build_key(*args, **opts)
|
|
35
44
|
opts = opts.select { |k, _| self.class.key.parameters.map(&:last).include?(k) }
|
|
36
45
|
return instance_exec(*args, **opts, &self.class.key) if self.class.key
|
data/lib/rails_query/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_query
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ilson Lasmar
|
|
@@ -9,6 +9,34 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: concurrent-ruby
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.3'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.3'
|
|
12
40
|
- !ruby/object:Gem::Dependency
|
|
13
41
|
name: minitest
|
|
14
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,19 +80,33 @@ dependencies:
|
|
|
52
80
|
- !ruby/object:Gem::Version
|
|
53
81
|
version: '0.14'
|
|
54
82
|
- !ruby/object:Gem::Dependency
|
|
55
|
-
name:
|
|
83
|
+
name: rake
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '13.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '13.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop
|
|
56
98
|
requirement: !ruby/object:Gem::Requirement
|
|
57
99
|
requirements:
|
|
58
100
|
- - "~>"
|
|
59
101
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '
|
|
102
|
+
version: '1.21'
|
|
61
103
|
type: :development
|
|
62
104
|
prerelease: false
|
|
63
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
106
|
requirements:
|
|
65
107
|
- - "~>"
|
|
66
108
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '
|
|
109
|
+
version: '1.21'
|
|
68
110
|
- !ruby/object:Gem::Dependency
|
|
69
111
|
name: simplecov
|
|
70
112
|
requirement: !ruby/object:Gem::Requirement
|