frozen_record 0.19.5 → 0.22.0

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: 9a5344be7378de1fb30f456b1a44b455dce2cec5d5db848058e8f3a137eb0ae7
4
- data.tar.gz: a8ed04973c5c1edd7600d180c8fc827bdb9312ec0a7e602d408373eb86ff2749
3
+ metadata.gz: bfb835436fc605e43a6b928ae9e8149ec80f726dd0650801c3e1e2e4d46dba1d
4
+ data.tar.gz: 1b2d08e68b018cb8a0c18e68420dab5930c51c3cf42c559c841eb340b0b0226d
5
5
  SHA512:
6
- metadata.gz: 8ec04aa72dfbee08525e8f4d6bea2413e7c84142f0f3bb8f6b8dc57538be10dcedfb07a7fd12f0c2992f292fb91ea492c8b6a21ab431470de551647b218b5dc2
7
- data.tar.gz: 03ac7a0e64dd6caa6f519611096130c4bb33ac77467792b4c48d9a368f31abb91a22ed4a0d370e94e76b1a63b91aaec06b3bee1d38fcc9761edc47ac4beabe16
6
+ metadata.gz: 8d7ae3739cc1a172d8a54e1f87513b307db69c937522032fbcf845a4f11141f7e373739f79c98e15216f6ce17ac4a0410cc6f72033b9a9cbb31ec260e650ea6c
7
+ data.tar.gz: 9f88ee2a8186533cbd8482704d2e02bc275e73af310b24801b7cebb37320ab6297038b3efa6368ea9ce0122de7aaaa56f1180fed4c9eefe1dc5de059e35ccbd0
@@ -0,0 +1,32 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby: [ '2.5', '2.6', '2.7', '3.0' ]
11
+ minimal: [ false, true ]
12
+ name: Ruby ${{ matrix.ruby }} tests, minimal=${{ matrix.minimal }}
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Setup Ruby
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ 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
29
+ - name: Run tests
30
+ env:
31
+ MINIMAL: ${{ matrix.minimal }}
32
+ run: bundle exec rake
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # FrozenRecord
2
2
 
3
3
  [![Build Status](https://secure.travis-ci.org/byroot/frozen_record.svg)](http://travis-ci.org/byroot/frozen_record)
4
- [![Code Climate](https://codeclimate.com/github/byroot/frozen_record.svg)](https://codeclimate.com/github/byroot/frozen_record)
5
- [![Coverage Status](https://coveralls.io/repos/byroot/frozen_record/badge.svg)](https://coveralls.io/r/byroot/frozen_record)
6
4
  [![Gem Version](https://badge.fury.io/rb/frozen_record.svg)](http://badge.fury.io/rb/frozen_record)
7
5
 
8
6
  ActiveRecord-like interface for **read only** access to static data files.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'setup'
5
+
6
+ records = FrozenRecord::Backends::Yaml.load(Country.file_path)
7
+ large_records = 100.times.flat_map do |i|
8
+ records.map do |record|
9
+ new_record = record.dup
10
+ new_record['id'] += 10 * i
11
+ new_record['name'] += i.to_s
12
+ new_record
13
+ end
14
+ end
15
+
16
+ File.write('/tmp/frozen-record-bench-countries.yml', YAML.dump(large_records))
17
+
18
+ class LargeCountry < Country
19
+ class << self
20
+ def file_path
21
+ '/tmp/frozen-record-bench-countries.yml'
22
+ end
23
+ end
24
+ end
25
+
26
+ puts "=== simple scalar match ==="
27
+ Benchmark.ips do |x|
28
+ x.report('simple') { LargeCountry.nato.size }
29
+ end
30
+
31
+ puts "=== range match ==="
32
+ Benchmark.ips do |x|
33
+ x.report('range') { LargeCountry.where(density: 100...200).size }
34
+ end
@@ -20,9 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.required_ruby_version = '>= 2.5'
21
21
 
22
22
  spec.add_runtime_dependency 'activemodel'
23
- spec.add_development_dependency 'bundler'
23
+ spec.add_runtime_dependency 'dedup'
24
+
24
25
  spec.add_development_dependency 'rake'
25
26
  spec.add_development_dependency 'rspec'
26
- spec.add_development_dependency 'pry'
27
- spec.add_development_dependency 'coveralls'
28
27
  end
data/lib/frozen_record.rb CHANGED
@@ -1,29 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
- require 'set'
5
- require 'active_support/all'
6
- require 'active_model'
7
-
8
- require 'frozen_record/version'
9
- require 'frozen_record/scope'
10
- require 'frozen_record/index'
11
- require 'frozen_record/base'
12
- require 'frozen_record/compact'
13
- require 'frozen_record/deduplication'
14
-
15
- module FrozenRecord
16
- RecordNotFound = Class.new(StandardError)
17
-
18
- class << self
19
- attr_accessor :deprecated_yaml_erb_backend
20
-
21
- def eager_load!
22
- Base.descendants.each(&:eager_load!)
23
- end
24
- end
25
-
26
- self.deprecated_yaml_erb_backend = true
27
- end
28
-
3
+ require 'frozen_record/minimal'
4
+ require 'frozen_record/serialization'
29
5
  require 'frozen_record/railtie' if defined?(Rails)
@@ -0,0 +1 @@
1
+ true
@@ -9,9 +9,26 @@ module FrozenRecord
9
9
  "#{model_name.underscore.pluralize}.json"
10
10
  end
11
11
 
12
- def load(file_path)
13
- json_data = File.read(file_path)
14
- JSON.parse(json_data) || []
12
+ if JSON.respond_to?(:load_file)
13
+ supports_freeze = begin
14
+ JSON.load_file(File.expand_path('../empty.json', __FILE__), freeze: true)
15
+ rescue ArgumentError
16
+ false
17
+ end
18
+
19
+ if supports_freeze
20
+ def load(file_path)
21
+ JSON.load_file(file_path, freeze: true) || Dedup::EMPTY_ARRAY
22
+ end
23
+ else
24
+ def load(file_path)
25
+ Dedup.deep_intern!(JSON.load_file(file_path) || Dedup::EMPTY_ARRAY)
26
+ end
27
+ end
28
+ else
29
+ def load(file_path)
30
+ Dedup.deep_intern!(JSON.parse(File.read(file_path)) || Dedup::EMPTY_ARRAY)
31
+ end
15
32
  end
16
33
  end
17
34
  end
@@ -3,6 +3,8 @@
3
3
  module FrozenRecord
4
4
  module Backends
5
5
  module Yaml
6
+ autoload :ERB, 'erb'
7
+
6
8
  extend self
7
9
 
8
10
  # Transforms the model name into a valid filename.
@@ -34,15 +36,41 @@ module FrozenRecord
34
36
  end
35
37
  end
36
38
 
37
- YAML.load(yml_data) || []
39
+ load_string(yml_data)
38
40
  else
39
41
  if file_path.end_with?('.erb')
40
- YAML.load(ERB.new(File.read(file_path)).result) || []
42
+ load_string(ERB.new(File.read(file_path)).result)
41
43
  else
42
- YAML.load_file(file_path) || []
44
+ load_file(file_path)
43
45
  end
44
46
  end
45
47
  end
48
+
49
+ private
50
+
51
+ supports_freeze = begin
52
+ YAML.load_file(File.expand_path('../empty.json', __FILE__), freeze: true)
53
+ rescue ArgumentError
54
+ false
55
+ end
56
+
57
+ if supports_freeze
58
+ def load_file(path)
59
+ YAML.load_file(path, freeze: true) || Dedup::EMPTY_ARRAY
60
+ end
61
+
62
+ def load_string(yaml)
63
+ YAML.load(yaml, freeze: true) || Dedup::EMPTY_ARRAY
64
+ end
65
+ else
66
+ def load_file(path)
67
+ Dedup.deep_intern!(YAML.load_file(path) || Dedup::EMPTY_ARRAY)
68
+ end
69
+
70
+ def load_string(yaml)
71
+ Dedup.deep_intern!(YAML.load(yaml) || Dedup::EMPTY_ARRAY)
72
+ end
73
+ end
46
74
  end
47
75
  end
48
76
  end
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
3
  require 'active_support/descendants_tracker'
5
4
  require 'frozen_record/backends'
6
- require 'objspace'
7
5
 
8
6
  module FrozenRecord
9
7
  class Base
@@ -11,11 +9,6 @@ module FrozenRecord
11
9
  extend ActiveModel::Naming
12
10
  include ActiveModel::Conversion
13
11
  include ActiveModel::AttributeMethods
14
- include ActiveModel::Serializers::JSON
15
-
16
- if defined? ActiveModel::Serializers::Xml
17
- include ActiveModel::Serializers::Xml
18
- end
19
12
 
20
13
  FIND_BY_PATTERN = /\Afind_by_(\w+)(!?)/
21
14
  FALSY_VALUES = [false, nil, 0, -''].to_set
@@ -52,7 +45,7 @@ module FrozenRecord
52
45
  alias_method :set_default_attributes, :default_attributes=
53
46
  private :set_default_attributes
54
47
  def default_attributes=(default_attributes)
55
- set_default_attributes(Deduplication.deep_deduplicate!(default_attributes.stringify_keys))
48
+ set_default_attributes(Dedup.deep_intern!(default_attributes.transform_keys(&:to_s)))
56
49
  end
57
50
 
58
51
  alias_method :set_primary_key, :primary_key=
@@ -90,8 +83,9 @@ module FrozenRecord
90
83
  store[:scope] = scope
91
84
  end
92
85
 
93
- delegate :find, :find_by_id, :find_by, :find_by!, :where, :first, :first!, :last, :last!, :pluck, :ids, :order, :limit, :offset,
94
- :minimum, :maximum, :average, :sum, :count, to: :current_scope
86
+ delegate :each, :find, :find_each, :find_by_id, :find_by, :find_by!, :where, :first, :first!, :last, :last!,
87
+ :pluck, :ids, :order, :limit, :offset, :minimum, :maximum, :average, :sum, :count,
88
+ to: :current_scope
95
89
 
96
90
  def file_path
97
91
  raise ArgumentError, "You must define `#{name}.base_path`" unless base_path
@@ -146,8 +140,9 @@ module FrozenRecord
146
140
 
147
141
  @records ||= begin
148
142
  records = backend.load(file_path)
149
- records.each { |r| assign_defaults!(r) }
150
- records = Deduplication.deep_deduplicate!(records)
143
+ if default_attributes
144
+ records = records.map { |r| assign_defaults!(r.dup).freeze }.freeze
145
+ end
151
146
  @attributes = list_attributes(records).freeze
152
147
  define_attribute_methods(@attributes.to_a)
153
148
  records = records.map { |r| load(r) }.freeze
@@ -164,7 +159,7 @@ module FrozenRecord
164
159
  private :load
165
160
 
166
161
  def new(attrs = {})
167
- load(assign_defaults!(attrs.stringify_keys))
162
+ load(assign_defaults!(attrs.transform_keys(&:to_s)))
168
163
  end
169
164
 
170
165
  private
@@ -206,7 +201,9 @@ module FrozenRecord
206
201
  def list_attributes(records)
207
202
  attributes = Set.new
208
203
  records.each do |record|
209
- attributes.merge(record.keys)
204
+ record.each_key do |key|
205
+ attributes.add(key)
206
+ end
210
207
  end
211
208
  attributes
212
209
  end
@@ -245,7 +242,7 @@ module FrozenRecord
245
242
  private
246
243
 
247
244
  def attribute?(attribute_name)
248
- FALSY_VALUES.exclude?(self[attribute_name]) && self[attribute_name].present?
245
+ !FALSY_VALUES.include?(self[attribute_name]) && self[attribute_name].present?
249
246
  end
250
247
 
251
248
  def attribute_method?(attribute_name)
@@ -13,8 +13,9 @@ module FrozenRecord
13
13
 
14
14
  @records ||= begin
15
15
  records = backend.load(file_path)
16
- records.each { |r| assign_defaults!(r) }
17
- records = Deduplication.deep_deduplicate!(records)
16
+ if default_attributes
17
+ records = records.map { |r| assign_defaults!(r.dup).freeze }.freeze
18
+ end
18
19
  @attributes = list_attributes(records).freeze
19
20
  build_attributes_cache
20
21
  define_attribute_methods(@attributes.to_a)
@@ -67,13 +68,13 @@ module FrozenRecord
67
68
 
68
69
  def attributes=(attributes)
69
70
  self.class.attributes.each do |attr|
70
- instance_variable_set(self.class._attributes_cache[attr], Deduplication.deep_deduplicate!(attributes[attr]))
71
+ instance_variable_set(self.class._attributes_cache[attr], Dedup.deep_intern!(attributes[attr]))
71
72
  end
72
73
  end
73
74
 
74
75
  def attribute?(attribute_name)
75
76
  val = self[attribute_name]
76
- Base::FALSY_VALUES.exclude?(val) && val.present?
77
+ !Base::FALSY_VALUES.include?(val) && val.present?
77
78
  end
78
79
  end
79
80
  end
@@ -19,12 +19,11 @@ module FrozenRecord
19
19
  false
20
20
  end
21
21
 
22
- def query(value)
23
- case value
24
- when Array, Range
25
- lookup_multi(value)
22
+ def query(matcher)
23
+ if matcher.ranged?
24
+ lookup_multi(matcher.value)
26
25
  else
27
- lookup(value)
26
+ lookup(matcher.value)
28
27
  end
29
28
  end
30
29
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'set'
5
+ require 'active_model'
6
+
7
+ require 'dedup'
8
+
9
+ require 'frozen_record/version'
10
+ require 'frozen_record/scope'
11
+ require 'frozen_record/index'
12
+ require 'frozen_record/base'
13
+ require 'frozen_record/compact'
14
+
15
+ module FrozenRecord
16
+ RecordNotFound = Class.new(StandardError)
17
+
18
+ class << self
19
+ attr_accessor :deprecated_yaml_erb_backend
20
+
21
+ def eager_load!
22
+ Base.descendants.each(&:eager_load!)
23
+ end
24
+ end
25
+
26
+ self.deprecated_yaml_erb_backend = true
27
+ end
@@ -2,13 +2,14 @@
2
2
 
3
3
  module FrozenRecord
4
4
  class Scope
5
- BLACKLISTED_ARRAY_METHODS = [
5
+ DISALLOWED_ARRAY_METHODS = [
6
6
  :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
7
7
  :shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
8
8
  :keep_if, :pop, :shift, :delete_at, :compact
9
9
  ].to_set
10
10
 
11
11
  delegate :first, :last, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to_json, :as_json, :count, to: :to_a
12
+ alias_method :find_each, :each
12
13
  if defined? ActiveModel::Serializers::Xml
13
14
  delegate :to_xml, to: :to_a
14
15
  end
@@ -187,8 +188,8 @@ module FrozenRecord
187
188
  end
188
189
 
189
190
  records.select do |record|
190
- unindexed_where_values.all? { |attr, value| compare_value(record[attr], value) } &&
191
- !@where_not_values.any? { |attr, value| compare_value(record[attr], value) }
191
+ unindexed_where_values.all? { |attr, matcher| matcher.match?(record[attr]) } &&
192
+ !@where_not_values.any? { |attr, matcher| matcher.match?(record[attr]) }
192
193
  end
193
194
  end
194
195
 
@@ -227,22 +228,23 @@ module FrozenRecord
227
228
  super
228
229
  end
229
230
  end
231
+ ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
230
232
 
231
233
  def delegate_to_class(*args, &block)
232
234
  scoping { @klass.public_send(*args, &block) }
233
235
  end
234
236
 
235
237
  def array_delegable?(method)
236
- Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
238
+ Array.method_defined?(method) && !DISALLOWED_ARRAY_METHODS.include?(method)
237
239
  end
238
240
 
239
241
  def where!(criterias)
240
- @where_values += criterias.map { |k, v| [k.to_s, v] }
242
+ @where_values += criterias.map { |k, v| [k.to_s, Matcher.for(v)] }
241
243
  self
242
244
  end
243
245
 
244
246
  def where_not!(criterias)
245
- @where_not_values += criterias.map { |k, v| [k.to_s, v] }
247
+ @where_not_values += criterias.map { |k, v| [k.to_s, Matcher.for(v)] }
246
248
  self
247
249
  end
248
250
 
@@ -265,9 +267,62 @@ module FrozenRecord
265
267
 
266
268
  private
267
269
 
268
- def compare_value(actual, requested)
269
- return actual == requested unless requested.is_a?(Array) || requested.is_a?(Range)
270
- requested.include?(actual)
270
+ class Matcher
271
+ class << self
272
+ def for(value)
273
+ case value
274
+ when Array
275
+ IncludeMatcher.new(value)
276
+ when Range
277
+ CoverMatcher.new(value)
278
+ else
279
+ new(value)
280
+ end
281
+ end
282
+ end
283
+
284
+ attr_reader :value
285
+
286
+ def hash
287
+ self.class.hash ^ value.hash
288
+ end
289
+
290
+ def initialize(value)
291
+ @value = value
292
+ end
293
+
294
+ def ranged?
295
+ false
296
+ end
297
+
298
+ def match?(other)
299
+ @value == other
300
+ end
301
+
302
+ def ==(other)
303
+ self.class == other.class && value == other.value
304
+ end
305
+ alias_method :eql?, :==
306
+ end
307
+
308
+ class IncludeMatcher < Matcher
309
+ def ranged?
310
+ true
311
+ end
312
+
313
+ def match?(other)
314
+ @value.include?(other)
315
+ end
316
+ end
317
+
318
+ class CoverMatcher < Matcher
319
+ def ranged?
320
+ true
321
+ end
322
+
323
+ def match?(other)
324
+ @value.cover?(other)
325
+ end
271
326
  end
272
327
  end
273
328
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'frozen_record/base'
4
+
5
+ module FrozenRecord
6
+ class Base
7
+ include ActiveModel::Serializers::JSON
8
+
9
+ if defined? ActiveModel::Serializers::Xml
10
+ include ActiveModel::Serializers::Xml
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FrozenRecord
4
- VERSION = '0.19.5'
4
+ VERSION = '0.22.0'
5
5
  end
@@ -90,6 +90,32 @@ RSpec.shared_examples 'main' do
90
90
 
91
91
  end
92
92
 
93
+ describe '.each' do
94
+
95
+ it 'yields the records one by one' do
96
+ count = 0
97
+ Country.each do |country|
98
+ expect(country).to be_a Country
99
+ count += 1
100
+ end
101
+ expect(count).to be == Country.count
102
+ end
103
+
104
+ end
105
+
106
+ describe '.find_each' do
107
+
108
+ it 'yields the records one by one' do
109
+ count = 0
110
+ Country.find_each do |country|
111
+ expect(country).to be_a Country
112
+ count += 1
113
+ end
114
+ expect(count).to be == Country.count
115
+ end
116
+
117
+ end
118
+
93
119
  describe '.memsize' do
94
120
 
95
121
  it 'retuns the records memory footprint' do
data/spec/scope_spec.rb CHANGED
@@ -180,6 +180,20 @@ describe 'querying' do
180
180
 
181
181
  end
182
182
 
183
+ describe '.find_each' do
184
+
185
+ it 'yields the records one by one' do
186
+ scope = Country.where(name: 'France')
187
+ count = 0
188
+ scope.find_each do |country|
189
+ expect(country).to be_a Country
190
+ count += 1
191
+ end
192
+ expect(count).to be == 1
193
+ end
194
+
195
+ end
196
+
183
197
  describe '.where' do
184
198
 
185
199
  it 'returns the records that match given criterias' do
@@ -193,6 +207,11 @@ describe 'querying' do
193
207
  expect(countries.length).to be == 0
194
208
  end
195
209
 
210
+ it 'is chainable with methods of the form `def method(*args, **kargs)' do
211
+ countries = Country.republics.continent_and_capital('Europe', capital: 'Paris')
212
+ expect(countries.length).to be == 1
213
+ end
214
+
196
215
  it 'can be used with arrays' do
197
216
  countries = Country.where(id: [1,2])
198
217
  expect(countries.length).to be == 2
@@ -410,7 +429,7 @@ describe 'querying' do
410
429
 
411
430
  end
412
431
 
413
- describe '.as_json' do
432
+ describe '.as_json', exclude_minimal: true do
414
433
 
415
434
  it 'serialize the results' do
416
435
  json = Country.all.as_json
@@ -440,8 +459,8 @@ describe 'querying' do
440
459
  it 'returns true when the same scope has be rechained' do
441
460
  scope_a = Country.nato.republics.nato.republics
442
461
  scope_b = Country.republics.nato
443
- expect(scope_a.instance_variable_get(:@where_values)).to be == [['nato', true], ['king', nil], ['nato', true], ['king', nil]]
444
- expect(scope_b.instance_variable_get(:@where_values)).to be == [['king', nil], ['nato', true]]
462
+ expect(scope_a.instance_variable_get(:@where_values).map { |k, v| [k, v.value] }).to be == [['nato', true], ['king', nil], ['nato', true], ['king', nil]]
463
+ expect(scope_b.instance_variable_get(:@where_values).map { |k, v| [k, v.value] }).to be == [['king', nil], ['nato', true]]
445
464
  expect(scope_a).to be == scope_b
446
465
  end
447
466
  end
data/spec/spec_helper.rb CHANGED
@@ -1,16 +1,15 @@
1
1
  lib = File.expand_path('../lib', __FILE__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
- require 'pry'
5
- require 'simplecov'
6
- require 'coveralls'
7
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
8
- SimpleCov::Formatter::HTMLFormatter,
9
- Coveralls::SimpleCov::Formatter
10
- ]
11
- SimpleCov.start
12
-
13
- require 'frozen_record'
4
+ minimal = ENV['MINIMAL'] == 'true'
5
+ require 'objspace'
6
+
7
+ if minimal
8
+ require 'frozen_record/minimal'
9
+ else
10
+ require 'frozen_record'
11
+ end
12
+
14
13
  require 'frozen_record/test_helper'
15
14
 
16
15
  FrozenRecord::Base.base_path = File.join(File.dirname(__FILE__), 'fixtures')
@@ -22,6 +21,7 @@ FrozenRecord.eager_load!
22
21
  RSpec.configure do |config|
23
22
  config.run_all_when_everything_filtered = true
24
23
  config.filter_run :focus
24
+ config.filter_run_excluding :exclude_minimal if minimal
25
25
 
26
26
  config.order = 'random'
27
27
 
@@ -12,6 +12,10 @@ class Country < FrozenRecord::Base
12
12
  where(nato: true)
13
13
  end
14
14
 
15
+ def self.continent_and_capital(continent, capital:)
16
+ where(continent: continent, capital: capital)
17
+ end
18
+
15
19
  def reverse_name
16
20
  name.reverse
17
21
  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.19.5
4
+ version: 0.22.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: 2020-08-19 00:00:00.000000000 Z
11
+ date: 2021-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -25,13 +25,13 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: dedup
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
- type: :development
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
@@ -66,34 +66,6 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: pry
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: coveralls
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
69
  description:
98
70
  email:
99
71
  - jean.boussier@gmail.com
@@ -101,30 +73,32 @@ executables: []
101
73
  extensions: []
102
74
  extra_rdoc_files: []
103
75
  files:
76
+ - ".github/workflows/main.yml"
104
77
  - ".gitignore"
105
78
  - ".rspec"
106
- - ".travis.yml"
107
79
  - Gemfile
108
80
  - LICENSE.txt
109
81
  - README.md
110
82
  - Rakefile
111
83
  - benchmark/attribute-read
112
84
  - benchmark/memory-usage
85
+ - benchmark/querying
113
86
  - benchmark/setup.rb
114
87
  - frozen_record.gemspec
115
88
  - lib/frozen_record.rb
116
89
  - lib/frozen_record/backends.rb
90
+ - lib/frozen_record/backends/empty.json
117
91
  - lib/frozen_record/backends/json.rb
118
92
  - lib/frozen_record/backends/yaml.rb
119
93
  - lib/frozen_record/base.rb
120
94
  - lib/frozen_record/compact.rb
121
- - lib/frozen_record/deduplication.rb
122
95
  - lib/frozen_record/index.rb
96
+ - lib/frozen_record/minimal.rb
123
97
  - lib/frozen_record/railtie.rb
124
98
  - lib/frozen_record/scope.rb
99
+ - lib/frozen_record/serialization.rb
125
100
  - lib/frozen_record/test_helper.rb
126
101
  - lib/frozen_record/version.rb
127
- - spec/deduplication_spec.rb
128
102
  - spec/fixtures/animals.json
129
103
  - spec/fixtures/cars.yml
130
104
  - spec/fixtures/countries.yml.erb
@@ -156,12 +130,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
130
  - !ruby/object:Gem::Version
157
131
  version: '0'
158
132
  requirements: []
159
- rubygems_version: 3.1.2
133
+ rubygems_version: 3.1.4
160
134
  signing_key:
161
135
  specification_version: 4
162
136
  summary: ActiveRecord like interface to read only access and query static YAML files
163
137
  test_files:
164
- - spec/deduplication_spec.rb
165
138
  - spec/fixtures/animals.json
166
139
  - spec/fixtures/cars.yml
167
140
  - spec/fixtures/countries.yml.erb
data/.travis.yml DELETED
@@ -1,5 +0,0 @@
1
- sudo: false
2
- rvm:
3
- - '2.5'
4
- - '2.6'
5
- - '2.7'
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/core_ext/object/duplicable'
4
-
5
- module FrozenRecord
6
- module Deduplication
7
- extend self
8
-
9
- # We deduplicate data in place because it is assumed it directly
10
- # comes from the parser, and won't be held by anyone.
11
- #
12
- # Frozen Hashes and Arrays are ignored because they are likely
13
- # the result of the use of YAML anchor. Meaning we already deduplicated
14
- # them.
15
- if RUBY_VERSION >= '2.7'
16
- def deep_deduplicate!(data)
17
- case data
18
- when Hash
19
- return data if data.frozen?
20
- data.transform_keys! { |k| deep_deduplicate!(k) }
21
- data.transform_values! { |v| deep_deduplicate!(v) }
22
- data.freeze
23
- when Array
24
- return data if data.frozen?
25
- data.map! { |d| deep_deduplicate!(d) }.freeze
26
- when String
27
- -data.freeze
28
- else
29
- data.duplicable? ? data.freeze : data
30
- end
31
- end
32
- else
33
- def deep_deduplicate!(data)
34
- case data
35
- when Hash
36
- return data if data.frozen?
37
- data.transform_keys! { |k| deep_deduplicate!(k) }
38
- data.transform_values! { |v| deep_deduplicate!(v) }
39
- data.freeze
40
- when Array
41
- return data if data.frozen?
42
- data.map! { |d| deep_deduplicate!(d) }.freeze
43
- when String
44
- # String#-@ doesn't deduplicate the string if it's tainted.
45
- # So in such case we need to untaint it first.
46
- if data.tainted?
47
- -(+data).untaint.freeze
48
- else
49
- -data.freeze
50
- end
51
- else
52
- data.duplicable? ? data.freeze : data
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,31 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe 'deduplication' do
4
-
5
- it 'deduplicate string values' do
6
- pending("Strings can't be deduplicated before Ruby 2.5") if RUBY_VERSION < '2.5'
7
-
8
- records = [
9
- { 'name' => 'George'.dup },
10
- ]
11
-
12
- expect(records[0]['name']).to_not equal 'George'.freeze
13
- FrozenRecord::Deduplication.deep_deduplicate!(records)
14
- expect(records[0]['name']).to equal 'George'.freeze
15
- end
16
-
17
- it 'handles duplicated references' do
18
- # This simulates the YAML anchor behavior
19
- tags = { 'foo' => 'bar' }
20
- records = [
21
- { 'name' => 'George', 'tags' => tags },
22
- { 'name' => 'Peter', 'tags' => tags },
23
- ]
24
-
25
- expect(records[0]['tags']).to_not be_frozen
26
- FrozenRecord::Deduplication.deep_deduplicate!(records)
27
- expect(records[0]['tags']).to be_frozen
28
- expect(records[0]['tags']).to equal records[1]['tags']
29
- end
30
-
31
- end