frozen_record 0.23.0 → 0.24.1
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 +17 -0
- data/README.md +10 -3
- data/lib/frozen_record/backends/empty.yml +1 -0
- data/lib/frozen_record/backends/yaml.rb +1 -1
- data/lib/frozen_record/base.rb +3 -0
- 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/test_helper/continents.yml.erb +3 -0
- data/spec/scope_spec.rb +15 -0
- data/spec/support/continent.rb +2 -0
- data/spec/test_helper_spec.rb +16 -0
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b99597156ea028c8d0cbd66024b901d55a5e26679030266367caa8ec81353946
|
4
|
+
data.tar.gz: 12e4756a7a49d32010298b4320f447d9b18afd76efe969a1f3a32e7d890592de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fcb9c7aa9c820dfa50b431d36bef98d39488be55fa034edaed7aefde063a919134af0d945e3233ca5a6efbc3c3798245a5ea5741b5ae1f4d8dad1b0189b1bf7e
|
7
|
+
data.tar.gz: df184ce79d274a9fe8a6fcb70c9548bb18f17c1e8da143c88d2ab9f2aedec746314a21ee2e591e191fd248f8d5e332dfdcc379f316d09fcb070a5284724f43ad
|
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,17 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
# v0.24.1
|
4
|
+
|
5
|
+
- Fix index selection not applying some restrictions.
|
6
|
+
|
7
|
+
# v0.24.0 (yanked)
|
8
|
+
|
9
|
+
- Improve index selection and combinaison. Should significantly help with performance in some cases.
|
10
|
+
- Implement `max_records_scan` to reject slow queries.
|
11
|
+
- Only load `Railtie` integration if `Rails::Railtie` is defined
|
12
|
+
- Allow granular fixture unloading
|
13
|
+
- Fix a bug affecting older bootsnap versions
|
14
|
+
|
15
|
+
# v0.23.0
|
16
|
+
|
17
|
+
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
|
@@ -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'
|
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
data/spec/scope_spec.rb
CHANGED
@@ -474,4 +474,19 @@ describe 'querying' do
|
|
474
474
|
|
475
475
|
end
|
476
476
|
|
477
|
+
context 'when max_records_scan is set' do
|
478
|
+
|
479
|
+
around :each do |example|
|
480
|
+
FrozenRecord::Base.max_records_scan = 1
|
481
|
+
example.run
|
482
|
+
FrozenRecord::Base.max_records_scan = nil
|
483
|
+
end
|
484
|
+
|
485
|
+
it 'raises on slow queries' do
|
486
|
+
expect {
|
487
|
+
Country.where(king: "Louis").to_a
|
488
|
+
}.to raise_error(FrozenRecord::SlowQuery)
|
489
|
+
end
|
490
|
+
|
491
|
+
end
|
477
492
|
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.24.1
|
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-10 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,9 @@ 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/test_helper/continents.yml.erb
|
105
109
|
- spec/fixtures/test_helper/countries.yml.erb
|
106
110
|
- spec/frozen_record_spec.rb
|
107
111
|
- spec/scope_spec.rb
|
@@ -109,6 +113,7 @@ files:
|
|
109
113
|
- spec/support/abstract_model.rb
|
110
114
|
- spec/support/animal.rb
|
111
115
|
- spec/support/car.rb
|
116
|
+
- spec/support/continent.rb
|
112
117
|
- spec/support/country.rb
|
113
118
|
- spec/test_helper_spec.rb
|
114
119
|
homepage: https://github.com/byroot/frozen_record
|
@@ -130,14 +135,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
135
|
- !ruby/object:Gem::Version
|
131
136
|
version: '0'
|
132
137
|
requirements: []
|
133
|
-
rubygems_version: 3.
|
138
|
+
rubygems_version: 3.3.7
|
134
139
|
signing_key:
|
135
140
|
specification_version: 4
|
136
141
|
summary: ActiveRecord like interface to read only access and query static YAML files
|
137
142
|
test_files:
|
138
143
|
- spec/fixtures/animals.json
|
139
144
|
- spec/fixtures/cars.yml
|
145
|
+
- spec/fixtures/continents.yml.erb
|
140
146
|
- spec/fixtures/countries.yml.erb
|
147
|
+
- spec/fixtures/test_helper/continents.yml.erb
|
141
148
|
- spec/fixtures/test_helper/countries.yml.erb
|
142
149
|
- spec/frozen_record_spec.rb
|
143
150
|
- spec/scope_spec.rb
|
@@ -145,5 +152,6 @@ test_files:
|
|
145
152
|
- spec/support/abstract_model.rb
|
146
153
|
- spec/support/animal.rb
|
147
154
|
- spec/support/car.rb
|
155
|
+
- spec/support/continent.rb
|
148
156
|
- spec/support/country.rb
|
149
157
|
- spec/test_helper_spec.rb
|