type_balancer_rails 0.2.7 → 0.2.8
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 +26 -1
- data/README.md +78 -0
- data/example/Gemfile.lock +2 -2
- data/example/config/environments/test.rb +1 -1
- data/example/spec/controllers/posts_controller_caching_spec.rb +65 -0
- data/example/spec/models/content_spec.rb +1 -0
- data/example/spec/models/post_spec.rb +1 -0
- data/example/storage/test.sqlite3 +0 -0
- data/lib/type_balancer/rails/cache_adapter.rb +5 -0
- data/lib/type_balancer/rails/collection_methods.rb +9 -2
- data/lib/type_balancer/rails/version.rb +1 -1
- data/lib/type_balancer/rails.rb +11 -1
- data/type_balancer_rails.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9866aa8259f829d620cf73c8646ca94d0df346fb919f2b9c162541e0206e39d7
|
4
|
+
data.tar.gz: 1869524e7b9660adae5e6a86e8e6807306d61ad6927ad2192005b12236f2ef09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98609db2225b4e762f7445bcc2fcf675e7995c49b1c41699ae5e5c2c706f34e0dbe88945b6bcf72f0651c2f92e2648c0515bda048df0631dd322e0abf3caaa5e
|
7
|
+
data.tar.gz: 2f8371a767e3301c57ffb3cf9bc16ae9e9e5e6ffe381ef97230632837eef7e9157f2968e40735fc00c8a2662e1f5dc56f5fb12644ed298b36a6162e0c5fe1d74
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,29 @@
|
|
1
|
-
## [
|
1
|
+
## [0.2.8] - 2025-05-10
|
2
|
+
|
3
|
+
- **Rails-style configuration block:**
|
4
|
+
You can now configure TypeBalancer Rails in an initializer using:
|
5
|
+
```ruby
|
6
|
+
TypeBalancer::Rails.configure do |config|
|
7
|
+
config.cache_adapter = TypeBalancer::Rails::CacheAdapter.new
|
8
|
+
config.cache_expiry_seconds = 600
|
9
|
+
end
|
10
|
+
```
|
11
|
+
Direct assignment is still supported for backward compatibility.
|
12
|
+
|
13
|
+
- **Per-request cache control:**
|
14
|
+
`balance_by_type` now accepts:
|
15
|
+
- `expires_in:` (override cache expiry for a single call)
|
16
|
+
- `cache_reset:` (force cache refresh for a single call)
|
17
|
+
|
18
|
+
- **Global cache expiry configuration:**
|
19
|
+
Set the default cache expiry for all balanced queries via `TypeBalancer::Rails.cache_expiry_seconds`.
|
20
|
+
|
21
|
+
- **Cache clearing:**
|
22
|
+
Use `TypeBalancer::Rails.clear_cache!` to clear all cached balanced results (e.g., from a console or admin task).
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
- **Caching and pagination are always enabled** for performance and reliability.
|
26
|
+
- **Cache keys are now isolated** per model and type field, preventing cross-contamination.
|
2
27
|
|
3
28
|
## [0.2.7] - 2025-05-04
|
4
29
|
|
data/README.md
CHANGED
@@ -26,6 +26,38 @@ Or install it yourself as:
|
|
26
26
|
$ gem install type_balancer_rails
|
27
27
|
```
|
28
28
|
|
29
|
+
## Caching and Performance
|
30
|
+
|
31
|
+
Type balancing can be computationally expensive, especially on large datasets. To ensure efficient performance, **TypeBalancer Rails automatically caches the balanced ID list for each query**. This means:
|
32
|
+
- The balancing algorithm only runs when needed (on cache miss or reset).
|
33
|
+
- Subsequent requests for the same query use the cached result, reducing database and CPU load.
|
34
|
+
- Caching is essential for production use. Disabling or misconfiguring cache may result in slow queries.
|
35
|
+
|
36
|
+
**Adjust cache settings thoughtfully:**
|
37
|
+
- Shorter expiries mean fresher data but more frequent recalculation.
|
38
|
+
- Longer expiries improve performance but may serve stale results if your data changes often.
|
39
|
+
|
40
|
+
## Configuration
|
41
|
+
|
42
|
+
To customize caching and performance, use the Rails-style configuration block in an initializer (e.g., `config/initializers/type_balancer.rb`):
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
TypeBalancer::Rails.configure do |config|
|
46
|
+
# Use the default cache adapter (backed by Rails.cache)
|
47
|
+
config.cache_adapter = TypeBalancer::Rails::CacheAdapter.new
|
48
|
+
|
49
|
+
# Set the global cache expiry (in seconds)
|
50
|
+
# Default is 600 (10 minutes). Adjust as needed for your app's freshness/performance needs.
|
51
|
+
config.cache_expiry_seconds = 600
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
> **Note:** You can also set these options directly
|
56
|
+
> ```ruby
|
57
|
+
> TypeBalancer::Rails.cache_adapter = TypeBalancer::Rails::CacheAdapter.new
|
58
|
+
> TypeBalancer::Rails.cache_expiry_seconds = 600
|
59
|
+
> ```
|
60
|
+
|
29
61
|
## Usage
|
30
62
|
|
31
63
|
To balance records by a given type field, use the following syntax:
|
@@ -112,6 +144,52 @@ Balanced results are cached by default for 10 minutes to improve performance and
|
|
112
144
|
|
113
145
|
> **Note:** If you need to retrieve all balanced records, you must manually iterate through all pages.
|
114
146
|
|
147
|
+
### Per-request Cache Control
|
148
|
+
|
149
|
+
You can override cache behavior for a single call to `balance_by_type`:
|
150
|
+
|
151
|
+
- **Custom Expiry:**
|
152
|
+
```ruby
|
153
|
+
# Cache the balanced results for 1 hour for this request only
|
154
|
+
@posts = Post.all.balance_by_type(type_field: :category, expires_in: 1.hour)
|
155
|
+
```
|
156
|
+
- **Force Cache Reset:**
|
157
|
+
```ruby
|
158
|
+
# Force recalculation and cache update for this request
|
159
|
+
@posts = Post.all.balance_by_type(type_field: :category, cache_reset: true)
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Controller Example
|
163
|
+
|
164
|
+
You can pass these options from controller params:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
class PostsController < ApplicationController
|
168
|
+
def index
|
169
|
+
@posts = Post.all.balance_by_type(
|
170
|
+
type_field: params[:type_field],
|
171
|
+
expires_in: params[:expires_in],
|
172
|
+
cache_reset: params[:cache_reset].present?
|
173
|
+
)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
## Cache Management and Isolation
|
179
|
+
|
180
|
+
- **Cache keys are unique per model and type field.**
|
181
|
+
- There is no cross-contamination between different models or type fields.
|
182
|
+
- If you use multiple type fields or models, each will have its own cache entry.
|
183
|
+
- To avoid stale data, clear the cache after bulk updates or schema changes using `TypeBalancer::Rails.clear_cache!`.
|
184
|
+
|
185
|
+
## Upgrade Notes
|
186
|
+
|
187
|
+
- `balance_by_type` now supports per-request `expires_in` and `cache_reset` options.
|
188
|
+
- Global cache expiry is configurable via `TypeBalancer::Rails.cache_expiry_seconds`.
|
189
|
+
- You can clear all cached results with `TypeBalancer::Rails.clear_cache!`.
|
190
|
+
- Caching is always enabled and required for performance.
|
191
|
+
- Pagination is always enabled; you must page through results if you want all records.
|
192
|
+
|
115
193
|
## Planned Enhancements
|
116
194
|
|
117
195
|
- Support for passing a symbol directly to `balance_by_type`, e.g., `balance_by_type(:media_type)`, for more ergonomic usage. This is planned for a future version.
|
data/example/Gemfile.lock
CHANGED
@@ -20,7 +20,7 @@ Rails.application.configure do
|
|
20
20
|
|
21
21
|
# Show full error reports.
|
22
22
|
config.consider_all_requests_local = true
|
23
|
-
config.cache_store = :
|
23
|
+
config.cache_store = :memory_store
|
24
24
|
|
25
25
|
# Render exception templates for rescuable exceptions and raise for other exceptions.
|
26
26
|
config.action_dispatch.show_exceptions = :rescuable
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe PostsController, type: :controller do
|
4
|
+
fixtures :posts
|
5
|
+
|
6
|
+
before do
|
7
|
+
Rails.cache.clear
|
8
|
+
allow(TypeBalancer).to receive(:balance).and_call_original
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'GET #index' do
|
12
|
+
it 'caches the balanced ID list and reuses it' do
|
13
|
+
# First call: should invoke the balancer
|
14
|
+
get :index
|
15
|
+
expect(TypeBalancer).to have_received(:balance).once
|
16
|
+
first_result = assigns(:posts).map(&:id)
|
17
|
+
|
18
|
+
# Second call: should use the cache, not call balancer again
|
19
|
+
get :index
|
20
|
+
expect(TypeBalancer).to have_received(:balance).once
|
21
|
+
second_result = assigns(:posts).map(&:id)
|
22
|
+
expect(second_result).to eq(first_result)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'resets the cache when requested via controller param' do
|
26
|
+
# Prime the cache
|
27
|
+
get :index
|
28
|
+
expect(TypeBalancer).to have_received(:balance).once
|
29
|
+
first_result = assigns(:posts).map(&:id)
|
30
|
+
|
31
|
+
# Simulate cache reset by passing param (assuming controller supports it)
|
32
|
+
allow(TypeBalancer).to receive(:balance).and_call_original # reset spy
|
33
|
+
get :index, params: { cache_reset: true }
|
34
|
+
expect(TypeBalancer).to have_received(:balance).once
|
35
|
+
second_result = assigns(:posts).map(&:id)
|
36
|
+
expect(second_result).to eq(first_result) # Should still be balanced, but balancer called again
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'isolates cache by type field' do
|
40
|
+
get :index, params: { type_field: 'media_type' }
|
41
|
+
expect(TypeBalancer).to have_received(:balance).once
|
42
|
+
allow(TypeBalancer).to receive(:balance).and_call_original # reset spy
|
43
|
+
get :index, params: { type_field: 'other_type' }
|
44
|
+
expect(TypeBalancer).to have_received(:balance).once
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'respects per-request expires_in option' do
|
48
|
+
get :index, params: { expires_in: 1 }
|
49
|
+
expect(TypeBalancer).to have_received(:balance).once
|
50
|
+
sleep 2
|
51
|
+
allow(TypeBalancer).to receive(:balance).and_call_original # reset spy
|
52
|
+
get :index, params: { expires_in: 1 }
|
53
|
+
expect(TypeBalancer).to have_received(:balance).once
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'expires cache and calls balancer again after expiry' do
|
57
|
+
get :index, params: { expires_in: 1 }
|
58
|
+
expect(TypeBalancer).to have_received(:balance).once
|
59
|
+
sleep 2
|
60
|
+
allow(TypeBalancer).to receive(:balance).and_call_original # reset spy
|
61
|
+
get :index, params: { expires_in: 1 }
|
62
|
+
expect(TypeBalancer).to have_received(:balance).once
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -19,6 +19,7 @@ RSpec.describe Content, type: :model do
|
|
19
19
|
|
20
20
|
describe 'type balancing with real records' do
|
21
21
|
before do
|
22
|
+
TypeBalancer::Rails.clear_cache!
|
22
23
|
described_class.delete_all
|
23
24
|
@content1 = described_class.create!(title: 'Content 1', content_type: 'blog')
|
24
25
|
@content2 = described_class.create!(title: 'Content 2', content_type: 'news')
|
@@ -16,6 +16,7 @@ RSpec.describe Post, type: :model do
|
|
16
16
|
let(:another_video_post) { described_class.create!(title: 'Post 3', media_type: 'video') }
|
17
17
|
|
18
18
|
before do
|
19
|
+
TypeBalancer::Rails.clear_cache!
|
19
20
|
described_class.delete_all
|
20
21
|
video_post; article_post; another_video_post # Create the records
|
21
22
|
end
|
Binary file
|
@@ -11,8 +11,15 @@ module TypeBalancer
|
|
11
11
|
def balance_by_type(options = {})
|
12
12
|
type_field, offset, per_page = pagination_params(options)
|
13
13
|
cache_key = build_cache_key(type_field)
|
14
|
-
|
15
|
-
|
14
|
+
expires_in = options[:expires_in] || ::TypeBalancer::Rails.cache_expiry_seconds
|
15
|
+
cache_reset = options[:cache_reset]
|
16
|
+
if cache_reset
|
17
|
+
ids = compute_ids(type_field)
|
18
|
+
TypeBalancer::Rails.cache_adapter.write(cache_key, ids, expires_in: expires_in)
|
19
|
+
else
|
20
|
+
ids = TypeBalancer::Rails.cache_adapter.fetch(cache_key, expires_in: expires_in) do
|
21
|
+
compute_ids(type_field)
|
22
|
+
end
|
16
23
|
end
|
17
24
|
page_ids = ids[offset, per_page] || []
|
18
25
|
return empty_relation if page_ids.empty?
|
data/lib/type_balancer/rails.rb
CHANGED
@@ -11,8 +11,18 @@ require_relative 'rails/cache_adapter'
|
|
11
11
|
module TypeBalancer
|
12
12
|
module Rails
|
13
13
|
class << self
|
14
|
-
attr_accessor :cache_adapter
|
14
|
+
attr_accessor :cache_adapter, :cache_expiry_seconds
|
15
|
+
|
16
|
+
def clear_cache!
|
17
|
+
cache_adapter&.clear_cache!
|
18
|
+
end
|
19
|
+
|
20
|
+
# Rails-style configuration block
|
21
|
+
def configure
|
22
|
+
yield self if block_given?
|
23
|
+
end
|
15
24
|
end
|
16
25
|
self.cache_adapter ||= TypeBalancer::Rails::CacheAdapter.new
|
26
|
+
self.cache_expiry_seconds ||= 600 # 10 minutes default
|
17
27
|
end
|
18
28
|
end
|
data/type_balancer_rails.gemspec
CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
# Runtime dependencies
|
33
33
|
spec.add_dependency 'activerecord', '>= 7.0', '< 9.0'
|
34
34
|
spec.add_dependency 'activesupport', '>= 7.0', '< 9.0'
|
35
|
-
spec.add_dependency 'type_balancer', '
|
35
|
+
spec.add_dependency 'type_balancer', '>= 0.2.1'
|
36
36
|
|
37
37
|
# For more information and examples about making a new gem, check out our
|
38
38
|
# guide at: https://bundler.io/guides/creating_gem.html
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: type_balancer_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-05-
|
11
|
+
date: 2025-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -54,14 +54,14 @@ dependencies:
|
|
54
54
|
name: type_balancer
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
|
-
- - "
|
57
|
+
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: 0.2.1
|
60
60
|
type: :runtime
|
61
61
|
prerelease: false
|
62
62
|
version_requirements: !ruby/object:Gem::Requirement
|
63
63
|
requirements:
|
64
|
-
- - "
|
64
|
+
- - ">="
|
65
65
|
- !ruby/object:Gem::Version
|
66
66
|
version: 0.2.1
|
67
67
|
description: Provides Rails integration for the type_balancer gem
|
@@ -147,6 +147,7 @@ files:
|
|
147
147
|
- example/lib/tasks/dev_fixtures.rake
|
148
148
|
- example/script/.keep
|
149
149
|
- example/spec/controllers/contents_controller_spec.rb
|
150
|
+
- example/spec/controllers/posts_controller_caching_spec.rb
|
150
151
|
- example/spec/controllers/posts_controller_spec.rb
|
151
152
|
- example/spec/features/contents_balancing_spec.rb
|
152
153
|
- example/spec/features/posts_balancing_spec.rb
|