occams-record 1.6.0 → 1.6.1
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/README.md +1 -1
- data/lib/occams-record/eager_loaders/ad_hoc_base.rb +7 -2
- data/lib/occams-record/eager_loaders/base.rb +7 -2
- 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 +7 -2
- 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
|
@@ -146,7 +146,7 @@ OccamsRecord
|
|
|
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
|
```
|
|
@@ -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,12 +23,14 @@ 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
|
-
@
|
|
32
|
+
@tracer = Tracer.new(name, parent)
|
|
33
|
+
@eager_loaders = EagerLoaders::Context.new(@model, tracer: @tracer)
|
|
29
34
|
if builder
|
|
30
35
|
if builder.arity > 0
|
|
31
36
|
builder.call(self)
|
|
@@ -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,13 +19,15 @@ 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
|
|
28
33
|
if builder.arity > 0
|
|
@@ -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,14 +17,16 @@ 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
|
-
@
|
|
28
|
+
@tracer = Tracer.new(name, parent)
|
|
29
|
+
@eager_loaders = EagerLoaders::Context.new(nil, tracer: @tracer, polymorphic: true)
|
|
25
30
|
if builder
|
|
26
31
|
if builder.arity > 0
|
|
27
32
|
builder.call(self)
|
|
@@ -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.6.
|
|
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
|