predictive_load 0.7.0 → 0.8.0
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 -0
- data/lib/predictive_load/active_record_collection_observation.rb +1 -7
- data/lib/predictive_load/loader.rb +5 -4
- data/lib/predictive_load/preload_log.rb +2 -5
- data/lib/predictive_load.rb +21 -0
- 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: 738aa194d5fa872a614133eafb9bf2eefe2bc4c6958eaf02784cdee1e3b56ade
|
4
|
+
data.tar.gz: ff4d1953f00722fee627b1b73c41d1af2ee3b5168993886dfa9bd65529d616d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d5c7f2b7bd5ce257238cc0a767f44dbb0889b5fbd5311dd7f56ce0cc237c6d424e9a78f0758fac73ca2e8ec55226f01db3f843455a7d2324b7d02f1aead937f
|
7
|
+
data.tar.gz: 3fd1edf5e995179c55e9bfba6a1801daad33f036f2f95ac2f56b928c143f65957d9be7221434592f5896ce7087f50f0eb1103b3f2022ffe710a16133a49a7ebd
|
data/README.md
CHANGED
@@ -41,6 +41,28 @@ Some things cannot be preloaded, use `predictive_load: false`
|
|
41
41
|
has_many :foos, predictive_load: false
|
42
42
|
```
|
43
43
|
|
44
|
+
### Instrumentation
|
45
|
+
|
46
|
+
The library can be instrumented by providing a callback, to be invoked every time automatic preloading happens. The callback must be a callable that receives two arguments:
|
47
|
+
* The record (instance) on which the queries that triggered automatic preloading are being performed, in the form of some association call.
|
48
|
+
* The association object, which can be inspected to check the type and name of the association.
|
49
|
+
|
50
|
+
For example, the callback could be used to emit some metrics:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require "active_support/core_ext/string"
|
54
|
+
|
55
|
+
PredictiveLoad.callback = -> (record, association) do
|
56
|
+
METRICS_CLIENT.increment_counter(
|
57
|
+
"active_record.automatic_preloads",
|
58
|
+
tags: [
|
59
|
+
"model:#{record.class.name.underscore}",
|
60
|
+
"association:#{association.reflection.name}"
|
61
|
+
]
|
62
|
+
)
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
44
66
|
#### Known limitations:
|
45
67
|
|
46
68
|
* Calling association#size will trigger an N+1 on SELECT COUNT(*). Work around by calling #length, loading all records.
|
@@ -1,5 +1,4 @@
|
|
1
1
|
module PredictiveLoad::ActiveRecordCollectionObservation
|
2
|
-
|
3
2
|
def self.included(base)
|
4
3
|
ActiveRecord::Relation.class_attribute :collection_observer
|
5
4
|
if ActiveRecord::VERSION::MAJOR >= 5
|
@@ -38,9 +37,7 @@ module PredictiveLoad::ActiveRecordCollectionObservation
|
|
38
37
|
end
|
39
38
|
|
40
39
|
module CollectionMember
|
41
|
-
|
42
40
|
attr_accessor :collection_observer
|
43
|
-
|
44
41
|
end
|
45
42
|
|
46
43
|
# disable eager loading since includes + unscoped is broken on rails 4
|
@@ -73,9 +70,7 @@ module PredictiveLoad::ActiveRecordCollectionObservation
|
|
73
70
|
protected
|
74
71
|
|
75
72
|
def notify_collection_observer
|
76
|
-
|
77
|
-
@owner.collection_observer.loading_association(@owner, self)
|
78
|
-
end
|
73
|
+
@owner.collection_observer&.loading_association(@owner, self)
|
79
74
|
end
|
80
75
|
end
|
81
76
|
|
@@ -86,5 +81,4 @@ module PredictiveLoad::ActiveRecordCollectionObservation
|
|
86
81
|
super
|
87
82
|
end
|
88
83
|
end
|
89
|
-
|
90
84
|
end
|
@@ -23,6 +23,7 @@ module PredictiveLoad
|
|
23
23
|
association_name = association.reflection.name
|
24
24
|
|
25
25
|
if all_records_will_likely_load_association?(association_name) && supports_preload?(association)
|
26
|
+
PredictiveLoad.callback&.call(record, association)
|
26
27
|
preload(association_name)
|
27
28
|
end
|
28
29
|
end
|
@@ -32,7 +33,7 @@ module PredictiveLoad
|
|
32
33
|
attr_reader :records
|
33
34
|
|
34
35
|
def all_records_will_likely_load_association?(association_name)
|
35
|
-
if defined?(Mocha) && association_name.to_s.index(
|
36
|
+
if defined?(Mocha) && association_name.to_s.index("_stub_")
|
36
37
|
false
|
37
38
|
else
|
38
39
|
true
|
@@ -43,11 +44,11 @@ module PredictiveLoad
|
|
43
44
|
return false if ActiveRecord::Base.predictive_load_disabled.include?(association.klass)
|
44
45
|
return false if association.reflection.options[:predictive_load] == false
|
45
46
|
return false if association.reflection.options[:conditions].respond_to?(:to_proc) # rails 3 conditions proc (we do not know if it uses instance methods)
|
46
|
-
if scope = association.reflection.scope
|
47
|
+
if (scope = association.reflection.scope)
|
47
48
|
if scope.is_a?(Proc)
|
48
49
|
# rails 4+ conditions block, if it uses a passed in object, we assume it is not preloadable
|
49
50
|
return false if scope.arity.to_i > 0
|
50
|
-
elsif where = scope.options[:where]
|
51
|
+
elsif (where = scope.options[:where])
|
51
52
|
# ActiveRecord::Associations::Builder::DeprecatedOptionsProc from rails 4.0 and deprecated finders
|
52
53
|
# when conditions was a proc the where will be a proc too -> check arity
|
53
54
|
return false if where.is_a?(Proc) && where.arity > 0
|
@@ -74,7 +75,7 @@ module PredictiveLoad
|
|
74
75
|
else
|
75
76
|
def preload(association_name)
|
76
77
|
rs = records_with_association(association_name).reject { |r| r.association(association_name).loaded? }
|
77
|
-
ActiveRecord::Associations::Preloader.new.preload(rs, [
|
78
|
+
ActiveRecord::Associations::Preloader.new.preload(rs, [association_name])
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
@@ -1,8 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "active_record/associations/preloader"
|
2
2
|
|
3
3
|
module PredictiveLoad
|
4
4
|
class PreloadLog < ActiveRecord::Associations::Preloader
|
5
|
-
|
6
5
|
attr_accessor :logger
|
7
6
|
|
8
7
|
def preload(association)
|
@@ -17,7 +16,7 @@ module PredictiveLoad
|
|
17
16
|
|
18
17
|
preload_sql = preloader.scope.where(collection_arel(preloader)).to_sql
|
19
18
|
|
20
|
-
log("would preload with: #{preload_sql
|
19
|
+
log("would preload with: #{preload_sql}")
|
21
20
|
klass.connection.explain(preload_sql).each_line do |line|
|
22
21
|
log(line)
|
23
22
|
end
|
@@ -34,7 +33,5 @@ module PredictiveLoad
|
|
34
33
|
def log(message)
|
35
34
|
ActiveRecord::Base.logger.info("predictive_load: #{message}")
|
36
35
|
end
|
37
|
-
|
38
36
|
end
|
39
|
-
|
40
37
|
end
|
data/lib/predictive_load.rb
CHANGED
@@ -4,6 +4,27 @@ module PredictiveLoad
|
|
4
4
|
super + [:predictive_load]
|
5
5
|
end
|
6
6
|
end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_reader :callback
|
10
|
+
|
11
|
+
# Configure a callback to be invoked when the library preloads some association.
|
12
|
+
#
|
13
|
+
# It must be a callable with an arity of two.
|
14
|
+
# The callback receives two arguments:
|
15
|
+
# - The record (instance) on which the queries that triggered automatic preloading
|
16
|
+
# are being performed, in the form of some association call.
|
17
|
+
# - The association object, which can be inspected to check the type and name of
|
18
|
+
# the association.
|
19
|
+
#
|
20
|
+
def callback=(c)
|
21
|
+
if c.nil? || (c.respond_to?(:call) && c.respond_to?(:arity) && c.arity == 2)
|
22
|
+
@callback = c
|
23
|
+
else
|
24
|
+
raise ArgumentError, "wrong callback type, it must be a callable that supports 2 arguments"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
7
28
|
end
|
8
29
|
|
9
30
|
if ActiveRecord::VERSION::MAJOR >= 5
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: predictive_load
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Chapweske
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|