occams-record 1.6.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 +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
|