occams-record 1.5.0 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -13
- data/lib/occams-record/eager_loaders/ad_hoc_base.rb +14 -3
- data/lib/occams-record/eager_loaders/base.rb +14 -3
- data/lib/occams-record/eager_loaders/builder.rb +6 -0
- data/lib/occams-record/eager_loaders/context.rb +7 -2
- data/lib/occams-record/eager_loaders/eager_loaders.rb +1 -0
- data/lib/occams-record/eager_loaders/polymorphic_belongs_to.rb +14 -3
- data/lib/occams-record/eager_loaders/through.rb +2 -2
- data/lib/occams-record/eager_loaders/tracer.rb +15 -0
- data/lib/occams-record/errors.rb +6 -2
- data/lib/occams-record/query.rb +2 -2
- data/lib/occams-record/raw_query.rb +1 -1
- data/lib/occams-record/results/results.rb +3 -1
- data/lib/occams-record/results/row.rb +2 -0
- data/lib/occams-record/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc34df60947ca8d92f1fa00ab74271fdec70439b07eccc2d5f3a2fa19a67efbe
|
4
|
+
data.tar.gz: 7a77182c754af5c0cfd6689a86745af8edc260fb721076a7c317fab3cebb6c1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9aac2db52de95e034e05c1fa2f078ea744cbecf715c92add927b2e227ec0ca20734575bec25f51da28bcab511febc2fd02a7efdb7def6a2a12165aebfa5eb8e
|
7
|
+
data.tar.gz: 89bff53f5b3e975e03074b4bfb7088f25441ecf170acbe3f676376eae8fcaa8b943274d5731d65d12fe2757d1327b27b8730adc5e101025cd8c72a563ef03d22
|
data/README.md
CHANGED
@@ -20,8 +20,8 @@ Continue using ActiveRecord's query builder, but let Occams take over running th
|
|
20
20
|
```ruby
|
21
21
|
OccamsRecord
|
22
22
|
.query(User.active)
|
23
|
-
.eager_load(:orders) {
|
24
|
-
scope { |q| q.where("created_at >= ?", date).order("created_at DESC") }
|
23
|
+
.eager_load(:orders) { |l|
|
24
|
+
l.scope { |q| q.where("created_at >= ?", date).order("created_at DESC") }
|
25
25
|
}
|
26
26
|
```
|
27
27
|
|
@@ -137,16 +137,16 @@ Eager loading is similiar to ActiveRecord's `preload`: each association is loade
|
|
137
137
|
OccamsRecord
|
138
138
|
.query(q)
|
139
139
|
.eager_load(:customer)
|
140
|
-
.eager_load(:line_items) {
|
141
|
-
eager_load(:product)
|
142
|
-
eager_load(:something_else)
|
140
|
+
.eager_load(:line_items) { |l|
|
141
|
+
l.eager_load(:product)
|
142
|
+
l.eager_load(:something_else)
|
143
143
|
}
|
144
144
|
.find_each { |order|
|
145
145
|
puts order.customer.name
|
146
146
|
order.line_items.each { |line_item|
|
147
147
|
puts line_item.product.name
|
148
148
|
puts line_item.product.category.name
|
149
|
-
OccamsRecord::MissingEagerLoadError: Association 'category' is unavailable on Product because it was not eager loaded!
|
149
|
+
OccamsRecord::MissingEagerLoadError: Association 'category' is unavailable on Product because it was not eager loaded! Found at root.line_items.product
|
150
150
|
}
|
151
151
|
}
|
152
152
|
```
|
@@ -163,11 +163,11 @@ orders = OccamsRecord
|
|
163
163
|
|
164
164
|
# Or use 'scope' to access the full power of ActiveRecord's query builder.
|
165
165
|
# Here, only 'active' line items will be returned, and in a specific order.
|
166
|
-
.eager_load(:line_items) {
|
167
|
-
scope { |q| q.active.order("created_at") }
|
166
|
+
.eager_load(:line_items) { |l|
|
167
|
+
l.scope { |q| q.active.order("created_at") }
|
168
168
|
|
169
|
-
eager_load(:product)
|
170
|
-
eager_load(:something_else)
|
169
|
+
l.eager_load(:product)
|
170
|
+
l.eager_load(:something_else)
|
171
171
|
}
|
172
172
|
.run
|
173
173
|
```
|
@@ -267,9 +267,9 @@ Let's say we want to load each product with an array of all customers who've ord
|
|
267
267
|
```ruby
|
268
268
|
products_with_orders = OccamsRecord
|
269
269
|
.query(Product.all)
|
270
|
-
.eager_load(:line_items) {
|
271
|
-
eager_load(:order) {
|
272
|
-
eager_load(:customer)
|
270
|
+
.eager_load(:line_items) { |l|
|
271
|
+
l.eager_load(:order) { |l|
|
272
|
+
l.eager_load(:customer)
|
273
273
|
}
|
274
274
|
}
|
275
275
|
.map { |product|
|
@@ -11,6 +11,9 @@ module OccamsRecord
|
|
11
11
|
# @return [String] association name
|
12
12
|
attr_reader :name
|
13
13
|
|
14
|
+
# @return [OccamsRecord::EagerLoaders::Tracer | nil] a reference to this eager loader and its parent (if any)
|
15
|
+
attr_reader :tracer
|
16
|
+
|
14
17
|
#
|
15
18
|
# Initialize a new add hoc association.
|
16
19
|
#
|
@@ -20,13 +23,21 @@ module OccamsRecord
|
|
20
23
|
# @param binds [Hash] any additional binds for your query.
|
21
24
|
# @param model [ActiveRecord::Base] optional - ActiveRecord model that represents what you're loading. required when using Sqlite.
|
22
25
|
# @param use [Array<Module>] optional - Ruby modules to include in the result objects (single or array)
|
26
|
+
# @param parent [OccamsRecord::EagerLoaders::Tracer] the eager loader this one is nested under (if any)
|
23
27
|
# @yield eager load associations nested under this one
|
24
28
|
#
|
25
|
-
def initialize(name, mapping, sql, binds: {}, model: nil, use: nil, &builder)
|
29
|
+
def initialize(name, mapping, sql, binds: {}, model: nil, use: nil, parent: nil, &builder)
|
26
30
|
@name, @mapping = name.to_s, mapping
|
27
31
|
@sql, @binds, @use, @model = sql, binds, use, model
|
28
|
-
@
|
29
|
-
|
32
|
+
@tracer = Tracer.new(name, parent)
|
33
|
+
@eager_loaders = EagerLoaders::Context.new(@model, tracer: @tracer)
|
34
|
+
if builder
|
35
|
+
if builder.arity > 0
|
36
|
+
builder.call(self)
|
37
|
+
else
|
38
|
+
instance_exec(&builder)
|
39
|
+
end
|
40
|
+
end
|
30
41
|
end
|
31
42
|
|
32
43
|
#
|
@@ -9,6 +9,9 @@ module OccamsRecord
|
|
9
9
|
# @return [String] association name
|
10
10
|
attr_reader :name
|
11
11
|
|
12
|
+
# @return [OccamsRecord::EagerLoaders::Tracer | nil] a reference to this eager loader and its parent (if any)
|
13
|
+
attr_reader :tracer
|
14
|
+
|
12
15
|
#
|
13
16
|
# @param ref [ActiveRecord::Association] the ActiveRecord association
|
14
17
|
# @param scope [Proc] a scope to apply to the query (optional). It will be passed an
|
@@ -16,15 +19,23 @@ module OccamsRecord
|
|
16
19
|
# @param use [Array(Module)] optional Module to include in the result class (single or array)
|
17
20
|
# @param as [Symbol] Load the association usign a different attribute name
|
18
21
|
# @param optimizer [Symbol] Only used for `through` associations. Options are :none (load all intermediate records) | :select (load all intermediate records but only SELECT the necessary columns)
|
22
|
+
# @param parent [OccamsRecord::EagerLoaders::Tracer] the eager loader this one is nested under (if any)
|
19
23
|
# @yield perform eager loading on *this* association (optional)
|
20
24
|
#
|
21
|
-
def initialize(ref, scope = nil, use: nil, as: nil, optimizer: :select, &builder)
|
25
|
+
def initialize(ref, scope = nil, use: nil, as: nil, optimizer: :select, parent: nil, &builder)
|
22
26
|
@ref, @scopes, @use, @as = ref, Array(scope), use, as
|
23
27
|
@model = ref.klass
|
24
28
|
@name = (as || ref.name).to_s
|
25
|
-
@
|
29
|
+
@tracer = Tracer.new(name, parent)
|
30
|
+
@eager_loaders = EagerLoaders::Context.new(@model, tracer: @tracer)
|
26
31
|
@optimizer = optimizer
|
27
|
-
|
32
|
+
if builder
|
33
|
+
if builder.arity > 0
|
34
|
+
builder.call(self)
|
35
|
+
else
|
36
|
+
instance_exec(&builder)
|
37
|
+
end
|
38
|
+
end
|
28
39
|
end
|
29
40
|
|
30
41
|
#
|
@@ -10,6 +10,12 @@ module OccamsRecord
|
|
10
10
|
# Specify an association to be eager-loaded. For maximum memory savings, only SELECT the
|
11
11
|
# colums you actually need.
|
12
12
|
#
|
13
|
+
# If you pass a block to nest more eager loads, you may call it with one of two forms: with an argument and without:
|
14
|
+
#
|
15
|
+
# If you ommit the block argument, the "self" inside the block will be the eager loader. You can call "eager_load" and "scope" directly.
|
16
|
+
#
|
17
|
+
# If you include the block argument, the "self" inside the block is the same as the self outside the block. The argument will be the eager loader, which you can use to make additional "eager_load" or "scope" calls.
|
18
|
+
#
|
13
19
|
# @param assoc [Symbol] name of association
|
14
20
|
# @param scope [Proc] a scope to apply to the query (optional). It will be passed an
|
15
21
|
# ActiveRecord::Relation on which you may call all the normal query hethods (select, where, etc) as well as any scopes you've defined on the model.
|
@@ -14,14 +14,19 @@ module OccamsRecord
|
|
14
14
|
# @return [ActiveRecord::Base]
|
15
15
|
attr_reader :model
|
16
16
|
|
17
|
+
# @return [OccamsRecord::EagerLoaders::Tracer]
|
18
|
+
attr_reader :tracer
|
19
|
+
|
17
20
|
#
|
18
21
|
# Initialize a new eager loading context.
|
19
22
|
#
|
20
23
|
# @param mode [ActiveRecord::Base] the model that contains the associations that will be referenced.
|
24
|
+
# @param tracer [OccamsRecord::EagerLoaders::Tracer] the eager loader that owns this context (if any)
|
21
25
|
# @param polymorphic [Boolean] When true, model is allowed to change, and it's assumed that not every loader is applicable to every model.
|
22
26
|
#
|
23
|
-
def initialize(model = nil, polymorphic: false)
|
27
|
+
def initialize(model = nil, tracer: nil, polymorphic: false)
|
24
28
|
@model, @polymorphic = model, polymorphic
|
29
|
+
@tracer = tracer || OccamsRecord::EagerLoaders::Tracer.new("root")
|
25
30
|
@loaders = []
|
26
31
|
@dynamic_loaders = []
|
27
32
|
end
|
@@ -125,7 +130,7 @@ module OccamsRecord
|
|
125
130
|
|
126
131
|
scope ||= ->(q) { q.select select } if select
|
127
132
|
loader_class = !!ref.through_reflection ? EagerLoaders::Through : EagerLoaders.fetch!(ref)
|
128
|
-
loader_class.new(ref, scope, use: use, as: custom_name, optimizer: optimizer, &builder)
|
133
|
+
loader_class.new(ref, scope, use: use, as: custom_name, optimizer: optimizer, parent: @tracer, &builder)
|
129
134
|
end
|
130
135
|
end
|
131
136
|
end
|
@@ -5,6 +5,7 @@ module OccamsRecord
|
|
5
5
|
module EagerLoaders
|
6
6
|
autoload :Builder, 'occams-record/eager_loaders/builder'
|
7
7
|
autoload :Context, 'occams-record/eager_loaders/context'
|
8
|
+
autoload :Tracer, 'occams-record/eager_loaders/tracer'
|
8
9
|
|
9
10
|
autoload :Base, 'occams-record/eager_loaders/base'
|
10
11
|
autoload :BelongsTo, 'occams-record/eager_loaders/belongs_to'
|
@@ -7,6 +7,9 @@ module OccamsRecord
|
|
7
7
|
# @return [String] association name
|
8
8
|
attr_reader :name
|
9
9
|
|
10
|
+
# @return [OccamsRecord::EagerLoaders::Tracer | nil] a reference to this eager loader and its parent (if any)
|
11
|
+
attr_reader :tracer
|
12
|
+
|
10
13
|
#
|
11
14
|
# @param ref [ActiveRecord::Association] the ActiveRecord association
|
12
15
|
# @param scope [Proc] a scope to apply to the query (optional). It will be passed an
|
@@ -14,15 +17,23 @@ module OccamsRecord
|
|
14
17
|
# @param use [Array<Module>] optional Module to include in the result class (single or array)
|
15
18
|
# @param as [Symbol] Load the association usign a different attribute name
|
16
19
|
# @param optimizer [Symbol] Only used for `through` associations. A no op here.
|
20
|
+
# @param parent [OccamsRecord::EagerLoaders::Tracer] the eager loader this one is nested under (if any)
|
17
21
|
# @yield perform eager loading on *this* association (optional)
|
18
22
|
#
|
19
|
-
def initialize(ref, scope = nil, use: nil, as: nil, optimizer: nil, &builder)
|
23
|
+
def initialize(ref, scope = nil, use: nil, as: nil, optimizer: nil, parent: nil, &builder)
|
20
24
|
@ref, @scopes, @use = ref, Array(scope), use
|
21
25
|
@name = (as || ref.name).to_s
|
22
26
|
@foreign_type = @ref.foreign_type.to_sym
|
23
27
|
@foreign_key = @ref.foreign_key.to_sym
|
24
|
-
@
|
25
|
-
|
28
|
+
@tracer = Tracer.new(name, parent)
|
29
|
+
@eager_loaders = EagerLoaders::Context.new(nil, tracer: @tracer, polymorphic: true)
|
30
|
+
if builder
|
31
|
+
if builder.arity > 0
|
32
|
+
builder.call(self)
|
33
|
+
else
|
34
|
+
instance_exec(&builder)
|
35
|
+
end
|
36
|
+
end
|
26
37
|
end
|
27
38
|
|
28
39
|
#
|
@@ -9,7 +9,7 @@ module OccamsRecord
|
|
9
9
|
#
|
10
10
|
# See documentation for OccamsRecord::EagerLoaders::Base.
|
11
11
|
#
|
12
|
-
def initialize(ref, scope = nil, use: nil, as: nil, optimizer: :select, &builder)
|
12
|
+
def initialize(ref, scope = nil, use: nil, as: nil, optimizer: :select, parent: nil, &builder)
|
13
13
|
super
|
14
14
|
|
15
15
|
unless @ref.macro == :has_one or @ref.macro == :has_many
|
@@ -74,7 +74,7 @@ module OccamsRecord
|
|
74
74
|
links = @chain[1..-2]
|
75
75
|
tail = @chain[-1]
|
76
76
|
|
77
|
-
outer_loader = EagerLoaders.fetch!(head.ref).new(head.ref, optimized_select(head))
|
77
|
+
outer_loader = EagerLoaders.fetch!(head.ref).new(head.ref, optimized_select(head), parent: tracer.parent)
|
78
78
|
|
79
79
|
links.
|
80
80
|
reduce(outer_loader) { |loader, link|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module OccamsRecord
|
2
|
+
module EagerLoaders
|
3
|
+
# A low-memory way to trace the path of eager loads from any point back to the root query
|
4
|
+
Tracer = Struct.new(:name, :parent) do
|
5
|
+
def to_s
|
6
|
+
lookup.join(".")
|
7
|
+
end
|
8
|
+
|
9
|
+
def lookup(trace = self)
|
10
|
+
return [] if trace.nil?
|
11
|
+
lookup(trace.parent) << trace.name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/occams-record/errors.rb
CHANGED
@@ -13,6 +13,7 @@ module OccamsRecord
|
|
13
13
|
def initialize(record, name)
|
14
14
|
@record, @name = record, name
|
15
15
|
@model_name = record.class.model_name
|
16
|
+
@load_trace = record.class.eager_loader_trace
|
16
17
|
end
|
17
18
|
|
18
19
|
# @return [String]
|
@@ -25,7 +26,8 @@ module OccamsRecord
|
|
25
26
|
class MissingColumnError < MissingDataError
|
26
27
|
# @return [String]
|
27
28
|
def message
|
28
|
-
|
29
|
+
loads = @load_trace.to_s
|
30
|
+
"Column '#{name}' is unavailable on #{model_name} because it was not included in the SELECT statement! Found at #{loads}"
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
@@ -33,7 +35,8 @@ module OccamsRecord
|
|
33
35
|
class MissingEagerLoadError < MissingDataError
|
34
36
|
# @return [String]
|
35
37
|
def message
|
36
|
-
|
38
|
+
loads = @load_trace.to_s
|
39
|
+
"Association '#{name}' is unavailable on #{model_name} because it was not eager loaded! Found at #{loads}"
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
@@ -45,6 +48,7 @@ module OccamsRecord
|
|
45
48
|
attr_reader :attrs
|
46
49
|
|
47
50
|
# @param model_name [String]
|
51
|
+
#
|
48
52
|
# @param attrs [Hash]
|
49
53
|
def initialize(model_name, attrs)
|
50
54
|
@model_name, @attrs = model_name, attrs
|
data/lib/occams-record/query.rb
CHANGED
@@ -92,7 +92,7 @@ module OccamsRecord
|
|
92
92
|
#
|
93
93
|
def run
|
94
94
|
sql = block_given? ? yield(scope).to_sql : scope.to_sql
|
95
|
-
@query_logger << sql if @query_logger
|
95
|
+
@query_logger << "#{@eager_loaders.tracer}: #{sql}" if @query_logger
|
96
96
|
result = if measure?
|
97
97
|
record_start_time!
|
98
98
|
measure!(model.table_name, sql) {
|
@@ -101,7 +101,7 @@ module OccamsRecord
|
|
101
101
|
else
|
102
102
|
model.connection.exec_query sql
|
103
103
|
end
|
104
|
-
row_class = OccamsRecord::Results.klass(result.columns, result.column_types, @eager_loaders.names, model: model, modules: @use)
|
104
|
+
row_class = OccamsRecord::Results.klass(result.columns, result.column_types, @eager_loaders.names, model: model, modules: @use, tracer: @eager_loaders.tracer)
|
105
105
|
rows = result.rows.map { |row| row_class.new row }
|
106
106
|
@eager_loaders.run!(rows, query_logger: @query_logger, measurements: @measurements)
|
107
107
|
yield_measurements!
|
@@ -117,7 +117,7 @@ module OccamsRecord
|
|
117
117
|
else
|
118
118
|
conn.exec_query _escaped_sql
|
119
119
|
end
|
120
|
-
row_class = OccamsRecord::Results.klass(result.columns, result.column_types, @eager_loaders.names, model: @eager_loaders.model, modules: @use)
|
120
|
+
row_class = OccamsRecord::Results.klass(result.columns, result.column_types, @eager_loaders.names, model: @eager_loaders.model, modules: @use, tracer: @eager_loaders.tracer)
|
121
121
|
rows = result.rows.map { |row| row_class.new row }
|
122
122
|
@eager_loaders.run!(rows, query_logger: @query_logger, measurements: @measurements)
|
123
123
|
yield_measurements!
|
@@ -17,9 +17,10 @@ module OccamsRecord
|
|
17
17
|
# @param association_names [Array<String>] names of associations that will be eager loaded into the results.
|
18
18
|
# @param model [ActiveRecord::Base] the AR model representing the table (it holds column & type info).
|
19
19
|
# @param modules [Array<Module>] (optional)
|
20
|
+
# @param [OccamsRecord::EagerLoaders::Base] the eager loaded that loaded this class of records
|
20
21
|
# @return [OccamsRecord::Results::Row] a class customized for this result set
|
21
22
|
#
|
22
|
-
def self.klass(column_names, column_types, association_names = [], model: nil, modules: nil)
|
23
|
+
def self.klass(column_names, column_types, association_names = [], model: nil, modules: nil, tracer: nil)
|
23
24
|
Class.new(Results::Row) do
|
24
25
|
Array(modules).each { |mod| prepend mod } if modules
|
25
26
|
|
@@ -28,6 +29,7 @@ module OccamsRecord
|
|
28
29
|
self._model = model
|
29
30
|
self.model_name = model ? model.name : nil
|
30
31
|
self.table_name = model ? model.table_name : nil
|
32
|
+
self.eager_loader_trace = tracer
|
31
33
|
self.primary_key = if model&.primary_key and (pkey = model.primary_key.to_s) and columns.include?(pkey)
|
32
34
|
pkey
|
33
35
|
end
|
@@ -19,6 +19,8 @@ module OccamsRecord
|
|
19
19
|
attr_accessor :table_name
|
20
20
|
# Name of primary key column (nil if column wasn't in the SELECT)
|
21
21
|
attr_accessor :primary_key
|
22
|
+
# A trace of how this record was loaded (for debugging)
|
23
|
+
attr_accessor :eager_loader_trace
|
22
24
|
end
|
23
25
|
self.columns = []
|
24
26
|
self.associations = []
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: occams-record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Hollinger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02-
|
11
|
+
date: 2023-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- lib/occams-record/eager_loaders/has_one.rb
|
71
71
|
- lib/occams-record/eager_loaders/polymorphic_belongs_to.rb
|
72
72
|
- lib/occams-record/eager_loaders/through.rb
|
73
|
+
- lib/occams-record/eager_loaders/tracer.rb
|
73
74
|
- lib/occams-record/errors.rb
|
74
75
|
- lib/occams-record/measureable.rb
|
75
76
|
- lib/occams-record/merge.rb
|