frozen_record 0.18.0 → 0.19.0
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 +4 -4
- data/.gitignore +1 -0
- data/README.md +16 -0
- data/lib/frozen_record.rb +1 -0
- data/lib/frozen_record/base.rb +10 -5
- data/lib/frozen_record/index.rb +58 -0
- data/lib/frozen_record/scope.rb +15 -4
- data/lib/frozen_record/version.rb +1 -1
- data/spec/frozen_record_spec.rb +1 -1
- data/spec/scope_spec.rb +6 -2
- data/spec/support/country.rb +3 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1006ddbdc9f21010a819e6085800b05fc549c9b513c75431b329f20d46e055a
|
4
|
+
data.tar.gz: b3f65c04430375bf7fe2be7ecd233515e9a8b4a1dc7ebc29a430fa4934fad8be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db63b827feb659e1228812947551db15ef777b33c71b38d3d9c77aa304d148b2e23eaaa6075e3dda90bd759abd9774635030bd01947b6d7b61fa01a53114601c
|
7
|
+
data.tar.gz: a11f92b0a5ff35033c348b69735821969f33a93fdca861f58b3dd4c5a58263da643ce6efe6c9f7f473e754615e91c94712dce7901f7d49b2893bd3a808b9bc7b
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -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
|
data/lib/frozen_record.rb
CHANGED
data/lib/frozen_record/base.rb
CHANGED
@@ -21,6 +21,7 @@ module FrozenRecord
|
|
21
21
|
FALSY_VALUES = [false, nil, 0, -''].to_set
|
22
22
|
|
23
23
|
class_attribute :base_path, :primary_key, :backend, :auto_reloading, :default_attributes, instance_accessor: false
|
24
|
+
class_attribute :index_definitions, instance_accessor: false, default: {}.freeze
|
24
25
|
|
25
26
|
self.primary_key = 'id'
|
26
27
|
|
@@ -103,6 +104,11 @@ module FrozenRecord
|
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
107
|
+
def add_index(attribute, unique: false)
|
108
|
+
index = unique ? UniqueIndex.new(self, attribute) : Index.new(self, attribute)
|
109
|
+
self.index_definitions = index_definitions.merge(index.attribute => index).freeze
|
110
|
+
end
|
111
|
+
|
106
112
|
def memsize(object = self, seen = Set.new.compare_by_identity)
|
107
113
|
return 0 unless seen.add?(object)
|
108
114
|
|
@@ -143,15 +149,14 @@ module FrozenRecord
|
|
143
149
|
records = Deduplication.deep_deduplicate!(records)
|
144
150
|
@attributes = list_attributes(records).freeze
|
145
151
|
define_attribute_methods(@attributes.to_a)
|
146
|
-
records.map { |r| load(r) }.freeze
|
152
|
+
records = records.map { |r| load(r) }.freeze
|
153
|
+
index_definitions.values.each { |index| index.build(records) }
|
154
|
+
records
|
147
155
|
end
|
148
156
|
end
|
149
157
|
|
150
158
|
def scope(name, body)
|
151
|
-
|
152
|
-
raise ArgumentError, "The scope body needs to be callable."
|
153
|
-
end
|
154
|
-
singleton_class.send(:define_method, name) { |*args| body.call(*args) }
|
159
|
+
singleton_class.send(:define_method, name, &body)
|
155
160
|
end
|
156
161
|
|
157
162
|
alias_method :load, :new
|
@@ -0,0 +1,58 @@
|
|
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
|
+
@index.fetch(value, EMPTY_ARRAY)
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset
|
27
|
+
@index = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def build(records)
|
31
|
+
@index = records.each_with_object({}) do |record, index|
|
32
|
+
entry = (index[record[attribute]] ||= [])
|
33
|
+
entry << record
|
34
|
+
end
|
35
|
+
@index.values.each(&:freeze)
|
36
|
+
@index.freeze
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class UniqueIndex < Index
|
41
|
+
def unique?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def query(value)
|
46
|
+
record = @index[value]
|
47
|
+
record ? [record] : EMPTY_ARRAY
|
48
|
+
end
|
49
|
+
|
50
|
+
def build(records)
|
51
|
+
@index = records.to_h { |r| [r[attribute], r] }
|
52
|
+
if @index.size != records.size
|
53
|
+
raise AttributeNonUnique, "#{model}##{attribute.inspect} is not unique."
|
54
|
+
end
|
55
|
+
@index.freeze
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/frozen_record/scope.rb
CHANGED
@@ -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
|
-
|
180
|
-
|
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.
|
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.
|
245
|
+
@where_not_values += criterias.map { |k, v| [k.to_s, v] }
|
235
246
|
self
|
236
247
|
end
|
237
248
|
|
data/spec/frozen_record_spec.rb
CHANGED
@@ -83,7 +83,7 @@ RSpec.shared_examples 'main' do
|
|
83
83
|
describe '.scope' do
|
84
84
|
|
85
85
|
it 'defines a scope method' do
|
86
|
-
country_model.scope :north_american, -> {
|
86
|
+
country_model.scope :north_american, -> { where(continent: 'North America') }
|
87
87
|
expect(country_model).to respond_to(:north_american)
|
88
88
|
expect(country_model.north_american.first.name).to be == 'Canada'
|
89
89
|
end
|
data/spec/scope_spec.rb
CHANGED
@@ -203,6 +203,10 @@ 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
|
206
210
|
end
|
207
211
|
|
208
212
|
describe '.where.not' do
|
@@ -428,8 +432,8 @@ describe 'querying' do
|
|
428
432
|
it 'returns true when the same scope has be rechained' do
|
429
433
|
scope_a = Country.nato.republics.nato.republics
|
430
434
|
scope_b = Country.republics.nato
|
431
|
-
expect(scope_a.instance_variable_get(:@where_values)).to be == [[
|
432
|
-
expect(scope_b.instance_variable_get(:@where_values)).to be == [[
|
435
|
+
expect(scope_a.instance_variable_get(:@where_values)).to be == [['nato', true], ['king', nil], ['nato', true], ['king', nil]]
|
436
|
+
expect(scope_b.instance_variable_get(:@where_values)).to be == [['king', nil], ['nato', true]]
|
433
437
|
expect(scope_a).to be == scope_b
|
434
438
|
end
|
435
439
|
end
|
data/spec/support/country.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: frozen_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.19.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- lib/frozen_record/base.rb
|
120
120
|
- lib/frozen_record/compact.rb
|
121
121
|
- lib/frozen_record/deduplication.rb
|
122
|
+
- lib/frozen_record/index.rb
|
122
123
|
- lib/frozen_record/railtie.rb
|
123
124
|
- lib/frozen_record/scope.rb
|
124
125
|
- lib/frozen_record/test_helper.rb
|