frozen_record 0.22.1 → 0.24.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f9b741714b43651cc824bf8fe992752dd4425f2357eaf6a995531cc37b3a224
4
- data.tar.gz: c3700095fba0b7f56722e3b4ee515a81fed36542fc8e1dadc3854610a32f4ff1
3
+ metadata.gz: b99597156ea028c8d0cbd66024b901d55a5e26679030266367caa8ec81353946
4
+ data.tar.gz: 12e4756a7a49d32010298b4320f447d9b18afd76efe969a1f3a32e7d890592de
5
5
  SHA512:
6
- metadata.gz: 6783fd2e014e11c2348cab5ab4926b12edf5927f347f311d4e7ecea80ab556db2952489c4e9a607936e3d32e052b89874050895c3fb27816c8a907bda7656d9d
7
- data.tar.gz: 6afe414bdce72a1eb9ed68f780af2f20dde1e4aa43b155a5542096a64361434716b14cbaface580de7893e1e15416b6daffe4b875e30878c9acdffce9b1e5662
6
+ metadata.gz: fcb9c7aa9c820dfa50b431d36bef98d39488be55fa034edaed7aefde063a919134af0d945e3233ca5a6efbc3c3798245a5ea5741b5ae1f4d8dad1b0189b1bf7e
7
+ data.tar.gz: df184ce79d274a9fe8a6fcb70c9548bb18f17c1e8da143c88d2ab9f2aedec746314a21ee2e591e191fd248f8d5e332dfdcc379f316d09fcb070a5284724f43ad
@@ -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: Setup Ruby
16
+ - name: Set up Ruby
16
17
  uses: ruby/setup-ruby@v1
17
18
  with:
18
19
  ruby-version: ${{ matrix.ruby }}
19
- - uses: actions/cache@v2
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
- ActiveRecord-like interface for **read only** access to static data files.
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 ActiveRecord, your models need to inherits from `FrozenRecord::Base`:
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 ActiveRecord querying interface, and only the non "string typed" ones.
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(%w(test support fixtures))
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
@@ -49,12 +49,20 @@ module FrozenRecord
49
49
  private
50
50
 
51
51
  supports_freeze = begin
52
- YAML.load_file(File.expand_path('../empty.json', __FILE__), freeze: true)
52
+ YAML.load_file(File.expand_path('../empty.yml', __FILE__), freeze: true)
53
53
  rescue ArgumentError
54
54
  false
55
55
  end
56
56
 
57
- if supports_freeze
57
+ if YAML.respond_to?(:unsafe_load_file)
58
+ def load_file(path)
59
+ YAML.unsafe_load_file(path, freeze: true) || Dedup::EMPTY_ARRAY
60
+ end
61
+
62
+ def load_string(yaml)
63
+ YAML.unsafe_load(yaml, freeze: true) || Dedup::EMPTY_ARRAY
64
+ end
65
+ elsif supports_freeze
58
66
  def load_file(path)
59
67
  YAML.load_file(path, freeze: true) || Dedup::EMPTY_ARRAY
60
68
  end
@@ -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'
@@ -23,5 +23,5 @@ module FrozenRecord
23
23
  end
24
24
  end
25
25
 
26
- self.deprecated_yaml_erb_backend = true
26
+ self.deprecated_yaml_erb_backend = false
27
27
  end
@@ -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 = indexed_where_values.shift
184
- records = indices[attribute].query(value)
185
- indexed_where_values.each do |(attribute, value)|
186
- records &= indices[attribute].query(value)
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
- unless model_class < FrozenRecord::Base
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 unload_fixtures
21
+ def unload_fixture(model_class)
24
22
  return unless defined?(@cache) && @cache
25
23
 
26
- @cache.each do |model_class, old_base_path|
27
- model_class.base_path = old_base_path
28
- model_class.load_records(force: true)
29
- end
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 = nil
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FrozenRecord
4
- VERSION = '0.22.1'
4
+ VERSION = '0.24.1'
5
5
  end
data/lib/frozen_record.rb CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  require 'frozen_record/minimal'
4
4
  require 'frozen_record/serialization'
5
- require 'frozen_record/railtie' if defined?(Rails)
5
+ require 'frozen_record/railtie' if defined?(Rails::Railtie)
@@ -0,0 +1,9 @@
1
+ ---
2
+ - id: 1
3
+ name: Africa
4
+
5
+ - id: 2
6
+ name: Antarctica
7
+
8
+ - id: 3
9
+ name: Asia
@@ -0,0 +1,3 @@
1
+ ---
2
+ - id: 1
3
+ name: Some continent
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
@@ -0,0 +1,2 @@
1
+ class Continent < FrozenRecord::Base
2
+ end
@@ -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.22.1
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: 2021-04-30 00:00:00.000000000 Z
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.1.4
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