frozen_record 0.17.0 → 0.19.3

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: f2012cdafeeed41657b76dbe5b5489a47f5e38d863335d0e2db87609d2030974
4
- data.tar.gz: 636ea1f081a595230c27a2a4abb40978b977d1e3b541d23e039aa790d5bdb62d
3
+ metadata.gz: b9976300538ac81378c16689a47f03994e79b65a6cbbf755fa986528d4324533
4
+ data.tar.gz: 5e9b6c08770cd5186025041b0c6244eff2360c5b096a532802d953527abd3d1c
5
5
  SHA512:
6
- metadata.gz: ae134c3c39dca424590880b06190656c2d03aa0516fa387a5742ecf2dc1943e33f4c6eee30cc7fa600e63bd3c14186c69dc348ed20783256e563657bf9058874
7
- data.tar.gz: bad575675f9a764c2e84f67b9c550beee1506ef763907d32a87d5eaf967eedeb3521ec28d6d9e6dd4ebc790ce0400bf579fb1764af3d13e46f14e5a6343a1a44
6
+ metadata.gz: a00f10f827cd7e046d1f12433e913c39b9442dca95986cb395f81d28a685f3e7f4625814de1aeaf31654f4d2b8decab71938fe9999ccf5cb8c2fe3d3fbebe6ca
7
+ data.tar.gz: 070fa00130a5577cdb7441c14cb76d76f7290245f60b7b927081d169b4e86871f936716c6bfecb9859113df71791611f57546139ab80dcf1f4078a79e1e279a0
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  vendor
19
+ .byebug_history
@@ -1,6 +1,5 @@
1
1
  sudo: false
2
2
  rvm:
3
- - '2.4'
4
3
  - '2.5'
5
4
  - '2.6'
6
5
  - '2.7'
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'benchmark-ips'
4
+ gem 'byebug'
3
5
  gemspec
data/README.md CHANGED
@@ -60,7 +60,7 @@ A custom backend must implement the methods `filename` and `load` as follows:
60
60
 
61
61
  ```ruby
62
62
  module MyCustomBackend
63
- extend self
63
+ extend self
64
64
 
65
65
  def filename(model_name)
66
66
  # Returns the file name as a String
@@ -140,6 +140,22 @@ Country.european.republics.part_of_nato.order(id: :desc)
140
140
  - average
141
141
 
142
142
 
143
+ ## Indexing
144
+
145
+ Querying is implemented as a simple linear search (`O(n)`). However if you are using Frozen Record with larger datasets, or are querying
146
+ a collection repetedly, you can define indices for faster access.
147
+
148
+ ```ruby
149
+ class Country < FrozenRecord::Base
150
+ add_index :name, unique: true
151
+ add_index :continent
152
+ end
153
+ ```
154
+
155
+ Composite index keys are not supported.
156
+
157
+ The primary key isn't indexed by default.
158
+
143
159
  ## Configuration
144
160
 
145
161
  ### Reloading
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'setup'
5
+
6
+ regular = Country.first
7
+ compact = Compact::Country.first
8
+
9
+
10
+ puts "=== record.attribute ==="
11
+ Benchmark.ips do |x|
12
+ x.report('regular') { regular.name }
13
+ x.report('compact') { compact.name }
14
+ x.compare!
15
+ end
16
+
17
+ puts "=== record[:attribute] ==="
18
+ Benchmark.ips do |x|
19
+ x.report('regular') { regular[:name] }
20
+ x.report('compact') { compact[:name] }
21
+ x.compare!
22
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'setup'
5
+
6
+ puts "regular: #{Country.memsize} bytes"
7
+ puts "compact: #{Compact::Country.memsize} bytes"
8
+
9
+ diff = (Compact::Country.memsize - Country.memsize).to_f / Country.memsize
10
+ puts "diff: #{(diff * 100).round(2)}%"
@@ -0,0 +1,6 @@
1
+ require 'bundler/setup'
2
+ require 'benchmark/ips'
3
+
4
+ require 'frozen_record'
5
+ require_relative '../spec/support/country'
6
+ FrozenRecord::Base.base_path = File.expand_path('../spec/fixtures', __dir__)
@@ -17,6 +17,8 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ['lib']
19
19
 
20
+ spec.required_ruby_version = '>= 2.5'
21
+
20
22
  spec.add_runtime_dependency 'activemodel'
21
23
  spec.add_development_dependency 'bundler'
22
24
  spec.add_development_dependency 'rake'
@@ -7,7 +7,9 @@ require 'active_model'
7
7
 
8
8
  require 'frozen_record/version'
9
9
  require 'frozen_record/scope'
10
+ require 'frozen_record/index'
10
11
  require 'frozen_record/base'
12
+ require 'frozen_record/compact'
11
13
  require 'frozen_record/deduplication'
12
14
 
13
15
  module FrozenRecord
@@ -3,6 +3,7 @@
3
3
  require 'set'
4
4
  require 'active_support/descendants_tracker'
5
5
  require 'frozen_record/backends'
6
+ require 'objspace'
6
7
 
7
8
  module FrozenRecord
8
9
  class Base
@@ -20,6 +21,8 @@ module FrozenRecord
20
21
  FALSY_VALUES = [false, nil, 0, -''].to_set
21
22
 
22
23
  class_attribute :base_path, :primary_key, :backend, :auto_reloading, :default_attributes, instance_accessor: false
24
+ class_attribute :index_definitions, instance_accessor: false
25
+ self.index_definitions = {}.freeze
23
26
 
24
27
  self.primary_key = 'id'
25
28
 
@@ -102,6 +105,26 @@ module FrozenRecord
102
105
  end
103
106
  end
104
107
 
108
+ def add_index(attribute, unique: false)
109
+ index = unique ? UniqueIndex.new(self, attribute) : Index.new(self, attribute)
110
+ self.index_definitions = index_definitions.merge(index.attribute => index).freeze
111
+ end
112
+
113
+ def memsize(object = self, seen = Set.new.compare_by_identity)
114
+ return 0 unless seen.add?(object)
115
+
116
+ size = ObjectSpace.memsize_of(object)
117
+ object.instance_variables.each { |v| size += memsize(object.instance_variable_get(v), seen) }
118
+
119
+ case object
120
+ when Hash
121
+ object.each { |k, v| size += memsize(k, seen) + memsize(v, seen) }
122
+ when Array
123
+ object.each { |i| size += memsize(i, seen) }
124
+ end
125
+ size
126
+ end
127
+
105
128
  def respond_to_missing?(name, *)
106
129
  if name.to_s =~ FIND_BY_PATTERN
107
130
  load_records # ensure attribute methods are defined
@@ -127,15 +150,14 @@ module FrozenRecord
127
150
  records = Deduplication.deep_deduplicate!(records)
128
151
  @attributes = list_attributes(records).freeze
129
152
  define_attribute_methods(@attributes.to_a)
130
- records.map { |r| load(r) }.freeze
153
+ records = records.map { |r| load(r) }.freeze
154
+ index_definitions.values.each { |index| index.build(records) }
155
+ records
131
156
  end
132
157
  end
133
158
 
134
159
  def scope(name, body)
135
- unless body.respond_to?(:call)
136
- raise ArgumentError, "The scope body needs to be callable."
137
- end
138
- singleton_class.send(:define_method, name) { |*args| body.call(*args) }
160
+ singleton_class.send(:define_method, name, &body)
139
161
  end
140
162
 
141
163
  alias_method :load, :new
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FrozenRecord
4
+ module Compact
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def load_records(force: false)
9
+ if force || (auto_reloading && file_changed?)
10
+ @records = nil
11
+ undefine_attribute_methods
12
+ end
13
+
14
+ @records ||= begin
15
+ records = backend.load(file_path)
16
+ records.each { |r| assign_defaults!(r) }
17
+ records = Deduplication.deep_deduplicate!(records)
18
+ @attributes = list_attributes(records).freeze
19
+ build_attributes_cache
20
+ define_attribute_methods(@attributes.to_a)
21
+ index_definitions.values.each { |index| index.build(records) }
22
+ records.map { |r| load(r) }.freeze
23
+ end
24
+ end
25
+
26
+ if ActiveModel.gem_version >= Gem::Version.new('6.1.0.alpha')
27
+ def define_method_attribute(attr, owner:)
28
+ owner << "attr_reader #{attr.inspect}"
29
+ end
30
+ else
31
+ def define_method_attribute(attr)
32
+ generated_attribute_methods.attr_reader(attr)
33
+ end
34
+ end
35
+
36
+ attr_reader :_attributes_cache
37
+
38
+ private
39
+
40
+ def build_attributes_cache
41
+ @_attributes_cache = @attributes.each_with_object({}) do |attr, cache|
42
+ var = :"@#{attr}"
43
+ cache[attr.to_s] = var
44
+ cache[attr.to_sym] = var
45
+ end
46
+ end
47
+ end
48
+
49
+ def initialize(attrs = {})
50
+ self.attributes = attrs
51
+ end
52
+
53
+ def attributes
54
+ self.class.attributes.each_with_object({}) do |attr, hash|
55
+ hash[attr] = self[attr]
56
+ end
57
+ end
58
+
59
+ def [](attr)
60
+ if var = self.class._attributes_cache[attr]
61
+ instance_variable_get(var)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def attributes=(attributes)
68
+ self.class.attributes.each do |attr|
69
+ instance_variable_set(self.class._attributes_cache[attr], Deduplication.deep_deduplicate!(attributes[attr]))
70
+ end
71
+ end
72
+
73
+ def attribute?(attribute_name)
74
+ val = self[attribute_name]
75
+ Base::FALSY_VALUES.exclude?(val) && val.present?
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FrozenRecord
4
+ class Index
5
+ EMPTY_ARRAY = [].freeze
6
+ private_constant :EMPTY_ARRAY
7
+
8
+ AttributeNonUnique = Class.new(StandardError)
9
+
10
+ attr_reader :attribute, :model
11
+
12
+ def initialize(model, attribute, unique: false)
13
+ @model = model
14
+ @attribute = -attribute.to_s
15
+ @index = nil
16
+ end
17
+
18
+ def unique?
19
+ false
20
+ end
21
+
22
+ def query(value)
23
+ case value
24
+ when Array, Range
25
+ lookup_multi(value)
26
+ else
27
+ lookup(value)
28
+ end
29
+ end
30
+
31
+ def lookup_multi(values)
32
+ values.flat_map { |v| lookup(v) }
33
+ end
34
+
35
+ def lookup(value)
36
+ @index.fetch(value, EMPTY_ARRAY)
37
+ end
38
+
39
+ def reset
40
+ @index = nil
41
+ end
42
+
43
+ def build(records)
44
+ @index = records.each_with_object({}) do |record, index|
45
+ entry = (index[record[attribute]] ||= [])
46
+ entry << record
47
+ end
48
+ @index.values.each(&:freeze)
49
+ @index.freeze
50
+ end
51
+ end
52
+
53
+ class UniqueIndex < Index
54
+ def unique?
55
+ true
56
+ end
57
+
58
+ def lookup_multi(values)
59
+ results = @index.values_at(*values)
60
+ results.compact!
61
+ results
62
+ end
63
+
64
+ def lookup(value)
65
+ record = @index[value]
66
+ record ? [record] : EMPTY_ARRAY
67
+ end
68
+
69
+ def build(records)
70
+ @index = records.each_with_object({}) { |r, index| index[r[attribute]] = r }
71
+ if @index.size != records.size
72
+ raise AttributeNonUnique, "#{model}##{attribute.inspect} is not unique."
73
+ end
74
+ @index.freeze
75
+ end
76
+ end
77
+ end
@@ -175,9 +175,20 @@ module FrozenRecord
175
175
  def select_records(records)
176
176
  return records if @where_values.empty? && @where_not_values.empty?
177
177
 
178
+ indices = @klass.index_definitions
179
+ indexed_where_values, unindexed_where_values = @where_values.partition { |criteria| indices.key?(criteria.first) }
180
+
181
+ unless indexed_where_values.empty?
182
+ attribute, value = indexed_where_values.shift
183
+ records = indices[attribute].query(value)
184
+ indexed_where_values.each do |(attribute, value)|
185
+ records &= indices[attribute].query(value)
186
+ end
187
+ end
188
+
178
189
  records.select do |record|
179
- @where_values.all? { |attr, value| compare_value(record[attr], value) } &&
180
- @where_not_values.all? { |attr, value| !compare_value(record[attr], value) }
190
+ unindexed_where_values.all? { |attr, value| compare_value(record[attr], value) } &&
191
+ !@where_not_values.any? { |attr, value| compare_value(record[attr], value) }
181
192
  end
182
193
  end
183
194
 
@@ -226,12 +237,12 @@ module FrozenRecord
226
237
  end
227
238
 
228
239
  def where!(criterias)
229
- @where_values += criterias.to_a
240
+ @where_values += criterias.map { |k, v| [k.to_s, v] }
230
241
  self
231
242
  end
232
243
 
233
244
  def where_not!(criterias)
234
- @where_not_values += criterias.to_a
245
+ @where_not_values += criterias.map { |k, v| [k.to_s, v] }
235
246
  self
236
247
  end
237
248
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FrozenRecord
4
- VERSION = '0.17.0'
4
+ VERSION = '0.19.3'
5
5
  end
@@ -1,32 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe FrozenRecord::Base do
4
-
5
- describe '.base_path' do
6
-
7
- it 'raise a RuntimeError on first query attempt if not set' do
8
- allow(Country).to receive_message_chain(:base_path).and_return(nil)
9
- expect {
10
- Country.file_path
11
- }.to raise_error(ArgumentError)
12
- end
13
-
14
- end
3
+ RSpec.shared_examples 'main' do
15
4
 
16
5
  describe '.primary_key' do
17
6
 
18
7
  around do |example|
19
- previous_primary_key = Country.primary_key
8
+ previous_primary_key = country_model.primary_key
20
9
  begin
21
10
  example.run
22
11
  ensure
23
- Country.primary_key = previous_primary_key
12
+ country_model.primary_key = previous_primary_key
24
13
  end
25
14
  end
26
15
 
27
16
  it 'is coerced to string' do
28
- Country.primary_key = :foobar
29
- expect(Country.primary_key).to be == 'foobar'
17
+ country_model.primary_key = :foobar
18
+ expect(country_model.primary_key).to be == 'foobar'
30
19
  end
31
20
 
32
21
  end
@@ -36,24 +25,24 @@ describe FrozenRecord::Base do
36
25
  context 'when enabled' do
37
26
 
38
27
  around do |example|
39
- previous_auto_reloading = Country.auto_reloading
40
- Country.auto_reloading = true
28
+ previous_auto_reloading = country_model.auto_reloading
29
+ country_model.auto_reloading = true
41
30
  begin
42
31
  example.run
43
32
  ensure
44
- Country.auto_reloading = previous_auto_reloading
33
+ country_model.auto_reloading = previous_auto_reloading
45
34
  end
46
35
  end
47
36
 
48
37
  it 'reloads the records if the file mtime changed' do
49
- mtime = File.mtime(Country.file_path)
38
+ mtime = File.mtime(country_model.file_path)
50
39
  expect {
51
- File.utime(mtime + 1, mtime + 1, Country.file_path)
52
- }.to change { Country.first.object_id }
40
+ File.utime(mtime + 1, mtime + 1, country_model.file_path)
41
+ }.to change { country_model.first.object_id }
53
42
  end
54
43
 
55
44
  it 'does not reload if the file has not changed' do
56
- expect(Country.first.object_id).to be == Country.first.object_id
45
+ expect(country_model.first.object_id).to be == country_model.first.object_id
57
46
  end
58
47
 
59
48
  end
@@ -61,10 +50,10 @@ describe FrozenRecord::Base do
61
50
  context 'when disabled' do
62
51
 
63
52
  it 'does not reloads the records if the file mtime changed' do
64
- mtime = File.mtime(Country.file_path)
53
+ mtime = File.mtime(country_model.file_path)
65
54
  expect {
66
- File.utime(mtime + 1, mtime + 1, Country.file_path)
67
- }.to_not change { Country.first.object_id }
55
+ File.utime(mtime + 1, mtime + 1, country_model.file_path)
56
+ }.to_not change { country_model.first.object_id }
68
57
  end
69
58
 
70
59
  end
@@ -74,40 +63,52 @@ describe FrozenRecord::Base do
74
63
  describe '.default_attributes' do
75
64
 
76
65
  it 'define the attribute' do
77
- expect(Country.new).to respond_to :contemporary
66
+ expect(country_model.new).to respond_to :contemporary
78
67
  end
79
68
 
80
69
  it 'sets the value as default' do
81
- expect(Country.find_by(name: 'Austria').contemporary).to be == true
70
+ expect(country_model.find_by(name: 'Austria').contemporary).to be == true
82
71
  end
83
72
 
84
73
  it 'gives precedence to the data file' do
85
- expect(Country.find_by(name: 'Austria').available).to be == false
74
+ expect(country_model.find_by(name: 'Austria').available).to be == false
86
75
  end
87
76
 
88
77
  it 'is also set in the initializer' do
89
- expect(Country.new.contemporary).to be == true
78
+ expect(country_model.new.contemporary).to be == true
90
79
  end
80
+
91
81
  end
92
82
 
93
83
  describe '.scope' do
84
+
94
85
  it 'defines a scope method' do
86
+ country_model.scope :north_american, -> { where(continent: 'North America') }
87
+ expect(country_model).to respond_to(:north_american)
88
+ expect(country_model.north_american.first.name).to be == 'Canada'
89
+ end
95
90
 
96
- Country.scope :north_american, -> { Country.where(continent: 'North America') }
97
- expect(Country).to respond_to(:north_american)
98
- expect(Country.north_american.first.name).to be == 'Canada'
91
+ end
92
+
93
+ describe '.memsize' do
94
+
95
+ it 'retuns the records memory footprint' do
96
+ # Memory footprint is very dependent on the Ruby implementation and version
97
+ expect(country_model.memsize).to be > 0
98
+ expect(car_model.memsize).to be > 0
99
99
  end
100
+
100
101
  end
101
102
 
102
103
  describe '#load_records' do
103
104
 
104
105
  it 'processes erb by default' do
105
- country = Country.first
106
+ country = country_model.first
106
107
  expect(country.capital).to be == 'Ottawa'
107
108
  end
108
109
 
109
110
  it 'loads records with a custom backend' do
110
- animal = Animal.first
111
+ animal = animal_model.first
111
112
  expect(animal.name).to be == 'cat'
112
113
  end
113
114
 
@@ -116,22 +117,22 @@ describe FrozenRecord::Base do
116
117
  describe '#==' do
117
118
 
118
119
  it 'returns true if both instances are from the same class and have the same id' do
119
- country = Country.first
120
+ country = country_model.first
120
121
  second_country = country.dup
121
122
 
122
123
  expect(country).to be == second_country
123
124
  end
124
125
 
125
126
  it 'returns false if both instances are not from the same class' do
126
- country = Country.first
127
- car = Car.new(id: country.id)
127
+ country = country_model.first
128
+ car = car_model.new(id: country.id)
128
129
 
129
130
  expect(country).to_not be == car
130
131
  end
131
132
 
132
133
  it 'returns false if both instances do not have the same id' do
133
- country = Country.first
134
- second_country = Country.last
134
+ country = country_model.first
135
+ second_country = country_model.last
135
136
 
136
137
  expect(country).to_not be == second_country
137
138
  end
@@ -141,7 +142,7 @@ describe FrozenRecord::Base do
141
142
  describe '#attributes' do
142
143
 
143
144
  it 'returns a Hash of the record attributes' do
144
- attributes = Country.first.attributes
145
+ attributes = country_model.first.attributes
145
146
  expect(attributes).to be == {
146
147
  'id' => 1,
147
148
  'name' => 'Canada',
@@ -162,9 +163,9 @@ describe FrozenRecord::Base do
162
163
 
163
164
  describe '`attribute`?' do
164
165
 
165
- let(:blank) { Country.new(id: 0, name: '', nato: false, king: nil) }
166
+ let(:blank) { country_model.new(id: 0, name: '', nato: false, king: nil) }
166
167
 
167
- let(:present) { Country.new(id: 42, name: 'Groland', nato: true, king: Object.new) }
168
+ let(:present) { country_model.new(id: 42, name: 'Groland', nato: true, king: Object.new) }
168
169
 
169
170
  it 'considers `0` as missing' do
170
171
  expect(blank.id?).to be false
@@ -203,7 +204,7 @@ describe FrozenRecord::Base do
203
204
  describe '#present?' do
204
205
 
205
206
  it 'returns true' do
206
- expect(Country.first).to be_present
207
+ expect(country_model.first).to be_present
207
208
  end
208
209
 
209
210
  end
@@ -211,12 +212,39 @@ describe FrozenRecord::Base do
211
212
  describe '#count' do
212
213
 
213
214
  it 'can count objects with no records' do
214
- expect(Car.count).to be 0
215
+ expect(car_model.count).to be 0
215
216
  end
216
217
 
217
218
  it 'can count objects with records' do
218
- expect(Country.count).to be 3
219
+ expect(country_model.count).to be 3
219
220
  end
220
221
 
221
222
  end
222
223
  end
224
+
225
+ describe FrozenRecord::Base do
226
+ let(:country_model) { Country }
227
+ let(:car_model) { Car }
228
+ let(:animal_model) { Animal }
229
+
230
+ it_behaves_like 'main'
231
+
232
+ describe '.base_path' do
233
+
234
+ it 'raise a RuntimeError on first query attempt if not set' do
235
+ allow(country_model).to receive_message_chain(:base_path).and_return(nil)
236
+ expect {
237
+ country_model.file_path
238
+ }.to raise_error(ArgumentError)
239
+ end
240
+
241
+ end
242
+ end
243
+
244
+ describe FrozenRecord::Compact do
245
+ let(:country_model) { Compact::Country }
246
+ let(:car_model) { Compact::Car }
247
+ let(:animal_model) { Compact::Animal }
248
+
249
+ it_behaves_like 'main'
250
+ end
@@ -203,6 +203,18 @@ describe 'querying' do
203
203
  expect(countries.length).to be == 2
204
204
  end
205
205
 
206
+ it 'can combine indices' do
207
+ countries = Country.where(name: 'France', continent: 'Europe')
208
+ expect(countries.length).to be == 1
209
+ end
210
+
211
+ it 'can use indices with inclusion query' do
212
+ countries = Country.where(continent: ['Europe', 'North America'])
213
+ expect(countries.length).to be == 3
214
+
215
+ countries = Country.where(name: ['France', 'Canada'])
216
+ expect(countries.length).to be == 2
217
+ end
206
218
  end
207
219
 
208
220
  describe '.where.not' do
@@ -428,8 +440,8 @@ describe 'querying' do
428
440
  it 'returns true when the same scope has be rechained' do
429
441
  scope_a = Country.nato.republics.nato.republics
430
442
  scope_b = Country.republics.nato
431
- expect(scope_a.instance_variable_get(:@where_values)).to be == [[:nato, true ], [:king, nil ], [:nato, true], [:king, nil]]
432
- expect(scope_b.instance_variable_get(:@where_values)).to be == [[:king, nil ], [:nato, true]]
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]]
433
445
  expect(scope_a).to be == scope_b
434
446
  end
435
447
  end
@@ -1,3 +1,12 @@
1
1
  class Animal < FrozenRecord::Base
2
2
  self.backend = FrozenRecord::Backends::Json
3
3
  end
4
+
5
+ module Compact
6
+ class Animal < ::Animal
7
+ include FrozenRecord::Compact
8
+ def self.file_path
9
+ superclass.file_path
10
+ end
11
+ end
12
+ end
@@ -1,2 +1,12 @@
1
1
  class Car < FrozenRecord::Base
2
2
  end
3
+
4
+ module Compact
5
+ class Car < ::Car
6
+ include FrozenRecord::Compact
7
+
8
+ def self.file_path
9
+ superclass.file_path
10
+ end
11
+ end
12
+ end
@@ -1,6 +1,9 @@
1
1
  class Country < FrozenRecord::Base
2
2
  self.default_attributes = { contemporary: true, available: true }
3
3
 
4
+ add_index :name, unique: true
5
+ add_index :continent
6
+
4
7
  def self.republics
5
8
  where(king: nil)
6
9
  end
@@ -13,3 +16,12 @@ class Country < FrozenRecord::Base
13
16
  name.reverse
14
17
  end
15
18
  end
19
+
20
+ module Compact
21
+ class Country < ::Country
22
+ include FrozenRecord::Compact
23
+ def self.file_path
24
+ superclass.file_path
25
+ end
26
+ end
27
+ 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.17.0
4
+ version: 0.19.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-16 00:00:00.000000000 Z
11
+ date: 2020-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -94,7 +94,7 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
- description:
97
+ description:
98
98
  email:
99
99
  - jean.boussier@gmail.com
100
100
  executables: []
@@ -108,13 +108,18 @@ files:
108
108
  - LICENSE.txt
109
109
  - README.md
110
110
  - Rakefile
111
+ - benchmark/attribute-read
112
+ - benchmark/memory-usage
113
+ - benchmark/setup.rb
111
114
  - frozen_record.gemspec
112
115
  - lib/frozen_record.rb
113
116
  - lib/frozen_record/backends.rb
114
117
  - lib/frozen_record/backends/json.rb
115
118
  - lib/frozen_record/backends/yaml.rb
116
119
  - lib/frozen_record/base.rb
120
+ - lib/frozen_record/compact.rb
117
121
  - lib/frozen_record/deduplication.rb
122
+ - lib/frozen_record/index.rb
118
123
  - lib/frozen_record/railtie.rb
119
124
  - lib/frozen_record/scope.rb
120
125
  - lib/frozen_record/test_helper.rb
@@ -136,7 +141,7 @@ homepage: https://github.com/byroot/frozen_record
136
141
  licenses:
137
142
  - MIT
138
143
  metadata: {}
139
- post_install_message:
144
+ post_install_message:
140
145
  rdoc_options: []
141
146
  require_paths:
142
147
  - lib
@@ -144,15 +149,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
149
  requirements:
145
150
  - - ">="
146
151
  - !ruby/object:Gem::Version
147
- version: '0'
152
+ version: '2.5'
148
153
  required_rubygems_version: !ruby/object:Gem::Requirement
149
154
  requirements:
150
155
  - - ">="
151
156
  - !ruby/object:Gem::Version
152
157
  version: '0'
153
158
  requirements: []
154
- rubygems_version: 3.0.4
155
- signing_key:
159
+ rubygems_version: 3.1.2
160
+ signing_key:
156
161
  specification_version: 4
157
162
  summary: ActiveRecord like interface to read only access and query static YAML files
158
163
  test_files: