frozen_record 0.19.5 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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