frozen_record 0.22.2 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +4 -12
- data/CHANGELOG.md +22 -0
- data/README.md +11 -4
- data/lib/frozen_record/backends/empty.yml +1 -0
- data/lib/frozen_record/backends/yaml.rb +1 -1
- data/lib/frozen_record/base.rb +12 -1
- data/lib/frozen_record/minimal.rb +1 -1
- data/lib/frozen_record/scope.rb +23 -4
- data/lib/frozen_record/test_helper.rb +19 -9
- data/lib/frozen_record/version.rb +1 -1
- data/lib/frozen_record.rb +1 -1
- data/spec/fixtures/continents.yml.erb +9 -0
- data/spec/fixtures/prices.yml.erb +13 -0
- data/spec/fixtures/test_helper/continents.yml.erb +3 -0
- data/spec/scope_spec.rb +26 -0
- data/spec/support/continent.rb +2 -0
- data/spec/support/price.rb +13 -0
- data/spec/test_helper_spec.rb +16 -0
- metadata +15 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2be87eaecead5b4f1d4c5e941d48fcd9be163c56f2c9b988ec4d6ff7ea84757
|
4
|
+
data.tar.gz: 0a4a51c609251f3a46ba49e16f85b1d27ce5e52581fb70bd605c8585b1f62fa8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4326747e566bdb03b700dd5bb8164b550f8d339991754cebeb2a5414f8858a4bb19c074ac2231d4e978be881ed0ff87611c107c69bbc2a21ba498b7d3eb563eb
|
7
|
+
data.tar.gz: f73715f137336bada8a8aad2a3be9d6b4cf87f4de9dae6c2b77463201f7a3e510ae6c7491d9957db9c7d2b6df8280576e7e896eb8bf28b661bff02d32d4634f5
|
data/.github/workflows/main.yml
CHANGED
@@ -6,26 +6,18 @@ jobs:
|
|
6
6
|
build:
|
7
7
|
runs-on: ubuntu-latest
|
8
8
|
strategy:
|
9
|
+
fail-fast: false
|
9
10
|
matrix:
|
10
|
-
ruby: [ '2.5', '2.6', '2.7', '3.0' ]
|
11
|
+
ruby: [ '2.5', '2.6', '2.7', '3.0' , '3.1']
|
11
12
|
minimal: [ false, true ]
|
12
13
|
name: Ruby ${{ matrix.ruby }} tests, minimal=${{ matrix.minimal }}
|
13
14
|
steps:
|
14
15
|
- uses: actions/checkout@v2
|
15
|
-
- name:
|
16
|
+
- name: Set up Ruby
|
16
17
|
uses: ruby/setup-ruby@v1
|
17
18
|
with:
|
18
19
|
ruby-version: ${{ matrix.ruby }}
|
19
|
-
|
20
|
-
with:
|
21
|
-
path: vendor/bundle
|
22
|
-
key: ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles('Gemfile', 'frozen_record.gemspec') }}
|
23
|
-
restore-keys: |
|
24
|
-
${{ runner.os }}-${{ matrix.ruby }}-gems-
|
25
|
-
- name: Bundle install
|
26
|
-
run: |
|
27
|
-
gem install bundler
|
28
|
-
bundle install --jobs 4 --retry 3 --path=vendor/bundle
|
20
|
+
bundler-cache: true
|
29
21
|
- name: Run tests
|
30
22
|
env:
|
31
23
|
MINIMAL: ${{ matrix.minimal }}
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
# v0.25.0
|
4
|
+
|
5
|
+
- Disable max_records_scan checks when loading records.
|
6
|
+
- Add `FrozenRecord::Base.with_max_record_scan` for more easily allowing larger amount in specific tests.
|
7
|
+
|
8
|
+
# v0.24.1
|
9
|
+
|
10
|
+
- Fix index selection not applying some restrictions.
|
11
|
+
|
12
|
+
# v0.24.0 (yanked)
|
13
|
+
|
14
|
+
- Improve index selection and combinaison. Should significantly help with performance in some cases.
|
15
|
+
- Implement `max_records_scan` to reject slow queries.
|
16
|
+
- Only load `Railtie` integration if `Rails::Railtie` is defined
|
17
|
+
- Allow granular fixture unloading
|
18
|
+
- Fix a bug affecting older bootsnap versions
|
19
|
+
|
20
|
+
# v0.23.0
|
21
|
+
|
22
|
+
NO DATA
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[![Build Status](https://secure.travis-ci.org/byroot/frozen_record.svg)](http://travis-ci.org/byroot/frozen_record)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/frozen_record.svg)](http://badge.fury.io/rb/frozen_record)
|
5
5
|
|
6
|
-
|
6
|
+
Activec Record-like interface for **read only** access to static data files of reasonable size.
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
|
@@ -21,7 +21,7 @@ Or install it yourself as:
|
|
21
21
|
|
22
22
|
## Models definition
|
23
23
|
|
24
|
-
Just like with
|
24
|
+
Just like with Active Record, your models need to inherits from `FrozenRecord::Base`:
|
25
25
|
|
26
26
|
```ruby
|
27
27
|
class Country < FrozenRecord::Base
|
@@ -72,7 +72,7 @@ end
|
|
72
72
|
|
73
73
|
## Query interface
|
74
74
|
|
75
|
-
FrozenRecord aim to replicate only modern
|
75
|
+
FrozenRecord aim to replicate only modern Active Record querying interface, and only the non "string typed" ones.
|
76
76
|
|
77
77
|
e.g
|
78
78
|
```ruby
|
@@ -154,6 +154,13 @@ Composite index keys are not supported.
|
|
154
154
|
|
155
155
|
The primary key isn't indexed by default.
|
156
156
|
|
157
|
+
## Limitations
|
158
|
+
|
159
|
+
Frozen Record is not meant to operate or large unindexed datasets.
|
160
|
+
|
161
|
+
To ensure that it doesn't happen by accident, you can set `FrozenRecord::Base.max_records_scan = 500` (or whatever limit makes sense to you), in your development and test environments.
|
162
|
+
This setting will cause Frozen Record to raise an error if it has to scan more than `max_records_scan` records. This property can also be set on a per model basis.
|
163
|
+
|
157
164
|
## Configuration
|
158
165
|
|
159
166
|
### Reloading
|
@@ -190,7 +197,7 @@ require 'frozen_record/test_helper'
|
|
190
197
|
|
191
198
|
class CountryTest < ActiveSupport::TestCase
|
192
199
|
setup do
|
193
|
-
test_fixtures_base_path = Rails.root.join(
|
200
|
+
test_fixtures_base_path = Rails.root.join('test/support/fixtures')
|
194
201
|
FrozenRecord::TestHelper.load_fixture(Country, test_fixtures_base_path)
|
195
202
|
end
|
196
203
|
|
@@ -0,0 +1 @@
|
|
1
|
+
true
|
data/lib/frozen_record/base.rb
CHANGED
@@ -4,6 +4,8 @@ require 'active_support/descendants_tracker'
|
|
4
4
|
require 'frozen_record/backends'
|
5
5
|
|
6
6
|
module FrozenRecord
|
7
|
+
SlowQuery = Class.new(StandardError)
|
8
|
+
|
7
9
|
class Base
|
8
10
|
extend ActiveSupport::DescendantsTracker
|
9
11
|
extend ActiveModel::Naming
|
@@ -15,6 +17,7 @@ module FrozenRecord
|
|
15
17
|
|
16
18
|
class_attribute :base_path, :primary_key, :backend, :auto_reloading, :default_attributes, instance_accessor: false
|
17
19
|
class_attribute :index_definitions, instance_accessor: false
|
20
|
+
class_attribute :max_records_scan, instance_accessor: false
|
18
21
|
self.index_definitions = {}.freeze
|
19
22
|
|
20
23
|
self.primary_key = 'id'
|
@@ -42,6 +45,14 @@ module FrozenRecord
|
|
42
45
|
end
|
43
46
|
|
44
47
|
class << self
|
48
|
+
def with_max_records_scan(value)
|
49
|
+
previous_max_records_scan = max_records_scan
|
50
|
+
self.max_records_scan = value
|
51
|
+
yield
|
52
|
+
ensure
|
53
|
+
self.max_records_scan = previous_max_records_scan
|
54
|
+
end
|
55
|
+
|
45
56
|
alias_method :set_default_attributes, :default_attributes=
|
46
57
|
private :set_default_attributes
|
47
58
|
def default_attributes=(default_attributes)
|
@@ -145,7 +156,7 @@ module FrozenRecord
|
|
145
156
|
end
|
146
157
|
@attributes = list_attributes(records).freeze
|
147
158
|
define_attribute_methods(@attributes.to_a)
|
148
|
-
records = records.map { |r| load(r) }.freeze
|
159
|
+
records = with_max_records_scan(nil) { records.map { |r| load(r) }.freeze }
|
149
160
|
index_definitions.values.each { |index| index.build(records) }
|
150
161
|
records
|
151
162
|
end
|
data/lib/frozen_record/scope.rb
CHANGED
@@ -173,6 +173,8 @@ module FrozenRecord
|
|
173
173
|
sort_records(select_records(@klass.load_records))
|
174
174
|
end
|
175
175
|
|
176
|
+
ARRAY_INTERSECTION = Array.method_defined?(:intersection)
|
177
|
+
|
176
178
|
def select_records(records)
|
177
179
|
return records if @where_values.empty? && @where_not_values.empty?
|
178
180
|
|
@@ -180,13 +182,30 @@ module FrozenRecord
|
|
180
182
|
indexed_where_values, unindexed_where_values = @where_values.partition { |criteria| indices.key?(criteria.first) }
|
181
183
|
|
182
184
|
unless indexed_where_values.empty?
|
183
|
-
attribute, value
|
184
|
-
|
185
|
-
|
186
|
-
|
185
|
+
usable_indexes = indexed_where_values.map { |(attribute, value)| [attribute, value, indices[attribute].query(value)] }
|
186
|
+
usable_indexes.sort_by! { |r| r[2].size }
|
187
|
+
records = usable_indexes.shift.last
|
188
|
+
|
189
|
+
# If the index is 5 times bigger that the current set of records it's not worth doing an array intersection.
|
190
|
+
# The value is somewhat arbitrary and could be adjusted.
|
191
|
+
useless_indexes, usable_indexes = usable_indexes.partition { |_, _, indexed_records| indexed_records.size > records.size * 5 }
|
192
|
+
unindexed_where_values += useless_indexes.map { |a| a.first(2) }
|
193
|
+
|
194
|
+
unless usable_indexes.empty?
|
195
|
+
if ARRAY_INTERSECTION
|
196
|
+
records = records.intersection(*usable_indexes.map(&:last))
|
197
|
+
else
|
198
|
+
usable_indexes.each do |_, _, indexed_records|
|
199
|
+
records &= indexed_records
|
200
|
+
end
|
201
|
+
end
|
187
202
|
end
|
188
203
|
end
|
189
204
|
|
205
|
+
if @klass.max_records_scan && records.size > @klass.max_records_scan
|
206
|
+
raise SlowQuery, "Scanning #{records.size} records is too slow, the allowed maximum is #{@klass.max_records_scan}. Try to find a better index or consider an alternative storage"
|
207
|
+
end
|
208
|
+
|
190
209
|
records.select do |record|
|
191
210
|
unindexed_where_values.all? { |attr, matcher| matcher.match?(record[attr]) } &&
|
192
211
|
!@where_not_values.any? { |attr, matcher| matcher.match?(record[attr]) }
|
@@ -8,9 +8,7 @@ module FrozenRecord
|
|
8
8
|
def load_fixture(model_class, alternate_base_path)
|
9
9
|
@cache ||= {}
|
10
10
|
|
11
|
-
|
12
|
-
raise ArgumentError, "Model class (#{model_class}) does not inherit from #{FrozenRecord::Base}"
|
13
|
-
end
|
11
|
+
ensure_model_class_is_frozenrecord(model_class)
|
14
12
|
|
15
13
|
return if @cache.key?(model_class)
|
16
14
|
|
@@ -20,20 +18,32 @@ module FrozenRecord
|
|
20
18
|
model_class.load_records(force: true)
|
21
19
|
end
|
22
20
|
|
23
|
-
def
|
21
|
+
def unload_fixture(model_class)
|
24
22
|
return unless defined?(@cache) && @cache
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
ensure_model_class_is_frozenrecord(model_class)
|
25
|
+
|
26
|
+
return unless @cache.key?(model_class)
|
27
|
+
|
28
|
+
old_base_path = @cache[model_class]
|
29
|
+
model_class.base_path = old_base_path
|
30
|
+
model_class.load_records(force: true)
|
30
31
|
|
31
|
-
@cache
|
32
|
+
@cache.delete(model_class)
|
33
|
+
end
|
34
|
+
|
35
|
+
def unload_fixtures
|
36
|
+
return unless defined?(@cache) && @cache
|
37
|
+
|
38
|
+
@cache.keys.each { |model_class| unload_fixture(model_class) }
|
32
39
|
end
|
33
40
|
|
34
41
|
private
|
35
42
|
|
36
43
|
def ensure_model_class_is_frozenrecord(model_class)
|
44
|
+
unless model_class < FrozenRecord::Base
|
45
|
+
raise ArgumentError, "Model class (#{model_class}) does not inherit from #{FrozenRecord::Base}"
|
46
|
+
end
|
37
47
|
end
|
38
48
|
end
|
39
49
|
end
|
data/lib/frozen_record.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
<% 100.times.map { |i| "plan_#{i}"}.each do |plan_name| %>
|
2
|
+
<% ["base", "online", "retail"].each do |type| %>
|
3
|
+
<% [nil, "USD", "EUR", "GBP", "JPY"].each do |currency| %>
|
4
|
+
<% ["monthly", "annual", "biennial", "triennial"].each do |period| %>
|
5
|
+
- plan_name: <%= plan_name %>
|
6
|
+
type: <%= type %>
|
7
|
+
period: <%= period %>
|
8
|
+
currency: <%= currency %>
|
9
|
+
amount: 10.0
|
10
|
+
<% end %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
data/spec/scope_spec.rb
CHANGED
@@ -474,4 +474,30 @@ describe 'querying' do
|
|
474
474
|
|
475
475
|
end
|
476
476
|
|
477
|
+
context 'when max_records_scan is set' do
|
478
|
+
|
479
|
+
it 'raises on slow queries' do
|
480
|
+
expect {
|
481
|
+
FrozenRecord::Base.with_max_records_scan(1) do
|
482
|
+
Country.where(king: "Louis").to_a
|
483
|
+
end
|
484
|
+
}.to raise_error(FrozenRecord::SlowQuery)
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'is accurate' do
|
488
|
+
FrozenRecord::Base.with_max_records_scan(60) do
|
489
|
+
expect(Price.count).to be == 6_000
|
490
|
+
|
491
|
+
Price.where(plan_name: "plan_24", currency: [nil, "EUR"], period: "monthly", type: "base").each do |price|
|
492
|
+
expect(price.plan_name).to be == "plan_24"
|
493
|
+
unless price.currency.nil?
|
494
|
+
expect(price.currency).to be == "EUR"
|
495
|
+
end
|
496
|
+
expect(price.period).to be == "monthly"
|
497
|
+
expect(price.type).to be == "base"
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
end
|
477
503
|
end
|
data/spec/test_helper_spec.rb
CHANGED
@@ -38,19 +38,35 @@ describe 'test fixture loading' do
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
describe '.unload_fixture' do
|
42
|
+
it 'restores the default fixtures for the specified model class' do
|
43
|
+
test_fixtures_base_path = File.join(File.dirname(__FILE__), 'fixtures', 'test_helper')
|
44
|
+
|
45
|
+
FrozenRecord::TestHelper.load_fixture(Continent, test_fixtures_base_path)
|
46
|
+
FrozenRecord::TestHelper.load_fixture(Country, test_fixtures_base_path)
|
47
|
+
FrozenRecord::TestHelper.unload_fixture(Country)
|
48
|
+
|
49
|
+
expect(Continent.count).to be == 1
|
50
|
+
expect(Country.count).to be == 3
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
41
54
|
describe '.unload_fixtures' do
|
42
55
|
it 'restores the default fixtures' do
|
43
56
|
test_fixtures_base_path = File.join(File.dirname(__FILE__), 'fixtures', 'test_helper')
|
44
57
|
|
58
|
+
FrozenRecord::TestHelper.load_fixture(Continent, test_fixtures_base_path)
|
45
59
|
FrozenRecord::TestHelper.load_fixture(Country, test_fixtures_base_path)
|
46
60
|
FrozenRecord::TestHelper.unload_fixtures
|
47
61
|
|
62
|
+
expect(Continent.count).to be == 3
|
48
63
|
expect(Country.count).to be == 3
|
49
64
|
end
|
50
65
|
|
51
66
|
it 'does has no effect if no alternate fixtures were loaded' do
|
52
67
|
FrozenRecord::TestHelper.unload_fixtures
|
53
68
|
|
69
|
+
expect(Continent.count).to be == 3
|
54
70
|
expect(Country.count).to be == 3
|
55
71
|
end
|
56
72
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: frozen_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- ".github/workflows/main.yml"
|
77
77
|
- ".gitignore"
|
78
78
|
- ".rspec"
|
79
|
+
- CHANGELOG.md
|
79
80
|
- Gemfile
|
80
81
|
- LICENSE.txt
|
81
82
|
- README.md
|
@@ -88,6 +89,7 @@ files:
|
|
88
89
|
- lib/frozen_record.rb
|
89
90
|
- lib/frozen_record/backends.rb
|
90
91
|
- lib/frozen_record/backends/empty.json
|
92
|
+
- lib/frozen_record/backends/empty.yml
|
91
93
|
- lib/frozen_record/backends/json.rb
|
92
94
|
- lib/frozen_record/backends/yaml.rb
|
93
95
|
- lib/frozen_record/base.rb
|
@@ -101,7 +103,10 @@ files:
|
|
101
103
|
- lib/frozen_record/version.rb
|
102
104
|
- spec/fixtures/animals.json
|
103
105
|
- spec/fixtures/cars.yml
|
106
|
+
- spec/fixtures/continents.yml.erb
|
104
107
|
- spec/fixtures/countries.yml.erb
|
108
|
+
- spec/fixtures/prices.yml.erb
|
109
|
+
- spec/fixtures/test_helper/continents.yml.erb
|
105
110
|
- spec/fixtures/test_helper/countries.yml.erb
|
106
111
|
- spec/frozen_record_spec.rb
|
107
112
|
- spec/scope_spec.rb
|
@@ -109,7 +114,9 @@ files:
|
|
109
114
|
- spec/support/abstract_model.rb
|
110
115
|
- spec/support/animal.rb
|
111
116
|
- spec/support/car.rb
|
117
|
+
- spec/support/continent.rb
|
112
118
|
- spec/support/country.rb
|
119
|
+
- spec/support/price.rb
|
113
120
|
- spec/test_helper_spec.rb
|
114
121
|
homepage: https://github.com/byroot/frozen_record
|
115
122
|
licenses:
|
@@ -130,14 +137,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
137
|
- !ruby/object:Gem::Version
|
131
138
|
version: '0'
|
132
139
|
requirements: []
|
133
|
-
rubygems_version: 3.3.
|
140
|
+
rubygems_version: 3.3.7
|
134
141
|
signing_key:
|
135
142
|
specification_version: 4
|
136
143
|
summary: ActiveRecord like interface to read only access and query static YAML files
|
137
144
|
test_files:
|
138
145
|
- spec/fixtures/animals.json
|
139
146
|
- spec/fixtures/cars.yml
|
147
|
+
- spec/fixtures/continents.yml.erb
|
140
148
|
- spec/fixtures/countries.yml.erb
|
149
|
+
- spec/fixtures/prices.yml.erb
|
150
|
+
- spec/fixtures/test_helper/continents.yml.erb
|
141
151
|
- spec/fixtures/test_helper/countries.yml.erb
|
142
152
|
- spec/frozen_record_spec.rb
|
143
153
|
- spec/scope_spec.rb
|
@@ -145,5 +155,7 @@ test_files:
|
|
145
155
|
- spec/support/abstract_model.rb
|
146
156
|
- spec/support/animal.rb
|
147
157
|
- spec/support/car.rb
|
158
|
+
- spec/support/continent.rb
|
148
159
|
- spec/support/country.rb
|
160
|
+
- spec/support/price.rb
|
149
161
|
- spec/test_helper_spec.rb
|