bulletmark_repairer 0.1.6 → 0.1.7

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: 5eabc96bcf1961f834090322c3b9a62173139f806513c5ec99d452f18a661c71
4
- data.tar.gz: b55ab6f5adade690b6cb34ef92fc752bdfa697a3b8d7779d54150aaf4e1083bb
3
+ metadata.gz: 39af4a769d591a28ab628d174b6224984bf3f87460e2ee4d782a5a16a3c04d87
4
+ data.tar.gz: 2b163d905540357085ef7d9436abc189d4cde357f95a5e7650b538b195bb95bc
5
5
  SHA512:
6
- metadata.gz: 714da7b74b7015194f965c83c1260d31b8a4f75b4f7a24b3038eac6c81ced4aa8a77a79abbe5a684a347093fbb00d0e9f1b05682729b96afe64ed2a83b110534
7
- data.tar.gz: e48064688caaa2003d30192bfd2a76ff677fdcb4b4bb02bb8b828fece22b9fd5dab69a47aef2fb836a69e9f5b16db10d2c1faefcb9e249ba5b8a3df671310c03
6
+ metadata.gz: c5a8331d8787887ff29e736d353978d6d86000d14713df1576f131c9a661aaf166421aa963bd4752a5d3d55f2ee642fdbfeac3e551defe126450cd6d4c5d1aa3
7
+ data.tar.gz: 9d9a6aa80d778ec59b86d4eea3123e732ab146b01dfaf46ef78a7d66ebb0e1923558a903eef36afd895fb97404b4650585ad7ee3b5cbc34e16b947d790952f7b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [0.1.7] - 2023-11-21
2
+
3
+ - Validate detected instance_variable is an ActiveRecord::Relation [ed650ae](https://github.com/makicamel/bulletmark_repairer/commit/ed650ae41b4389774cb1135031f077e953d2c5db) [86497bd](https://github.com/makicamel/bulletmark_repairer/commit/86497bd3cbb6daf1672cd18210e5842d0ecc084f)
4
+ - Add includes after methods returning AR on autocorrect [767e5e1](https://github.com/makicamel/bulletmark_repairer/commit/767e5e1389f84daa6efce4463a4979a100c16640)
5
+
1
6
  ## [0.1.6] - 2023-11-09
2
7
 
3
8
  Fix a bug when N+1 is caused not in the request (e.g. Sidekiq) [51f051e](https://github.com/makicamel/bulletmark_repairer/commit/51f051e608b84b7da96ac879a324ed438c14eeeb)
@@ -33,6 +33,14 @@ class ControllerCorrector < Parser::TreeRewriter
33
33
  @patched ||= false
34
34
  end
35
35
 
36
+ def includes_token
37
+ @includes_token ||= ".includes(#{associations})"
38
+ end
39
+
40
+ def inserted?(parent_node)
41
+ parent_node.location.expression.source.include?(includes_token)
42
+ end
43
+
36
44
  def target_nodes
37
45
  @target_nodes ||= {}
38
46
  end
@@ -47,10 +55,8 @@ class ControllerCorrector < Parser::TreeRewriter
47
55
 
48
56
  type, identifier = node.to_sexp_array.take(2)
49
57
  if type == :ivasgn && identifier == instance_variable_name
50
- inserted = ".includes(#{associations})"
51
- unless node.location.expression.source.include?(inserted)
52
- insert_after node.children.last.location.expression, inserted
53
- @patched = true
58
+ node.children.each do |child_node|
59
+ execute_insert_includes(node: child_node, parent_node: node)
54
60
  end
55
61
  else
56
62
  node
@@ -67,6 +73,22 @@ class ControllerCorrector < Parser::TreeRewriter
67
73
  end
68
74
  end
69
75
 
76
+ def execute_insert_includes(node:, parent_node:)
77
+ return unless node.respond_to?(:children)
78
+ return if patched?
79
+
80
+ node.children.each do |child_node|
81
+ if child_node.is_a?(Symbol)
82
+ if BulletmarkRepairer::Thread.correctable_method?(child_node) && !inserted?(parent_node)
83
+ insert_after node.location.expression, includes_token
84
+ @patched = true
85
+ end
86
+ else
87
+ execute_insert_includes(node: child_node, parent_node: node)
88
+ end
89
+ end
90
+ end
91
+
70
92
  def action
71
93
  :__EMBEDDED_ACTION__
72
94
  end
@@ -82,7 +82,6 @@ module BulletmarkRepairer
82
82
  end
83
83
  view_file, view_yield_index = @stacktraces[view_file_index].scan(%r{\A(/[./\w]+):(\d+):in `[\w]+'\z}).flatten
84
84
  view_yield_index = view_yield_index.to_i
85
- # TODO: Compile views
86
85
  File.open(view_file) do |f|
87
86
  lines = f.readlines
88
87
  loop do
@@ -90,7 +89,8 @@ module BulletmarkRepairer
90
89
 
91
90
  view_yield_index -= 1
92
91
  line = lines[view_yield_index]
93
- @instance_variable_name = line&.scan(/\b?(@[\w]+)\b?/)&.flatten&.last
92
+ token = line&.scan(/\b?(@[\w]+)\b?/)&.flatten&.last
93
+ @instance_variable_name = token if BulletmarkRepairer::Thread.instance_variable_name?(token)
94
94
  end
95
95
  end
96
96
  @index = @instance_variable_name ? "#{view_file}:#{view_yield_index}" : nil
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BulletmarkRepairer
4
+ module ActionView
5
+ module Base
6
+ def initialize(*args)
7
+ super(*args)
8
+ @_assigns.each do |ivname, value|
9
+ BulletmarkRepairer::Thread.memorize_instance_variable_name(name: ivname, value: value)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -4,17 +4,17 @@ module BulletmarkRepairer
4
4
  module ActiveRecord
5
5
  module QueryMethod
6
6
  def includes(*args)
7
- BulletmarkRepairer::Thread.add(name: model.name, method_type: :includes, args: args)
7
+ BulletmarkRepairer::Thread.memorize_associations(name: model.name, method_type: :includes, args: args)
8
8
  super(args)
9
9
  end
10
10
 
11
11
  def eager_load(*args)
12
- BulletmarkRepairer::Thread.add(name: model.name, method_type: :eager_load, args: args)
12
+ BulletmarkRepairer::Thread.memorize_associations(name: model.name, method_type: :eager_load, args: args)
13
13
  super(args)
14
14
  end
15
15
 
16
16
  def preload(*args)
17
- BulletmarkRepairer::Thread.add(name: model.name, method_type: :preload, args: args)
17
+ BulletmarkRepairer::Thread.memorize_associations(name: model.name, method_type: :preload, args: args)
18
18
  super(args)
19
19
  end
20
20
  end
@@ -7,15 +7,22 @@ module BulletmarkRepairer
7
7
  end
8
8
 
9
9
  def call(env)
10
+ trace_point = TracePoint.trace(:return) do |tp|
11
+ BulletmarkRepairer::Thread.memorize_methods(
12
+ method_name: tp.method_id,
13
+ value: tp.return_value
14
+ )
15
+ end
10
16
  @app.call(env)
11
17
  ensure
18
+ trace_point.disable
12
19
  begin
13
20
  if ::Thread.current[:bullet_notification_collector].notifications_present?
14
21
  BulletmarkRepairer::Patcher.execute(
15
22
  notifications: ::Thread.current[:bullet_notification_collector],
16
23
  controller: env['action_dispatch.request.parameters']['controller'],
17
24
  action: env['action_dispatch.request.parameters']['action'],
18
- loaded_associations: BulletmarkRepairer::Thread.current
25
+ loaded_associations: BulletmarkRepairer::Thread.current(:loaded_associations)
19
26
  )
20
27
  end
21
28
  rescue StandardError => e
@@ -14,5 +14,9 @@ module BulletmarkRepairer
14
14
  require 'bulletmark_repairer/monkey_patches/active_record/query_method'
15
15
  ::ActiveRecord::Relation.prepend(BulletmarkRepairer::ActiveRecord::QueryMethod)
16
16
  end
17
+ ActiveSupport.on_load(:action_view) do
18
+ require 'bulletmark_repairer/monkey_patches/action_view/base'
19
+ ::ActionView::Base.prepend(BulletmarkRepairer::ActionView::Base)
20
+ end
17
21
  end
18
22
  end
@@ -13,6 +13,14 @@ class RetryCorrector < Parser::TreeRewriter
13
13
  @patched ||= false
14
14
  end
15
15
 
16
+ def includes_token
17
+ @includes_token ||= ".includes(#{associations})"
18
+ end
19
+
20
+ def inserted?(parent_node)
21
+ parent_node.location.expression.source.include?(includes_token)
22
+ end
23
+
16
24
  def insert_includes(node:)
17
25
  return if patched?
18
26
  return if !node.respond_to?(:children) || node.children.empty?
@@ -21,11 +29,25 @@ class RetryCorrector < Parser::TreeRewriter
21
29
  if node.type == :begin
22
30
  node.children.each { |child_node| insert_includes(node: child_node) }
23
31
  else
24
- inserted = ".includes(#{associations})"
25
- return if node.location.expression.source.include?(inserted)
32
+ node.children.each do |child_node|
33
+ execute_insert_includes(node: child_node, parent_node: node)
34
+ end
35
+ end
36
+ end
37
+
38
+ def execute_insert_includes(node:, parent_node:)
39
+ return unless node.respond_to?(:children)
40
+ return if patched?
26
41
 
27
- insert_after node.location.expression, ".includes(#{associations})"
28
- @patched = true
42
+ node.children.each do |child_node|
43
+ if child_node.is_a?(Symbol)
44
+ if BulletmarkRepairer::Thread.correctable_method?(child_node) && !inserted?(parent_node)
45
+ insert_after node.location.expression, includes_token
46
+ @patched = true
47
+ end
48
+ else
49
+ execute_insert_includes(node: child_node, parent_node: node)
50
+ end
29
51
  end
30
52
  end
31
53
 
@@ -5,24 +5,52 @@ require 'rails'
5
5
  module BulletmarkRepairer
6
6
  class Thread
7
7
  class << self
8
- def current
9
- touch
8
+ def current(key)
9
+ touch(key)
10
10
  end
11
11
 
12
- def add(name:, method_type:, args:)
13
- touch
14
- ::Thread.current[:bulletmark_repaier_loaded_associations][name][method_type].add(args)
12
+ def memorize_associations(name:, method_type:, args:)
13
+ current(:loaded_associations)[name][method_type].add(args)
14
+ end
15
+
16
+ def memorize_instance_variable_name(name:, value:)
17
+ return unless value.is_a?(::ActiveRecord::Relation)
18
+
19
+ current(:loaded_instance_variables).add("@#{name}")
20
+ end
21
+
22
+ def instance_variable_name?(name)
23
+ current(:loaded_instance_variables).include?(name)
24
+ end
25
+
26
+ # TODO: Memorize methods with class
27
+ def memorize_methods(method_name:, value:)
28
+ return unless value.is_a?(::ActiveRecord::Associations::CollectionProxy) || value.is_a?(::ActiveRecord::Relation)
29
+
30
+ current(:loaded_methods).add(method_name)
31
+ end
32
+
33
+ def correctable_method?(method_name)
34
+ current(:loaded_methods).include?(method_name)
15
35
  end
16
36
 
17
37
  def clear
18
- ::Thread.current[:bulletmark_repaier_loaded_associations] = nil
38
+ ::Thread.current[:bulletmark_repairer] = nil
19
39
  end
20
40
 
21
41
  private
22
42
 
23
- def touch
24
- ::Thread.current[:bulletmark_repaier_loaded_associations] ||= Hash.new do |hash, key|
25
- hash[key] = { includes: Set.new, eager_load: Set.new, preload: Set.new }
43
+ def touch(key)
44
+ ::Thread.current[:bulletmark_repairer] ||= {}
45
+ case key
46
+ when :loaded_associations
47
+ ::Thread.current[:bulletmark_repairer][:loaded_associations] ||= Hash.new do |hash, key|
48
+ hash[key] = { includes: Set.new, eager_load: Set.new, preload: Set.new }
49
+ end
50
+ when :loaded_instance_variables
51
+ ::Thread.current[:bulletmark_repairer][:loaded_instance_variables] ||= Set.new
52
+ when :loaded_methods
53
+ ::Thread.current[:bulletmark_repairer][:loaded_methods] ||= Set.new
26
54
  end
27
55
  end
28
56
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BulletmarkRepairer
4
- VERSION = '0.1.6'
4
+ VERSION = '0.1.7'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bulletmark_repairer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - makicamel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-08 00:00:00.000000000 Z
11
+ date: 2023-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -90,6 +90,7 @@ files:
90
90
  - lib/bulletmark_repairer/corrector_builder.rb
91
91
  - lib/bulletmark_repairer/loaded_associations.rb
92
92
  - lib/bulletmark_repairer/markers.rb
93
+ - lib/bulletmark_repairer/monkey_patches/action_view/base.rb
93
94
  - lib/bulletmark_repairer/monkey_patches/active_record/query_method.rb
94
95
  - lib/bulletmark_repairer/patcher.rb
95
96
  - lib/bulletmark_repairer/rack.rb
@@ -121,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
122
  - !ruby/object:Gem::Version
122
123
  version: '0'
123
124
  requirements: []
124
- rubygems_version: 3.1.4
125
+ rubygems_version: 3.4.10
125
126
  signing_key:
126
127
  specification_version: 4
127
128
  summary: Auto corrector for N+1 queries detected at runtime with Bullet