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 +4 -4
- data/README.md +22 -1
- data/lib/occams-record/eager_loaders/ad_hoc_base.rb +3 -0
- data/lib/occams-record/eager_loaders/base.rb +7 -2
- data/lib/occams-record/eager_loaders/builder.rb +6 -4
- data/lib/occams-record/eager_loaders/context.rb +14 -7
- data/lib/occams-record/eager_loaders/polymorphic_belongs_to.rb +7 -2
- data/lib/occams-record/eager_loaders/through.rb +9 -4
- data/lib/occams-record/eager_loaders/tracer.rb +3 -2
- data/lib/occams-record/errors.rb +2 -2
- data/lib/occams-record/query.rb +7 -4
- data/lib/occams-record/results/results.rb +7 -2
- data/lib/occams-record/results/row.rb +21 -5
- data/lib/occams-record/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a233847b8c1a9de8c77e8c5e1d12f70ea7c8b9efa498208491a17ff09a6f217
|
|
4
|
+
data.tar.gz: f87fbaf43703cd29b60b94a480ad903d00cad2b9b3bd9bfb4a77b7748f68aa81
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
11
|
+
name = trace.through ? "through(#{trace.name})" : trace.name
|
|
12
|
+
lookup(trace.parent) << name
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
end
|
data/lib/occams-record/errors.rb
CHANGED
|
@@ -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!
|
|
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!
|
|
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
|
|
data/lib/occams-record/query.rb
CHANGED
|
@@ -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::
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) &&
|
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.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-
|
|
11
|
+
date: 2023-02-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|