frozen_record 0.17.0 → 0.19.3

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