frozen_record 0.19.4 → 0.21.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: 2fe4a43299f20ff72c35ee4093ef5f6c8d49214aabc82cf67108da083980b28e
4
- data.tar.gz: 7023b4b75d3eb733ef534ae85f1b25e0efb7aac3ba8548dd9e1a25bdbe1ec804
3
+ metadata.gz: 0a25a269067396d2e6054a57c514b1251e39dca90928e7bdd837bfbb9ef8d2cf
4
+ data.tar.gz: 77b55ee24e39590dd213c157b22e3707c8f551a051df34f516a1e9d061d4eb6a
5
5
  SHA512:
6
- metadata.gz: 4031cf6cd49bec9dc97c246a0999736ed178d9f6b1e623ef6e60070da454e1141fb7ca25056d2d55f2f808d2812f0f1c14e856cecaf73b0dd403a2a047debf62
7
- data.tar.gz: 823f999ec6e21c55b4b0cad6b39008c55377052d194a29af96edb0e85cc9a33227635246a9187fdc7207eac9ec515c352f54ac8c568a7ec7bd3589ef74dea0dd
6
+ metadata.gz: a1d23916d54278598ec3ba29e5c1000f4adb0f13566db9be7900e5344199e36fe02254539a531a26d8b2d211e62f3916438c98032e7166795fbbf1681a170b91
7
+ data.tar.gz: af66cb72d621f8d6008de238d7f0846bf576200437fbdead4cfcdd1d598af62ac4d29bd801efd14420bfddb4470301312eb992628e2c7185e21b8dbee0381f6e
@@ -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
@@ -20,7 +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
27
  spec.add_development_dependency 'pry'
@@ -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)
@@ -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
@@ -34,15 +34,41 @@ module FrozenRecord
34
34
  end
35
35
  end
36
36
 
37
- YAML.load(yml_data) || []
37
+ load_string(yml_data)
38
38
  else
39
39
  if file_path.end_with?('.erb')
40
- YAML.load(ERB.new(File.read(file_path)).result) || []
40
+ load_string(ERB.new(File.read(file_path)).result)
41
41
  else
42
- YAML.load_file(file_path) || []
42
+ load_file(file_path)
43
43
  end
44
44
  end
45
45
  end
46
+
47
+ private
48
+
49
+ supports_freeze = begin
50
+ YAML.load_file(File.expand_path('../empty.json', __FILE__), freeze: true)
51
+ rescue ArgumentError
52
+ false
53
+ end
54
+
55
+ if supports_freeze
56
+ def load_file(path)
57
+ YAML.load_file(path, freeze: true) || Dedup::EMPTY_ARRAY
58
+ end
59
+
60
+ def load_string(yaml)
61
+ YAML.load(yaml, freeze: true) || Dedup::EMPTY_ARRAY
62
+ end
63
+ else
64
+ def load_file(path)
65
+ Dedup.deep_intern!(YAML.load_file(path) || Dedup::EMPTY_ARRAY)
66
+ end
67
+
68
+ def load_string(yaml)
69
+ Dedup.deep_intern!(YAML.load(yaml) || Dedup::EMPTY_ARRAY)
70
+ end
71
+ end
46
72
  end
47
73
  end
48
74
  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=
@@ -146,8 +139,9 @@ module FrozenRecord
146
139
 
147
140
  @records ||= begin
148
141
  records = backend.load(file_path)
149
- records.each { |r| assign_defaults!(r) }
150
- records = Deduplication.deep_deduplicate!(records)
142
+ if default_attributes
143
+ records = records.map { |r| assign_defaults!(r.dup).freeze }.freeze
144
+ end
151
145
  @attributes = list_attributes(records).freeze
152
146
  define_attribute_methods(@attributes.to_a)
153
147
  records = records.map { |r| load(r) }.freeze
@@ -164,7 +158,7 @@ module FrozenRecord
164
158
  private :load
165
159
 
166
160
  def new(attrs = {})
167
- load(assign_defaults!(attrs.stringify_keys))
161
+ load(assign_defaults!(attrs.transform_keys(&:to_s)))
168
162
  end
169
163
 
170
164
  private
@@ -245,7 +239,7 @@ module FrozenRecord
245
239
  private
246
240
 
247
241
  def attribute?(attribute_name)
248
- FALSY_VALUES.exclude?(self[attribute_name]) && self[attribute_name].present?
242
+ !FALSY_VALUES.include?(self[attribute_name]) && self[attribute_name].present?
249
243
  end
250
244
 
251
245
  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
@@ -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,7 +2,7 @@
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
@@ -227,13 +227,14 @@ module FrozenRecord
227
227
  super
228
228
  end
229
229
  end
230
+ ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
230
231
 
231
232
  def delegate_to_class(*args, &block)
232
233
  scoping { @klass.public_send(*args, &block) }
233
234
  end
234
235
 
235
236
  def array_delegable?(method)
236
- Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
237
+ Array.method_defined?(method) && !DISALLOWED_ARRAY_METHODS.include?(method)
237
238
  end
238
239
 
239
240
  def where!(criterias)
@@ -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.4'
4
+ VERSION = '0.21.1'
5
5
  end
@@ -193,6 +193,11 @@ describe 'querying' do
193
193
  expect(countries.length).to be == 0
194
194
  end
195
195
 
196
+ it 'is chainable with methods of the form `def method(*args, **kargs)' do
197
+ countries = Country.republics.continent_and_capital('Europe', capital: 'Paris')
198
+ expect(countries.length).to be == 1
199
+ end
200
+
196
201
  it 'can be used with arrays' do
197
202
  countries = Country.where(id: [1,2])
198
203
  expect(countries.length).to be == 2
@@ -410,7 +415,7 @@ describe 'querying' do
410
415
 
411
416
  end
412
417
 
413
- describe '.as_json' do
418
+ describe '.as_json', exclude_minimal: true do
414
419
 
415
420
  it 'serialize the results' do
416
421
  json = Country.all.as_json
@@ -1,6 +1,8 @@
1
1
  lib = File.expand_path('../lib', __FILE__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
+ minimal = ENV['MINIMAL'] == 'true'
5
+
4
6
  require 'pry'
5
7
  require 'simplecov'
6
8
  require 'coveralls'
@@ -10,7 +12,14 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
10
12
  ]
11
13
  SimpleCov.start
12
14
 
13
- require 'frozen_record'
15
+ require 'objspace'
16
+
17
+ if minimal
18
+ require 'frozen_record/minimal'
19
+ else
20
+ require 'frozen_record'
21
+ end
22
+
14
23
  require 'frozen_record/test_helper'
15
24
 
16
25
  FrozenRecord::Base.base_path = File.join(File.dirname(__FILE__), 'fixtures')
@@ -22,6 +31,7 @@ FrozenRecord.eager_load!
22
31
  RSpec.configure do |config|
23
32
  config.run_all_when_everything_filtered = true
24
33
  config.filter_run :focus
34
+ config.filter_run_excluding :exclude_minimal if minimal
25
35
 
26
36
  config.order = 'random'
27
37
 
@@ -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.4
4
+ version: 0.21.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: 2020-08-18 00:00:00.000000000 Z
11
+ date: 2021-01-27 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:
@@ -101,9 +101,9 @@ executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
+ - ".github/workflows/main.yml"
104
105
  - ".gitignore"
105
106
  - ".rspec"
106
- - ".travis.yml"
107
107
  - Gemfile
108
108
  - LICENSE.txt
109
109
  - README.md
@@ -114,17 +114,18 @@ files:
114
114
  - frozen_record.gemspec
115
115
  - lib/frozen_record.rb
116
116
  - lib/frozen_record/backends.rb
117
+ - lib/frozen_record/backends/empty.json
117
118
  - lib/frozen_record/backends/json.rb
118
119
  - lib/frozen_record/backends/yaml.rb
119
120
  - lib/frozen_record/base.rb
120
121
  - lib/frozen_record/compact.rb
121
- - lib/frozen_record/deduplication.rb
122
122
  - lib/frozen_record/index.rb
123
+ - lib/frozen_record/minimal.rb
123
124
  - lib/frozen_record/railtie.rb
124
125
  - lib/frozen_record/scope.rb
126
+ - lib/frozen_record/serialization.rb
125
127
  - lib/frozen_record/test_helper.rb
126
128
  - lib/frozen_record/version.rb
127
- - spec/deduplication_spec.rb
128
129
  - spec/fixtures/animals.json
129
130
  - spec/fixtures/cars.yml
130
131
  - spec/fixtures/countries.yml.erb
@@ -156,12 +157,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
157
  - !ruby/object:Gem::Version
157
158
  version: '0'
158
159
  requirements: []
159
- rubygems_version: 3.1.2
160
+ rubygems_version: 3.1.4
160
161
  signing_key:
161
162
  specification_version: 4
162
163
  summary: ActiveRecord like interface to read only access and query static YAML files
163
164
  test_files:
164
- - spec/deduplication_spec.rb
165
165
  - spec/fixtures/animals.json
166
166
  - spec/fixtures/cars.yml
167
167
  - spec/fixtures/countries.yml.erb
@@ -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
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
48
- else
49
- -data
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