occams-record 1.6.1 → 1.6.2

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: 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