frozen_record 0.19.3 → 0.21.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: b9976300538ac81378c16689a47f03994e79b65a6cbbf755fa986528d4324533
4
- data.tar.gz: 5e9b6c08770cd5186025041b0c6244eff2360c5b096a532802d953527abd3d1c
3
+ metadata.gz: b998592ed0453f220a0679b3ee2aea49d1d5381a3a7c4d6e9286434e6e3b6c27
4
+ data.tar.gz: 3f430857f5b5626951360d2c28402c58b5751cb1ef2805f139558d600b7e1e1c
5
5
  SHA512:
6
- metadata.gz: a00f10f827cd7e046d1f12433e913c39b9442dca95986cb395f81d28a685f3e7f4625814de1aeaf31654f4d2b8decab71938fe9999ccf5cb8c2fe3d3fbebe6ca
7
- data.tar.gz: 070fa00130a5577cdb7441c14cb76d76f7290245f60b7b927081d169b4e86871f936716c6bfecb9859113df71791611f57546139ab80dcf1f4078a79e1e279a0
6
+ metadata.gz: cbc8307030df2b393d69499c7be236ef62349ad6f74b7fc9de914260748fd0919e293204b30ba31e377acf157acb90e9371d4ed50a77da955f60222694f74892
7
+ data.tar.gz: f211b9298137e71e098e841658341f0cceb3dd902f7974a5a9a7c2233173bd1016037832ba744f8966beb9045fa9c93068365ad5d9b7d64f9b68631e1b840ca1
@@ -0,0 +1,29 @@
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
+ name: Ruby ${{ matrix.ruby }} tests
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Setup Ruby
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ - uses: actions/cache@v2
19
+ with:
20
+ path: vendor/bundle
21
+ key: ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles('Gemfile', 'frozen_record.gemspec') }}
22
+ restore-keys: |
23
+ ${{ runner.os }}-${{ matrix.ruby }}-gems-
24
+ - name: Bundle install
25
+ run: |
26
+ gem install bundler
27
+ bundle install --jobs 4 --retry 3 --path=vendor/bundle
28
+ - name: Run tests
29
+ 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
@@ -13,13 +13,15 @@ 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)
22
+ records = records.map { |r| load(r) }.freeze
21
23
  index_definitions.values.each { |index| index.build(records) }
22
- records.map { |r| load(r) }.freeze
24
+ records
23
25
  end
24
26
  end
25
27
 
@@ -66,7 +68,7 @@ module FrozenRecord
66
68
 
67
69
  def attributes=(attributes)
68
70
  self.class.attributes.each do |attr|
69
- 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]))
70
72
  end
71
73
  end
72
74
 
@@ -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.exclude?(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.3'
4
+ VERSION = '0.21.0'
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
@@ -10,6 +10,7 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
10
10
  ]
11
11
  SimpleCov.start
12
12
 
13
+ require 'objspace'
13
14
  require 'frozen_record'
14
15
  require 'frozen_record/test_helper'
15
16
 
@@ -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.3
4
+ version: 0.21.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-18 00:00:00.000000000 Z
11
+ date: 2021-01-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:
@@ -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