predictive_load 0.7.0 → 0.8.0

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