type_balancer_rails 0.2.6 → 0.2.7
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/.rubocop.yml +2 -0
- data/CHANGELOG.md +9 -0
- data/README.md +30 -0
- data/benchmarks/collection_methods_benchmark.rb +43 -0
- data/docs/architecture_overview.md +148 -0
- data/example/Gemfile.lock +3 -3
- data/example/spec/features/contents_balancing_spec.rb +1 -1
- data/example/storage/test.sqlite3 +0 -0
- data/lib/type_balancer/rails/cache_adapter.rb +33 -0
- data/lib/type_balancer/rails/collection_methods.rb +37 -58
- data/lib/type_balancer/rails/version.rb +1 -1
- data/lib/type_balancer/rails.rb +5 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d4ba73db98a7266039db3cf00de37983a4348514d93f7ae1420f4d87be88144
|
4
|
+
data.tar.gz: 54a10ed555eaaae52dc37b3ca0e8ed86a6f5d61d155333e08ad4fad9da2baa58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f289f1d2feadaf05fd1b1e2d4e5b51cd1fa1a6809ccce0fa5ff27347c0c652dc20523ff776339ec2e9a7c5ec3a8f67da9f97d0a1a2ed53287220da3206ed6ec8
|
7
|
+
data.tar.gz: c18b3c8cc22192f5df37c61e3633406c75718e9c2e27a1d34a16838198b4bcf8b95a267e104e9dbb849c3a0d7991f607d9110a4a18146ec6a6b54faa7fb653ae
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.7] - 2025-05-04
|
4
|
+
|
5
|
+
- Always-on pagination: Results from `balance_by_type` are now always paginated for performance (default: 20 per page, page 1). There is no option to disable pagination.
|
6
|
+
- Added support for `per_page` and `page` options to control result size and page.
|
7
|
+
- Added support for `expires_in` option to override the default cache expiration (default: 10 minutes) per call.
|
8
|
+
- Cache adapter is now a first-class, configurable component (`TypeBalancer::Rails.cache_adapter`).
|
9
|
+
- Improved documentation and architecture overview to reflect new pagination and caching behavior.
|
10
|
+
- RuboCop and test stability improvements.
|
11
|
+
|
3
12
|
## [0.2.6] - 2025-05-01
|
4
13
|
|
5
14
|
- Updated type_balancer dependency to ~> 0.2.1 for improved performance
|
data/README.md
CHANGED
@@ -82,6 +82,36 @@ The `balance_by_type` method preserves the ActiveRecord query interface:
|
|
82
82
|
.per(20)
|
83
83
|
```
|
84
84
|
|
85
|
+
### Pagination and Caching (Always Enabled)
|
86
|
+
|
87
|
+
Results from `balance_by_type` are **always paginated** for performance reasons. By default, only the first 20 balanced records are returned. You can control the page size and which page is returned using the `per_page` and `page` options:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
# Get the first 20 balanced records (default)
|
91
|
+
@posts = Post.all.balance_by_type
|
92
|
+
|
93
|
+
# Get the second page of 10 balanced records
|
94
|
+
@posts = Post.all.balance_by_type(type_field: :category, per_page: 10, page: 2)
|
95
|
+
```
|
96
|
+
|
97
|
+
- **Default page size:** 20
|
98
|
+
- **Default page:** 1
|
99
|
+
- **Pagination is required:** There is no option to disable pagination. This is necessary for performance, especially on large datasets.
|
100
|
+
|
101
|
+
#### Cache Expiration
|
102
|
+
|
103
|
+
Balanced results are cached by default for 10 minutes to improve performance and reduce redundant calculations. You can override the cache expiration for a specific call by passing the `expires_in` option:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
# Cache the balanced results for 1 hour instead of 10 minutes
|
107
|
+
@posts = Post.all.balance_by_type(type_field: :category, expires_in: 1.hour)
|
108
|
+
```
|
109
|
+
|
110
|
+
- **Default cache expiration:** 10 minutes
|
111
|
+
- **Custom cache expiration:** Pass `expires_in: ...` (e.g., `expires_in: 1.hour`)
|
112
|
+
|
113
|
+
> **Note:** If you need to retrieve all balanced records, you must manually iterate through all pages.
|
114
|
+
|
85
115
|
## Planned Enhancements
|
86
116
|
|
87
117
|
- 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.
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# /Users/carl/gems/type_balancer-rails/benchmarks/collection_methods_benchmark.rb
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'type_balancer'
|
6
|
+
require 'active_record'
|
7
|
+
require 'benchmark'
|
8
|
+
require_relative '../lib/type_balancer/rails'
|
9
|
+
|
10
|
+
# Setup in-memory SQLite DB and model
|
11
|
+
db_path = ':memory:'
|
12
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: db_path)
|
13
|
+
|
14
|
+
# Define a simple model for benchmarking
|
15
|
+
class Item < ActiveRecord::Base; end
|
16
|
+
|
17
|
+
# Create table and seed data
|
18
|
+
ActiveRecord::Schema.define do
|
19
|
+
create_table :items, force: true do |t|
|
20
|
+
t.string :media_type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Seed with N records, distributed across types
|
25
|
+
N = 20_000
|
26
|
+
types = ['video', 'article', 'podcast']
|
27
|
+
puts "Seeding #{N} records..."
|
28
|
+
N.times do |i|
|
29
|
+
Item.create!(media_type: types[i % types.size])
|
30
|
+
end
|
31
|
+
puts 'Seeding complete.'
|
32
|
+
|
33
|
+
# Extend the relation with CollectionMethods
|
34
|
+
relation = Item.all.extending(TypeBalancer::Rails::CollectionMethods)
|
35
|
+
|
36
|
+
# Benchmark balance_by_type
|
37
|
+
puts "\nBenchmarking balance_by_type on #{N} records..."
|
38
|
+
Benchmark.bm(20) do |x|
|
39
|
+
x.report('balance_by_type:') do
|
40
|
+
result = relation.balance_by_type(type_field: :media_type)
|
41
|
+
result.to_a
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# TypeBalancer Rails Gem: Architecture Overview
|
2
|
+
|
3
|
+
## 1. Project Summary
|
4
|
+
|
5
|
+
TypeBalancer Rails is a Ruby gem designed to provide advanced balancing and ordering capabilities for ActiveRecord collections in Rails applications. Its primary feature is the ability to balance records by a specified type, ensuring even distribution and flexible pagination of heterogeneous data sets. The gem is intended for use in Rails projects that require sophisticated content or resource balancing, such as feeds, dashboards, or content aggregators.
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
## 2. Architecture & Dependencies
|
10
|
+
|
11
|
+
### High-Level Architecture
|
12
|
+
- **Core Module:** The gem's core logic is implemented under `lib/type_balancer/rails/`, with extensions for ActiveRecord and integration with Rails via a Railtie.
|
13
|
+
- **ActiveRecord Integration:** The gem extends ActiveRecord models and relations to provide the `balance_by_type` method, which can be called on any model or relation.
|
14
|
+
- **Rails Integration:** A Railtie ensures the gem is loaded and configured automatically in Rails environments.
|
15
|
+
- **Generators:** The gem provides a Rails generator for easy installation and configuration.
|
16
|
+
|
17
|
+
### Key Dependencies
|
18
|
+
- **active_support**: Used for concerns, core extensions, and Rails integration.
|
19
|
+
- **active_record**: The gem extends ActiveRecord models and relations.
|
20
|
+
- **type_balancer**: The core balancing logic is delegated to the `type_balancer` gem (see below).
|
21
|
+
- **Rails (optional)**: For automatic integration via Railtie and generator support.
|
22
|
+
|
23
|
+
### About the TypeBalancer Gem
|
24
|
+
|
25
|
+
TypeBalancer is a Ruby gem that provides advanced algorithms for distributing items of different types evenly across a sequence. Its primary use case is to ensure that, in collections where certain types (e.g., articles, images, videos) are overrepresented, the output is balanced so that all types are fairly and optimally spaced. This is especially useful for content feeds, e-commerce listings, news aggregators, and recommendation systems.
|
26
|
+
|
27
|
+
**Key Features:**
|
28
|
+
- Balances collections by type, ensuring optimal spacing and respecting type ratios.
|
29
|
+
- Uses a sophisticated sliding window strategy by default, with support for custom window sizes and type orderings.
|
30
|
+
- Extensible strategy system for future balancing algorithms.
|
31
|
+
- Thread-safe, memory-efficient, and suitable for real-time processing of collections up to 10,000 items.
|
32
|
+
- No external dependencies and high performance across Ruby versions.
|
33
|
+
|
34
|
+
**Core API:**
|
35
|
+
- `TypeBalancer.balance(items, type_field: :type, strategy: :sliding_window, window_size: 10, type_order: [...])`
|
36
|
+
Balances an array of items by the specified type field, using the chosen strategy and options.
|
37
|
+
- `TypeBalancer.calculate_positions(total_count:, ratio:, available_items: [...])`
|
38
|
+
Calculates optimal positions for a given type or subset within a sequence.
|
39
|
+
|
40
|
+
**Integration with Rails:**
|
41
|
+
- The TypeBalancer Rails gem acts as a façade and adapter, exposing TypeBalancer's balancing logic as an easy-to-use method (`balance_by_type`) on ActiveRecord relations and models.
|
42
|
+
- This allows Rails developers to leverage advanced balancing in queries and collections with minimal setup.
|
43
|
+
|
44
|
+
---
|
45
|
+
|
46
|
+
## 3. Class & Module Documentation
|
47
|
+
|
48
|
+
### `TypeBalancer::Rails::CollectionMethods`
|
49
|
+
- **Location:** `lib/type_balancer/rails/collection_methods.rb`
|
50
|
+
- **Purpose:**
|
51
|
+
- Provides the `balance_by_type` method for ActiveRecord::Relation, enabling balanced selection and ordering of records by a type field.
|
52
|
+
- **Always paginates results** for performance. Default: 20 per page, page 1. There is no option to disable pagination.
|
53
|
+
- Supports `per_page` and `page` options to control result size and page.
|
54
|
+
- **Results are cached** for 10 minutes by default, but you can override this per call with the `expires_in` option (e.g., `expires_in: 1.hour`).
|
55
|
+
- Handles result ordering and cache key generation.
|
56
|
+
- **Dependencies:**
|
57
|
+
- Depends on `TypeBalancer.balance` for core balancing logic.
|
58
|
+
- Expects to be included in ActiveRecord::Relation.
|
59
|
+
- **Patterns:**
|
60
|
+
- Adapter/Extension pattern for ActiveRecord::Relation.
|
61
|
+
|
62
|
+
### `TypeBalancer::Rails::ActiveRecordExtension`
|
63
|
+
- **Location:** `/Users/carl/gems/type_balancer-rails/lib/type_balancer/rails/active_record_extension.rb`
|
64
|
+
- **Purpose:**
|
65
|
+
- Provides a concern to extend ActiveRecord models with type balancing configuration and class-level `balance_by_type` method.
|
66
|
+
- Ensures `CollectionMethods` is included in ActiveRecord::Relation.
|
67
|
+
- **Dependencies:**
|
68
|
+
- `ActiveSupport::Concern`, `ActiveRecord::Relation`, `TypeBalancer::Rails::CollectionMethods`.
|
69
|
+
- **Patterns:**
|
70
|
+
- Rails Concern, Extension.
|
71
|
+
|
72
|
+
### `TypeBalancer::Rails::Railtie`
|
73
|
+
- **Location:** `/Users/carl/gems/type_balancer-rails/lib/type_balancer/rails/railtie.rb`
|
74
|
+
- **Purpose:**
|
75
|
+
- Integrates the gem with Rails, ensuring the ActiveRecord extension is loaded automatically.
|
76
|
+
- **Dependencies:**
|
77
|
+
- `Rails::Railtie`, `ActiveSupport.on_load(:active_record)`.
|
78
|
+
- **Patterns:**
|
79
|
+
- Railtie for Rails integration.
|
80
|
+
|
81
|
+
### `TypeBalancer::Rails::VERSION`
|
82
|
+
- **Location:** `/Users/carl/gems/type_balancer-rails/lib/type_balancer/rails/version.rb`
|
83
|
+
- **Purpose:**
|
84
|
+
- Defines the gem's version constant.
|
85
|
+
- **Dependencies:** None.
|
86
|
+
|
87
|
+
### `TypeBalancer::Rails` (Module)
|
88
|
+
- **Location:** `/Users/carl/gems/type_balancer-rails/lib/type_balancer/rails.rb`
|
89
|
+
- **Purpose:**
|
90
|
+
- Loads and wires up all Rails-specific extensions and dependencies for the gem.
|
91
|
+
- **Exposes a configurable cache adapter** (`TypeBalancer::Rails.cache_adapter`) used for caching balanced results.
|
92
|
+
- **Dependencies:**
|
93
|
+
- `active_support`, `active_record`, `TypeBalancer::Rails::ActiveRecordExtension`, `TypeBalancer::Rails::CollectionMethods`.
|
94
|
+
|
95
|
+
### Main Entry File
|
96
|
+
- **Location:** `/Users/carl/gems/type_balancer-rails/lib/type_balancer_rails.rb`
|
97
|
+
- **Purpose:**
|
98
|
+
- Loads all required dependencies and sets up the gem for use in a Rails environment.
|
99
|
+
- Loads the Railtie if Rails is present.
|
100
|
+
|
101
|
+
### Generator: `TypeBalancer::Generators::InstallGenerator`
|
102
|
+
- **Location:** `/Users/carl/gems/type_balancer-rails/lib/generators/type_balancer/install/install_generator.rb`
|
103
|
+
- **Purpose:**
|
104
|
+
- Provides a Rails generator for installing TypeBalancer configuration into a Rails app.
|
105
|
+
- **Dependencies:**
|
106
|
+
- Rails generator framework.
|
107
|
+
- **Patterns:**
|
108
|
+
- Generator pattern for Rails setup.
|
109
|
+
|
110
|
+
---
|
111
|
+
|
112
|
+
## 4. Testing Strategy
|
113
|
+
|
114
|
+
- **Unit Tests:**
|
115
|
+
- Located in `/Users/carl/gems/type_balancer-rails/spec/`.
|
116
|
+
- Organized by feature/module (e.g., `spec/type_balancer/rails/collection_methods_spec.rb`).
|
117
|
+
- Do **not** use a database; all database and dependency interactions are strictly mocked or stubbed.
|
118
|
+
- Shared contexts and helpers are provided in `spec/support/` (e.g., `test_helpers.rb`, `test_fixtures.rb`).
|
119
|
+
- Test models for mocking are in `spec/support/models/`.
|
120
|
+
|
121
|
+
- **Integration Tests:**
|
122
|
+
- Located in the example Rails app under `/Users/carl/gems/type_balancer-rails/example/`.
|
123
|
+
- Example app contains its own `spec/` directory with feature, controller, and model specs.
|
124
|
+
- Integration tests use a real database and Rails stack to verify end-to-end behavior.
|
125
|
+
- Example app includes its own Gemfile and configuration for isolated testing.
|
126
|
+
|
127
|
+
- **Testing Practices:**
|
128
|
+
- Unit tests focus on class responsibilities and use mocking for all external dependencies.
|
129
|
+
- Integration tests are only created in the example app and are not run as part of the main gem's unit test suite.
|
130
|
+
- No direct tests of private methods; only public interfaces are tested.
|
131
|
+
- RSpec is used as the test framework throughout.
|
132
|
+
- RuboCop is used for code style enforcement (`.rubocop.yml` present in both root and example app).
|
133
|
+
- CI and test runner setup is inferred from the presence of `.rspec`, `.rspec_status`, and Rakefile.
|
134
|
+
|
135
|
+
- **Interesting/Unique Practices:**
|
136
|
+
- Strict separation of unit and integration tests, with clear boundaries and no database usage in unit tests.
|
137
|
+
- Use of shared contexts and helpers to DRY up test setup.
|
138
|
+
- Example app serves as a living integration testbed for real-world Rails usage.
|
139
|
+
|
140
|
+
---
|
141
|
+
|
142
|
+
## 5. Additional Notes
|
143
|
+
- All file paths in this documentation are absolute, per project standards.
|
144
|
+
- The gem follows SOLID principles and Rails best practices, with minimal monkey-patching and clear extension points.
|
145
|
+
- **Pagination and caching are always enabled for performance reasons.**
|
146
|
+
- Default: 20 per page, page 1, 10 minute cache expiration.
|
147
|
+
- Use `per_page`, `page`, and `expires_in` options to customize.
|
148
|
+
- For more details, see the README and inline code comments.
|
data/example/Gemfile.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
type_balancer_rails (0.2.
|
4
|
+
type_balancer_rails (0.2.7)
|
5
5
|
activerecord (>= 7.0, < 9.0)
|
6
6
|
activesupport (>= 7.0, < 9.0)
|
7
|
-
type_balancer (~> 0.2.
|
7
|
+
type_balancer (~> 0.2.1)
|
8
8
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
@@ -381,7 +381,7 @@ GEM
|
|
381
381
|
turbo-rails (2.0.13)
|
382
382
|
actionpack (>= 7.1.0)
|
383
383
|
railties (>= 7.1.0)
|
384
|
-
type_balancer (0.2.
|
384
|
+
type_balancer (0.2.1)
|
385
385
|
tzinfo (2.0.6)
|
386
386
|
concurrent-ruby (~> 1.0)
|
387
387
|
unicode-display_width (3.1.4)
|
@@ -9,7 +9,7 @@ RSpec.feature 'Contents balancing', type: :feature do
|
|
9
9
|
# Collect the categories from the second column of the table rows
|
10
10
|
categories = page.all('table tbody tr').map { |row| row.all('td')[1]&.text }.compact
|
11
11
|
|
12
|
-
expect(categories.count).to eq(
|
12
|
+
expect(categories.count).to eq(20)
|
13
13
|
# There should be a mix of categories, not just a long run of one category
|
14
14
|
expect(categories.uniq.sort).to eq(%w[blog news tutorial])
|
15
15
|
# Check that the first 10 are not all the same (skewed fixture would be all news)
|
Binary file
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module TypeBalancer
|
2
|
+
module Rails
|
3
|
+
class CacheAdapter
|
4
|
+
def initialize
|
5
|
+
@memory_cache = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def fetch(key, options = {}, &block)
|
9
|
+
if defined?(::Rails) && ::Rails.respond_to?(:cache) && ::Rails.cache
|
10
|
+
::Rails.cache.fetch(key, options, &block)
|
11
|
+
else
|
12
|
+
@memory_cache[key] ||= block.call
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(key, value, options = {})
|
17
|
+
if defined?(::Rails) && ::Rails.respond_to?(:cache) && ::Rails.cache
|
18
|
+
::Rails.cache.write(key, value, options)
|
19
|
+
else
|
20
|
+
@memory_cache[key] = value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
if defined?(::Rails) && ::Rails.respond_to?(:cache) && ::Rails.cache
|
26
|
+
::Rails.cache.delete(key)
|
27
|
+
else
|
28
|
+
@memory_cache.delete(key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -6,52 +6,53 @@ module TypeBalancer
|
|
6
6
|
# Provides collection methods for balancing by type
|
7
7
|
# These methods are extended onto ActiveRecord::Relation
|
8
8
|
module CollectionMethods
|
9
|
-
|
10
|
-
records = to_a
|
11
|
-
return empty_relation if records.empty?
|
12
|
-
|
13
|
-
type_field = fetch_type_field(options).to_sym
|
14
|
-
type_counts = records.group_by { |r| r.send(type_field).to_s }.transform_values(&:count)
|
15
|
-
type_order = compute_type_order(type_counts)
|
16
|
-
items = build_items(records, type_field)
|
9
|
+
require 'digest/md5'
|
17
10
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
paged = apply_pagination(balanced, options)
|
11
|
+
def balance_by_type(options = {})
|
12
|
+
type_field, offset, per_page = pagination_params(options)
|
13
|
+
cache_key = build_cache_key(type_field)
|
14
|
+
ids = TypeBalancer::Rails.cache_adapter.fetch(cache_key, expires_in: 10.minutes) do
|
15
|
+
compute_ids(type_field)
|
16
|
+
end
|
17
|
+
page_ids = ids[offset, per_page] || []
|
18
|
+
return empty_relation if page_ids.empty?
|
27
19
|
|
28
|
-
|
20
|
+
order_by_ids(page_ids)
|
29
21
|
end
|
30
22
|
|
31
23
|
private
|
32
24
|
|
33
|
-
def
|
34
|
-
|
25
|
+
def pagination_params(options)
|
26
|
+
type_field = fetch_type_field(options).to_sym
|
27
|
+
page = (options[:page] || 1).to_i
|
28
|
+
per_page = (options[:per_page] || 20).to_i
|
29
|
+
offset = (page - 1) * per_page
|
30
|
+
[type_field, offset, per_page]
|
31
|
+
end
|
35
32
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
def build_cache_key(type_field)
|
34
|
+
[
|
35
|
+
'type_balancer',
|
36
|
+
klass.name,
|
37
|
+
type_field,
|
38
|
+
Digest::MD5.hexdigest(to_sql)
|
39
|
+
].join(':')
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
def compute_ids(type_field)
|
43
|
+
records = select(:id, type_field)
|
44
|
+
items = records.map { |r| { id: r.id, type_field => r.public_send(type_field) } }
|
45
|
+
type_counts = items.group_by { |h| h[type_field] }.transform_values(&:size)
|
46
|
+
order = compute_type_order(type_counts)
|
47
|
+
balanced = TypeBalancer.balance(items, type_field: type_field, type_order: order)
|
48
|
+
balanced ? balanced.flatten(1).map { |h| h[:id] } : []
|
49
|
+
rescue TypeBalancer::EmptyCollectionError
|
50
|
+
[]
|
51
|
+
end
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
relation = relation.order(Arel.sql(case_sql))
|
53
|
-
end
|
54
|
-
relation
|
53
|
+
def order_by_ids(ids)
|
54
|
+
case_sql = "CASE id #{ids.each_with_index.map { |id, idx| "WHEN #{id} THEN #{idx}" }.join(' ')} END"
|
55
|
+
klass.where(id: ids).order(Arel.sql(case_sql))
|
55
56
|
end
|
56
57
|
|
57
58
|
def empty_relation
|
@@ -70,28 +71,6 @@ module TypeBalancer
|
|
70
71
|
def compute_type_order(type_counts)
|
71
72
|
type_counts.sort_by { |_, count| count }.map(&:first)
|
72
73
|
end
|
73
|
-
|
74
|
-
def build_items(records, type_field)
|
75
|
-
records.map do |record|
|
76
|
-
{ id: record.id, type_field => record.send(type_field).to_s }
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def logger?
|
81
|
-
defined?(::Rails) && ::Rails.logger
|
82
|
-
end
|
83
|
-
|
84
|
-
def balance_results_lines(balanced, _type_field)
|
85
|
-
if balanced.nil?
|
86
|
-
['Balanced result is nil!']
|
87
|
-
else
|
88
|
-
[
|
89
|
-
"First 10 balanced types: \#{balanced.first(10).map { |h| h[type_field] }.inspect}",
|
90
|
-
"Unique types in first 10: \#{balanced.first(10).map { |h| h[type_field] }.uniq.inspect}",
|
91
|
-
"Total balanced items: \#{balanced.size}"
|
92
|
-
]
|
93
|
-
end
|
94
|
-
end
|
95
74
|
end
|
96
75
|
end
|
97
76
|
end
|
data/lib/type_balancer/rails.rb
CHANGED
@@ -6,8 +6,13 @@ require 'active_record'
|
|
6
6
|
|
7
7
|
require_relative 'rails/active_record_extension'
|
8
8
|
require_relative 'rails/collection_methods'
|
9
|
+
require_relative 'rails/cache_adapter'
|
9
10
|
|
10
11
|
module TypeBalancer
|
11
12
|
module Rails
|
13
|
+
class << self
|
14
|
+
attr_accessor :cache_adapter
|
15
|
+
end
|
16
|
+
self.cache_adapter ||= TypeBalancer::Rails::CacheAdapter.new
|
12
17
|
end
|
13
18
|
end
|
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.7
|
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-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -78,6 +78,8 @@ files:
|
|
78
78
|
- LICENSE.txt
|
79
79
|
- README.md
|
80
80
|
- Rakefile
|
81
|
+
- benchmarks/collection_methods_benchmark.rb
|
82
|
+
- docs/architecture_overview.md
|
81
83
|
- example/.dockerignore
|
82
84
|
- example/.rspec
|
83
85
|
- example/.rubocop.yml
|
@@ -164,6 +166,7 @@ files:
|
|
164
166
|
- lib/generators/type_balancer/install/templates/type_balancer.rb.erb
|
165
167
|
- lib/type_balancer/rails.rb
|
166
168
|
- lib/type_balancer/rails/active_record_extension.rb
|
169
|
+
- lib/type_balancer/rails/cache_adapter.rb
|
167
170
|
- lib/type_balancer/rails/collection_methods.rb
|
168
171
|
- lib/type_balancer/rails/railtie.rb
|
169
172
|
- lib/type_balancer/rails/version.rb
|