frozen_record 0.19.4 → 0.21.1

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: 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