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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a367407e2af3e4110327d26438fcb2a590b7fbd34e6b60dd4bd9396665c495fe
4
- data.tar.gz: 8cf6fb913ac787126a80fe38baff3a56940cea8afc80e1b05e3c864ae8368e11
3
+ metadata.gz: bc34df60947ca8d92f1fa00ab74271fdec70439b07eccc2d5f3a2fa19a67efbe
4
+ data.tar.gz: 7a77182c754af5c0cfd6689a86745af8edc260fb721076a7c317fab3cebb6c1c
5
5
  SHA512:
6
- metadata.gz: 2bc45d11bc974e5a5ec6b2b9ffab8e3f069048ee8335122ad81a8b49cc852fa4bdb27116865000e4b70e4f6d617b3f870861288e00a43b273e3ec14d0dfdb6b6
7
- data.tar.gz: af1dac7260e93ed3778ff022c3d6113819528e37d77c275b9cada6cd5e731593bbe9e4f24ae283ed572a4876672126df4cb002ab8f9c863e3de4943f95acdbc2
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
- @eager_loaders = EagerLoaders::Context.new(@model)
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
- @eager_loaders = EagerLoaders::Context.new(@model)
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
- @eager_loaders = EagerLoaders::Context.new(nil, polymorphic: true)
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
@@ -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
- "Column '#{name}' is unavailable on #{model_name} because it was not included in the SELECT statement!"
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
- "Association '#{name}' is unavailable on #{model_name} because it was not eager loaded!"
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
@@ -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 = []
@@ -3,5 +3,5 @@
3
3
  #
4
4
  module OccamsRecord
5
5
  # @private
6
- VERSION = "1.6.0".freeze
6
+ VERSION = "1.6.1".freeze
7
7
  end
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.0
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-14 00:00:00.000000000 Z
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