occams-record 1.6.1 → 1.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc34df60947ca8d92f1fa00ab74271fdec70439b07eccc2d5f3a2fa19a67efbe
4
- data.tar.gz: 7a77182c754af5c0cfd6689a86745af8edc260fb721076a7c317fab3cebb6c1c
3
+ metadata.gz: 6a233847b8c1a9de8c77e8c5e1d12f70ea7c8b9efa498208491a17ff09a6f217
4
+ data.tar.gz: f87fbaf43703cd29b60b94a480ad903d00cad2b9b3bd9bfb4a77b7748f68aa81
5
5
  SHA512:
6
- metadata.gz: f9aac2db52de95e034e05c1fa2f078ea744cbecf715c92add927b2e227ec0ca20734575bec25f51da28bcab511febc2fd02a7efdb7def6a2a12165aebfa5eb8e
7
- data.tar.gz: 89bff53f5b3e975e03074b4bfb7088f25441ecf170acbe3f676376eae8fcaa8b943274d5731d65d12fe2757d1327b27b8730adc5e101025cd8c72a563ef03d22
6
+ metadata.gz: 21115e90f5f455eaf33d5c0596d6276957905fb14932583fcdfacdb1a44f7afeaee029458de903fa58c2801e7504643c6c6e4a530822409ff93da6954648a06e
7
+ data.tar.gz: a38eba237379a4a0b55d027746a8796f9aa9904e60bb1ba6acd6fd46348fc84e0aef17fc6de482d5b32d6677c09b2ea53602b000bf6da9a60d5bd2dec0e72b3f
data/README.md CHANGED
@@ -303,7 +303,11 @@ The SQL string and binds should be familiar. `%{ids}` will be provided for you -
303
303
 
304
304
  ## Injecting instance methods
305
305
 
306
- Occams Records results are just plain rows; there are no methods from your Rails models. (Separating your persistence layer from your domain is good thing!) But sometimes you need a few methods. Occams Record allows you to specify modules to be included in your results.
306
+ Occams Records results are just plain rows; there are no methods from your Rails models. (Separating your persistence layer from your domain is good thing!) But sometimes you need a few methods. Occams Record provides two ways of accomplishing this.
307
+
308
+ ### Include custom modules
309
+
310
+ You may also specify one or more modules to be included in your results:
307
311
 
308
312
  ```ruby
309
313
  module MyOrderMethods
@@ -326,6 +330,23 @@ orders = OccamsRecord
326
330
  .run
327
331
  ```
328
332
 
333
+ ### ActiveRecord method fallback
334
+
335
+ This is an ugly hack of last resort if you can't easily extract a method from your model into a shared module. Plugins, like `carrierwave`, are a good example. When you call a method that doesn't exist on an Occams Record result, it will initialize an ActiveRecord object and forward the method call to it.
336
+
337
+ The `active_record_fallback` option must be passed either `:lazy` or `:strict` (recommended). `:strict` enables ActiveRecord's strict loading option, helping you avoid N+1 queries. :lazy allows them. Note that `:strict` is only available for ActiveRecord 6.1 and later.
338
+
339
+ The following will forward any nonexistent methods for `Order` and `Product` records:
340
+
341
+ ```ruby
342
+ orders = OccamsRecord
343
+ .query(Order.all, active_record_fallback: :strict)
344
+ .eager_load(:line_items) {
345
+ eager_load(:product, active_record_fallback: :strict)
346
+ }
347
+ .run
348
+ ```
349
+
329
350
  ---
330
351
 
331
352
  # Unsupported features
@@ -14,6 +14,9 @@ module OccamsRecord
14
14
  # @return [OccamsRecord::EagerLoaders::Tracer | nil] a reference to this eager loader and its parent (if any)
15
15
  attr_reader :tracer
16
16
 
17
+ # @return [OccamsRecord::EagerLoaders::Context]
18
+ attr_reader :eager_loaders
19
+
17
20
  #
18
21
  # Initialize a new add hoc association.
19
22
  #
@@ -12,6 +12,9 @@ module OccamsRecord
12
12
  # @return [OccamsRecord::EagerLoaders::Tracer | nil] a reference to this eager loader and its parent (if any)
13
13
  attr_reader :tracer
14
14
 
15
+ # @return [OccamsRecord::EagerLoaders::Context]
16
+ attr_reader :eager_loaders
17
+
15
18
  #
16
19
  # @param ref [ActiveRecord::Association] the ActiveRecord association
17
20
  # @param scope [Proc] a scope to apply to the query (optional). It will be passed an
@@ -20,14 +23,16 @@ module OccamsRecord
20
23
  # @param as [Symbol] Load the association usign a different attribute name
21
24
  # @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
25
  # @param parent [OccamsRecord::EagerLoaders::Tracer] the eager loader this one is nested under (if any)
26
+ # @param active_record_fallback [Symbol] If passed, missing methods will be forwarded to an ActiveRecord instance. Options are :lazy (allow lazy loading in the AR record) or :strict (require strict loading)
23
27
  # @yield perform eager loading on *this* association (optional)
24
28
  #
25
- def initialize(ref, scope = nil, use: nil, as: nil, optimizer: :select, parent: nil, &builder)
29
+ def initialize(ref, scope = nil, use: nil, as: nil, optimizer: :select, parent: nil, active_record_fallback: nil, &builder)
26
30
  @ref, @scopes, @use, @as = ref, Array(scope), use, as
27
31
  @model = ref.klass
28
32
  @name = (as || ref.name).to_s
29
33
  @tracer = Tracer.new(name, parent)
30
34
  @eager_loaders = EagerLoaders::Context.new(@model, tracer: @tracer)
35
+ @active_record_fallback = active_record_fallback
31
36
  @optimizer = optimizer
32
37
  if builder
33
38
  if builder.arity > 0
@@ -60,7 +65,7 @@ module OccamsRecord
60
65
  #
61
66
  def run(rows, query_logger: nil, measurements: nil)
62
67
  query(rows) { |*args|
63
- assoc_rows = args[0] ? Query.new(args[0], use: @use, eager_loaders: @eager_loaders, query_logger: query_logger, measurements: measurements).run : []
68
+ assoc_rows = args[0] ? Query.new(args[0], use: @use, eager_loaders: @eager_loaders, query_logger: query_logger, measurements: measurements, active_record_fallback: @active_record_fallback).run : []
64
69
  merge! assoc_rows, rows, *args[1..-1]
65
70
  }
66
71
  nil
@@ -24,11 +24,12 @@ module OccamsRecord
24
24
  # @param as [Symbol] Load the association usign a different attribute name
25
25
  # @param from [Symbol] Opposite of `as`. `assoc` is the custom name and `from` is the name of association on the ActiveRecord model.
26
26
  # @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)
27
+ # @param active_record_fallback [Symbol] If passed, missing methods will be forwarded to an ActiveRecord instance. Options are :lazy (allow lazy loading in the AR record) or :strict (require strict loading)
27
28
  # @yield a block where you may perform eager loading on *this* association (optional)
28
29
  # @return self
29
30
  #
30
- def eager_load(assoc, scope = nil, select: nil, use: nil, as: nil, from: nil, optimizer: :select, &builder)
31
- @eager_loaders.add(assoc, scope, select: select, use: use, as: as, from: from, optimizer: optimizer, &builder)
31
+ def eager_load(assoc, scope = nil, select: nil, use: nil, as: nil, from: nil, optimizer: :select, active_record_fallback: nil, &builder)
32
+ @eager_loaders.add(assoc, scope, select: select, use: use, as: as, from: from, optimizer: optimizer, active_record_fallback: active_record_fallback, &builder)
32
33
  self
33
34
  end
34
35
 
@@ -45,11 +46,12 @@ module OccamsRecord
45
46
  # @param as [Symbol] Load the association usign a different attribute name
46
47
  # @param from [Symbol] Opposite of `as`. `assoc` is the custom name and `from` is the name of association on the ActiveRecord model.
47
48
  # @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)
49
+ # @param active_record_fallback [Symbol] If passed, the record(s) will be converted to read-only ActiveRecord objects. Options are :lazy (allow lazy loading) or :strict (enables strict loading)
48
50
  # @return [OccamsRecord::EagerLoaders::Base]
49
51
  #
50
- def nest(assoc, scope = nil, select: nil, use: nil, as: nil, from: nil, optimizer: :select)
52
+ def nest(assoc, scope = nil, select: nil, use: nil, as: nil, from: nil, optimizer: :select, active_record_fallback: nil)
51
53
  raise ArgumentError, "OccamsRecord::EagerLoaders::Builder#nest does not accept a block!" if block_given?
52
- @eager_loaders.add(assoc, scope, select: select, use: use, as: as, from: from, optimizer: optimizer) ||
54
+ @eager_loaders.add(assoc, scope, select: select, use: use, as: as, from: from, optimizer: optimizer, active_record_fallback: active_record_fallback) ||
53
55
  raise("OccamsRecord::EagerLoaders::Builder#nest may not be called under a polymorphic association")
54
56
  end
55
57
 
@@ -77,10 +77,11 @@ module OccamsRecord
77
77
  # @param as [Symbol] Load the association usign a different attribute name
78
78
  # @param from [Symbol] Opposite of `as`. `assoc` is the custom name and `from` is the name of association on the ActiveRecord model.
79
79
  # @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)
80
+ # @param active_record_fallback [Symbol] If passed, missing methods will be forwarded to an ActiveRecord instance. Options are :lazy (allow lazy loading in the AR record) or :strict (require strict loading)
80
81
  # @yield a block where you may perform eager loading on *this* association (optional)
81
82
  # @return [OccamsRecord::EagerLoaders::Base] the new loader. if @model is nil, nil will be returned.
82
83
  #
83
- def add(assoc, scope = nil, select: nil, use: nil, as: nil, from: nil, optimizer: :select, &builder)
84
+ def add(assoc, scope = nil, select: nil, use: nil, as: nil, from: nil, optimizer: :select, active_record_fallback: nil, &builder)
84
85
  if from
85
86
  real_assoc = from
86
87
  custom_name = assoc
@@ -93,11 +94,11 @@ module OccamsRecord
93
94
  end
94
95
 
95
96
  if @model
96
- loader = build_loader!(real_assoc, custom_name, scope, select, use, optimizer, builder)
97
+ loader = build_loader!(real_assoc, custom_name, scope, select, use, optimizer, builder, active_record_fallback)
97
98
  @loaders << loader
98
99
  loader
99
100
  else
100
- @dynamic_loaders << [real_assoc, custom_name, scope, select, use, optimizer, builder]
101
+ @dynamic_loaders << [real_assoc, custom_name, scope, select, use, optimizer, builder, active_record_fallback]
101
102
  nil
102
103
  end
103
104
  end
@@ -116,21 +117,27 @@ module OccamsRecord
116
117
  nil
117
118
  end
118
119
 
120
+ # Iterate over each loader
121
+ def each
122
+ return @loaders.each unless block_given?
123
+ @loaders.each { |l| yield l }
124
+ end
125
+
119
126
  private
120
127
 
121
- def build_loader!(assoc, custom_name, scope, select, use, optimizer, builder)
122
- build_loader(assoc, custom_name, scope, select, use, optimizer, builder) ||
128
+ def build_loader!(assoc, custom_name, scope, select, use, optimizer, builder, active_record_fallback)
129
+ build_loader(assoc, custom_name, scope, select, use, optimizer, builder, active_record_fallback) ||
123
130
  raise("OccamsRecord: No association `:#{assoc}` on `#{@model.name}` or subclasses")
124
131
  end
125
132
 
126
- def build_loader(assoc, custom_name, scope, select, use, optimizer, builder)
133
+ def build_loader(assoc, custom_name, scope, select, use, optimizer, builder, active_record_fallback)
127
134
  ref = @model.reflections[assoc.to_s] ||
128
135
  @model.subclasses.map(&:reflections).detect { |x| x.has_key? assoc.to_s }&.[](assoc.to_s)
129
136
  return nil if ref.nil?
130
137
 
131
138
  scope ||= ->(q) { q.select select } if select
132
139
  loader_class = !!ref.through_reflection ? EagerLoaders::Through : EagerLoaders.fetch!(ref)
133
- loader_class.new(ref, scope, use: use, as: custom_name, optimizer: optimizer, parent: @tracer, &builder)
140
+ loader_class.new(ref, scope, use: use, as: custom_name, optimizer: optimizer, parent: @tracer, active_record_fallback: active_record_fallback, &builder)
134
141
  end
135
142
  end
136
143
  end
@@ -10,6 +10,9 @@ module OccamsRecord
10
10
  # @return [OccamsRecord::EagerLoaders::Tracer | nil] a reference to this eager loader and its parent (if any)
11
11
  attr_reader :tracer
12
12
 
13
+ # @return [OccamsRecord::EagerLoaders::Context]
14
+ attr_reader :eager_loaders
15
+
13
16
  #
14
17
  # @param ref [ActiveRecord::Association] the ActiveRecord association
15
18
  # @param scope [Proc] a scope to apply to the query (optional). It will be passed an
@@ -18,15 +21,17 @@ module OccamsRecord
18
21
  # @param as [Symbol] Load the association usign a different attribute name
19
22
  # @param optimizer [Symbol] Only used for `through` associations. A no op here.
20
23
  # @param parent [OccamsRecord::EagerLoaders::Tracer] the eager loader this one is nested under (if any)
24
+ # @param active_record_fallback [Symbol] If passed, missing methods will be forwarded to an ActiveRecord instance. Options are :lazy (allow lazy loading in the AR record) or :strict (require strict loading)
21
25
  # @yield perform eager loading on *this* association (optional)
22
26
  #
23
- def initialize(ref, scope = nil, use: nil, as: nil, optimizer: nil, parent: nil, &builder)
27
+ def initialize(ref, scope = nil, use: nil, as: nil, optimizer: nil, parent: nil, active_record_fallback: nil, &builder)
24
28
  @ref, @scopes, @use = ref, Array(scope), use
25
29
  @name = (as || ref.name).to_s
26
30
  @foreign_type = @ref.foreign_type.to_sym
27
31
  @foreign_key = @ref.foreign_key.to_sym
28
32
  @tracer = Tracer.new(name, parent)
29
33
  @eager_loaders = EagerLoaders::Context.new(nil, tracer: @tracer, polymorphic: true)
34
+ @active_record_fallback = active_record_fallback
30
35
  if builder
31
36
  if builder.arity > 0
32
37
  builder.call(self)
@@ -60,7 +65,7 @@ module OccamsRecord
60
65
  query(rows) { |scope|
61
66
  eager_loaders = @eager_loaders.dup
62
67
  eager_loaders.model = scope.klass
63
- assoc_rows = Query.new(scope, use: @use, eager_loaders: eager_loaders, query_logger: query_logger, measurements: measurements).run
68
+ assoc_rows = Query.new(scope, use: @use, eager_loaders: eager_loaders, query_logger: query_logger, measurements: measurements, active_record_fallback: @active_record_fallback).run
64
69
  merge! assoc_rows, rows
65
70
  }
66
71
  nil
@@ -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, parent: nil, &builder)
12
+ def initialize(ref, scope = nil, use: nil, as: nil, optimizer: :select, parent: nil, active_record_fallback: nil, &builder)
13
13
  super
14
14
 
15
15
  unless @ref.macro == :has_one or @ref.macro == :has_many
@@ -75,13 +75,18 @@ module OccamsRecord
75
75
  tail = @chain[-1]
76
76
 
77
77
  outer_loader = EagerLoaders.fetch!(head.ref).new(head.ref, optimized_select(head), parent: tracer.parent)
78
+ outer_loader.tracer.through = true
78
79
 
79
- links.
80
+ inner_loader = links.
80
81
  reduce(outer_loader) { |loader, link|
81
- loader.nest(link.ref.source_reflection.name, optimized_select(link))
82
+ nested_loader = loader.nest(link.ref.source_reflection.name, optimized_select(link))
83
+ nested_loader.tracer.through = true
84
+ nested_loader
82
85
  }.
83
- nest(tail.ref.source_reflection.name, @scope, use: @use, as: @as)
86
+ nest(tail.ref.source_reflection.name, @scope, use: @use, as: @as, active_record_fallback: @active_record_fallback)
84
87
 
88
+ @eager_loaders.each { |loader| inner_loader.eager_loaders << loader }
89
+ inner_loader.tracer.name = tracer.name
85
90
  outer_loader
86
91
  end
87
92
 
@@ -1,14 +1,15 @@
1
1
  module OccamsRecord
2
2
  module EagerLoaders
3
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
4
+ Tracer = Struct.new(:name, :parent, :through) do
5
5
  def to_s
6
6
  lookup.join(".")
7
7
  end
8
8
 
9
9
  def lookup(trace = self)
10
10
  return [] if trace.nil?
11
- lookup(trace.parent) << trace.name
11
+ name = trace.through ? "through(#{trace.name})" : trace.name
12
+ lookup(trace.parent) << name
12
13
  end
13
14
  end
14
15
  end
@@ -27,7 +27,7 @@ module OccamsRecord
27
27
  # @return [String]
28
28
  def message
29
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}"
30
+ "Column '#{name}' is unavailable on #{model_name} because it was not included in the SELECT statement! Occams Record trace: #{loads}"
31
31
  end
32
32
  end
33
33
 
@@ -36,7 +36,7 @@ module OccamsRecord
36
36
  # @return [String]
37
37
  def message
38
38
  loads = @load_trace.to_s
39
- "Association '#{name}' is unavailable on #{model_name} because it was not eager loaded! Found at #{loads}"
39
+ "Association '#{name}' is unavailable on #{model_name} because it was not eager loaded! Occams Record trace: #{loads}"
40
40
  end
41
41
  end
42
42
 
@@ -18,10 +18,11 @@ module OccamsRecord
18
18
  # @param scope [ActiveRecord::Relation]
19
19
  # @param use [Module] optional Module to include in the result class
20
20
  # @param query_logger [Array] (optional) an array into which all queries will be inserted for logging/debug purposes
21
+ # @param active_record_fallback [Symbol] If passed, missing methods will be forwarded to an ActiveRecord instance. Options are :lazy (allow lazy loading in the AR record) or :strict (require strict loading)
21
22
  # @return [OccamsRecord::Query]
22
23
  #
23
- def self.query(scope, use: nil, query_logger: nil)
24
- Query.new(scope, use: use, query_logger: query_logger)
24
+ def self.query(scope, use: nil, active_record_fallback: nil, query_logger: nil)
25
+ Query.new(scope, use: use, query_logger: query_logger, active_record_fallback: active_record_fallback)
25
26
  end
26
27
 
27
28
  #
@@ -47,13 +48,15 @@ module OccamsRecord
47
48
  # @param query_logger [Array] (optional) an array into which all queries will be inserted for logging/debug purposes
48
49
  # @param eager_loaders [OccamsRecord::EagerLoaders::Context]
49
50
  # @param measurements [Array]
51
+ # @param active_record_fallback [Symbol] If passed, missing methods will be forwarded to an ActiveRecord instance. Options are :lazy (allow lazy loading in the AR record) or :strict (require strict loading)
50
52
  #
51
- def initialize(scope, use: nil, eager_loaders: nil, query_logger: nil, measurements: nil)
53
+ def initialize(scope, use: nil, eager_loaders: nil, query_logger: nil, measurements: nil, active_record_fallback: nil)
52
54
  @model = scope.klass
53
55
  @scope = scope
54
56
  @eager_loaders = eager_loaders || EagerLoaders::Context.new(@model)
55
57
  @use = use
56
58
  @query_logger, @measurements = query_logger, measurements
59
+ @active_record_fallback = active_record_fallback
57
60
  end
58
61
 
59
62
  #
@@ -101,7 +104,7 @@ module OccamsRecord
101
104
  else
102
105
  model.connection.exec_query sql
103
106
  end
104
- row_class = OccamsRecord::Results.klass(result.columns, result.column_types, @eager_loaders.names, model: model, modules: @use, tracer: @eager_loaders.tracer)
107
+ row_class = OccamsRecord::Results.klass(result.columns, result.column_types, @eager_loaders.names, model: model, modules: @use, tracer: @eager_loaders.tracer, active_record_fallback: @active_record_fallback)
105
108
  rows = result.rows.map { |row| row_class.new row }
106
109
  @eager_loaders.run!(rows, query_logger: @query_logger, measurements: @measurements)
107
110
  yield_measurements!
@@ -17,10 +17,14 @@ 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
+ # @param tracer [OccamsRecord::EagerLoaders::Tracer] the eager loaded that loaded this class of records
21
+ # @param active_record_fallback [Symbol] If passed, missing methods will be forwarded to an ActiveRecord instance. Options are :lazy (allow lazy loading in the AR record) or :strict (require strict loading)
21
22
  # @return [OccamsRecord::Results::Row] a class customized for this result set
22
23
  #
23
- def self.klass(column_names, column_types, association_names = [], model: nil, modules: nil, tracer: nil)
24
+ def self.klass(column_names, column_types, association_names = [], model: nil, modules: nil, tracer: nil, active_record_fallback: nil)
25
+ raise ArgumentError, "Invalid active_record_fallback option :#{active_record_fallback}. Valid options are :lazy, :strict" if active_record_fallback and !%i(lazy strict).include?(active_record_fallback)
26
+ raise ArgumentError, "Option active_record_fallback is not allowed when no model is present" if active_record_fallback and model.nil?
27
+
24
28
  Class.new(Results::Row) do
25
29
  Array(modules).each { |mod| prepend mod } if modules
26
30
 
@@ -30,6 +34,7 @@ module OccamsRecord
30
34
  self.model_name = model ? model.name : nil
31
35
  self.table_name = model ? model.table_name : nil
32
36
  self.eager_loader_trace = tracer
37
+ self.active_record_fallback = active_record_fallback
33
38
  self.primary_key = if model&.primary_key and (pkey = model.primary_key.to_s) and columns.include?(pkey)
34
39
  pkey
35
40
  end
@@ -21,6 +21,8 @@ module OccamsRecord
21
21
  attr_accessor :primary_key
22
22
  # A trace of how this record was loaded (for debugging)
23
23
  attr_accessor :eager_loader_trace
24
+ # If present, missing methods will be forwarded to the ActiveRecord model. :lazy allows lazy loading in AR, :strict doesn't
25
+ attr_accessor :active_record_fallback
24
26
  end
25
27
  self.columns = []
26
28
  self.associations = []
@@ -112,19 +114,24 @@ module OccamsRecord
112
114
  IDS_SUFFIX = /_ids$/
113
115
  def method_missing(name, *args, &block)
114
116
  model = self.class._model
115
- return super if args.any? or !block.nil? or model.nil?
117
+ ex = NoMethodError.new("Undefined method `#{name}' for #{self.inspect}. Occams Record trace: #{self.class.eager_loader_trace}", name, args)
118
+ raise ex if model.nil?
116
119
 
117
120
  name_str = name.to_s
118
121
  assoc = name_str.sub(IDS_SUFFIX, "").pluralize
119
- if name_str =~ IDS_SUFFIX and can_define_ids_reader? assoc
122
+ no_args = args.empty? && block.nil?
123
+
124
+ if no_args and name_str =~ IDS_SUFFIX and can_define_ids_reader? assoc
120
125
  define_ids_reader! assoc
121
126
  send name
122
- elsif model.reflections.has_key? name_str
127
+ elsif no_args and model.reflections.has_key? name_str
123
128
  raise MissingEagerLoadError.new(self, name)
124
- elsif model.columns_hash.has_key? name_str
129
+ elsif no_args and model.columns_hash.has_key? name_str
125
130
  raise MissingColumnError.new(self, name)
131
+ elsif self.class.active_record_fallback
132
+ active_record_fallback(name, *args, &block)
126
133
  else
127
- super
134
+ raise ex
128
135
  end
129
136
  end
130
137
 
@@ -143,6 +150,15 @@ module OccamsRecord
143
150
 
144
151
  private
145
152
 
153
+ def active_record_fallback(name, *args, &block)
154
+ @active_record_fallback ||= Ugly::active_record(self.class._model, self).tap { |record|
155
+ record.strict_loading! if self.class.active_record_fallback == :strict
156
+ }
157
+ @active_record_fallback.send(name, *args, &block)
158
+ rescue NoMethodError => e
159
+ raise NoMethodError.new("#{e.message}. Occams Record trace: #{self.class.eager_loader_trace}.active_record_fallback(#{self.class._model.name})", name, args)
160
+ end
161
+
146
162
  def can_define_ids_reader?(assoc)
147
163
  model = self.class._model
148
164
  self.class.associations.include?(assoc) &&
@@ -3,5 +3,5 @@
3
3
  #
4
4
  module OccamsRecord
5
5
  # @private
6
- VERSION = "1.6.1".freeze
6
+ VERSION = "1.6.2".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.1
4
+ version: 1.6.2
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-17 00:00:00.000000000 Z
11
+ date: 2023-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord