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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93ddc441f98b6e7f9ec3b9a4277b5bf91cad8d360564e2c46dfae3a4963e647b
4
- data.tar.gz: 05c1fc9459671d0cad41b68c9f6e5e5003e006ba582a5fedf3b63aca379a85fa
3
+ metadata.gz: 738aa194d5fa872a614133eafb9bf2eefe2bc4c6958eaf02784cdee1e3b56ade
4
+ data.tar.gz: ff4d1953f00722fee627b1b73c41d1af2ee3b5168993886dfa9bd65529d616d5
5
5
  SHA512:
6
- metadata.gz: a6b4e23f4c7cbb1d4751e7aef27fabaa1e5ccb337276788489b08c031b0f3a9b9b3ac318328215579d78b2477ac95148af1dfa350a0635cb98959bb28c76b296
7
- data.tar.gz: 8d836cbd60a8798408752818ec84b08165d27f9196645129683a1c15abd43288976575ad0e57c737c93e9169b9995a065c3d1bfea2a962da4b39f62d25f53a07
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
- if @owner.collection_observer
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('_stub_')
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, [ association_name ])
78
+ ActiveRecord::Associations::Preloader.new.preload(rs, [association_name])
78
79
  end
79
80
  end
80
81
 
@@ -1,8 +1,7 @@
1
- require 'active_record/associations/preloader'
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.to_s}")
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
@@ -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.7.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-05-05 00:00:00.000000000 Z
11
+ date: 2023-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord