bulletmark_repairer 0.1.3 → 0.1.5

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: a7110364b32065ccdce57c9220481a6c3ab36265a1fbc1fcb0d40a5d6a92ceeb
4
- data.tar.gz: e69d9ae29d6a2f0e013e8d974ac5634e98750c31cf57eacbbbee1ed48778f5ce
3
+ metadata.gz: 3d44edbc5a6deb0c95e4e1ed79c2f2b1c7b283ff54e3f00d33bbf9a8b70f698e
4
+ data.tar.gz: 5d3ba75ca53cd5e721f971894fc2f1001e1fd391f4daf44a53184ef861cb532b
5
5
  SHA512:
6
- metadata.gz: 4eb6a1e8d2274100f04d72f544c0dce8588b758216bfbe08968947fc535d7fe3e0fba8f7d722600737c18e52cbe468e0ed2482e48e9d9372ca18c3f66e1c05fa
7
- data.tar.gz: fe6a169678171236abebdff07cf2adfef7af32066c07656ad327fbb33d692a0b9171c5b418932c653ae4a8c0eb95be823c8d29d2f17423f7fe7d8be663fd5aa2
6
+ metadata.gz: 617f5750639fe286efd6fa7d0d7bb40d0be0d258ef94600717ad98f2f46c6bfad966ea651bd5b0113f3ab9b0ff4d5749a5316bc7cca7c83c7dabc781d890ac20
7
+ data.tar.gz: e6698f869f1c1f50b08332c6ec3134cfd32716325c5b2607e4a7247cf5bf807ed5f39f633822288d641246167b82e1f6b6e969d24b7cde322764204823fbea24
data/.rubocop.yml CHANGED
@@ -31,3 +31,6 @@ Metrics/BlockLength:
31
31
 
32
32
  Style/Documentation:
33
33
  Enabled: false
34
+
35
+ Style/MultilineBlockChain:
36
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.1.5] - 2023-10-27
2
+
3
+ Be able to patch for nested associations require `includes` though child associations are already included [d1b7445](https://github.com/makicamel/bulletmark_repairer/commit/d1b7445556c20bc037beb6a013ac70531426a7ea)
4
+
5
+ ## [0.1.4] - 2023-10-22
6
+
7
+ - Patch files other than controllers [218566d](https://github.com/makicamel/bulletmark_repairer/commit/218566d1531751f204941c3dcff7f095a056d39f)
8
+ - Patch unassigned queries [159573a](https://github.com/makicamel/bulletmark_repairer/commit/159573ada036ee3ee39428b1e59066934b676c02)
9
+ - Apply patches starting from the top of the method [f8d0058](https://github.com/makicamel/bulletmark_repairer/commit/f8d00582a5b3b084c0a35a54726396a2a063f8dd)
10
+ - Log also when the target file is in the skip list [a23a3bc](https://github.com/makicamel/bulletmark_repairer/commit/a23a3bc0edf1e94d3aa6ea95449c9570b9322d65)
11
+
1
12
  ## [0.1.3] - 2023-10-18
2
13
 
3
14
  - Fix a redundant auto-correct for multiple tests with n+1 queries when running RSpec [#6](https://github.com/makicamel/bulletmark_repairer/pull/6) ([@ydah])
data/README.md CHANGED
@@ -47,14 +47,12 @@ For example, the following cases are not supported as known cases currently:
47
47
 
48
48
  ```ruby
49
49
  def index
50
- # N+1 is caused but not assigned
51
- Play.all_actors_name
52
- end
53
-
54
- def index
55
- # N+1 occurs when assigning a local variable
56
- plays = Play.all.as_json
57
- @play = plays.last
50
+ # Multiple nested associations require `includes` though child associations are already included
51
+ @plays = Play.includes(:actors)
52
+ # expected correct
53
+ @plays = Play.includes(:actors).includes({:actors=>{:company=>[:offices]}})
54
+ # actual correct
55
+ @plays = Play.includes(:actors).includes({:actors=>[:company]})
58
56
  end
59
57
  ```
60
58
 
@@ -10,7 +10,11 @@ module BulletmarkRepairer
10
10
  if associations[marker.index]
11
11
  associations[marker.index].add(marker)
12
12
  else
13
- associations[marker.index] = Associations.new(marker, @application_associations)
13
+ associations[marker.index] = Associations.new(
14
+ marker,
15
+ @application_associations,
16
+ @loaded_associations
17
+ )
14
18
  end
15
19
  end
16
20
 
@@ -20,8 +24,9 @@ module BulletmarkRepairer
20
24
 
21
25
  private
22
26
 
23
- def initialize
27
+ def initialize(loaded_associations)
24
28
  @application_associations = BulletmarkRepairer::ApplicationAssociations.new
29
+ @loaded_associations = BulletmarkRepairer::LoadedAssociations.new(loaded_associations)
25
30
  end
26
31
  end
27
32
 
@@ -44,10 +49,12 @@ module BulletmarkRepairer
44
49
 
45
50
  private
46
51
 
47
- def initialize(marker, application_associations)
52
+ def initialize(marker, application_associations, loaded_associations)
48
53
  @marker = marker
49
- @associations = { base: marker.associations }
50
54
  @application_associations = application_associations
55
+ @loaded_associations = loaded_associations
56
+ key = @loaded_associations.key(marker.base_class)
57
+ @associations = { base: key ? { key => marker.associations } : marker.associations }
51
58
  end
52
59
 
53
60
  # @return [Hash, nil]
@@ -55,7 +55,6 @@ class ControllerCorrector < Parser::TreeRewriter
55
55
  else
56
56
  node
57
57
  .children
58
- .reverse
59
58
  .each do |child_node|
60
59
  child_type, _, child_identifier = child_node.try(:to_sexp_array)
61
60
  if child_type == :send && target_nodes.key?(child_identifier)
@@ -16,17 +16,30 @@ module BulletmarkRepairer
16
16
  end
17
17
 
18
18
  def execute
19
- corrector_name = '/controller_corrector.rb'
20
- File.open("#{@dir}#{corrector_name}", 'w') do |f|
21
- corrector = Pathname.new(__FILE__).sub('/corrector_builder.rb', corrector_name)
22
- src = File.read(corrector)
23
- src
24
- .sub!(ASSOCIATIONS, @associations[:base].to_s)
25
- .sub!(ACTION, @action)
26
- .sub!(INSTANCE_VARIABLE_NAME, @instance_variable_name)
27
- f.puts src
28
- f
29
- end.path
19
+ if @marker.retry
20
+ corrector_name = '/retry_corrector.rb'
21
+ File.open("#{@dir}#{corrector_name}", 'w') do |f|
22
+ corrector = Pathname.new(__FILE__).sub('/corrector_builder.rb', corrector_name)
23
+ src = File.read(corrector)
24
+ src
25
+ .sub!(ASSOCIATIONS, @associations[:base].to_s)
26
+ .sub!(LINE_NO, @marker.line_no)
27
+ f.puts src
28
+ f
29
+ end.path
30
+ else
31
+ corrector_name = '/controller_corrector.rb'
32
+ File.open("#{@dir}#{corrector_name}", 'w') do |f|
33
+ corrector = Pathname.new(__FILE__).sub('/corrector_builder.rb', corrector_name)
34
+ src = File.read(corrector)
35
+ src
36
+ .sub!(ASSOCIATIONS, @associations[:base].to_s)
37
+ .sub!(ACTION, @action)
38
+ .sub!(INSTANCE_VARIABLE_NAME, @instance_variable_name)
39
+ f.puts src
40
+ f
41
+ end.path
42
+ end
30
43
  end
31
44
  end
32
45
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BulletmarkRepairer
4
+ class LoadedAssociations
5
+ attr_reader :associations
6
+
7
+ def key(target_klass_name)
8
+ key = target_klass_name.underscore
9
+
10
+ result = []
11
+ @associations.each do |_base_klass_name, all_associations|
12
+ all_associations.each do |_key, associations|
13
+ # TODO: reccurent check
14
+ associations.each do |values|
15
+ values.flatten.each do |value|
16
+ result.append search_key(key, value)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ result = result.flatten.compact.uniq.presence
22
+ build_keys(result)
23
+ end
24
+
25
+ private
26
+
27
+ def search_key(key, value)
28
+ case value
29
+ when Hash
30
+ search_key(key, value.keys) || search_key(key, value.values)
31
+ when Array
32
+ value.map { |v| search_key(key, v) }
33
+ else
34
+ if key.pluralize.to_sym == value
35
+ key.pluralize.to_sym
36
+ elsif key.singularize.to_sym == value
37
+ key.singularize.to_sym
38
+ end
39
+ end
40
+ end
41
+
42
+ def build_keys(keys)
43
+ return unless keys
44
+
45
+ if keys.size == 1
46
+ keys.first
47
+ else
48
+ { keys.first => keys.last }
49
+ end
50
+ end
51
+
52
+ def initialize(original_associations)
53
+ @associations = Hash.new { |h, k| h[k] = {} }
54
+ original_associations.each do |base_class, all_associations|
55
+ all_associations.each do |key, associations|
56
+ @associations[base_class][key] = associations.to_a
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -28,7 +28,7 @@ module BulletmarkRepairer
28
28
  end
29
29
 
30
30
  class Marker
31
- attr_reader :base_class, :associations, :action, :file_name, :instance_variable_name, :index
31
+ attr_reader :base_class, :associations, :action, :file_name, :instance_variable_name, :index, :retry, :line_no
32
32
 
33
33
  def initialize(notification, controller:, action:)
34
34
  @base_class = notification.instance_variable_get(:@base_class)
@@ -36,6 +36,8 @@ module BulletmarkRepairer
36
36
  @associations = notification.instance_variable_get(:@associations)
37
37
  @controller = controller
38
38
  @action = action
39
+ @retry = false
40
+ @line_no = nil
39
41
  set_up
40
42
  end
41
43
 
@@ -56,6 +58,7 @@ module BulletmarkRepairer
56
58
 
57
59
  def log_patchable_files_not_be_found
58
60
  return if index
61
+ return if BulletmarkRepairer.config.skip_file_list.exclude?(file_name.remove("#{Rails.root}/"))
59
62
 
60
63
  BulletmarkRepairer.config.logger.info <<~LOG
61
64
  Repairer couldn't patch
@@ -93,7 +96,6 @@ module BulletmarkRepairer
93
96
  @index = @instance_variable_name ? "#{view_file}:#{view_yield_index}" : nil
94
97
  else
95
98
  # TODO: Ignore controllers list
96
- # TODO: Allow directories list
97
99
  controller_file_index = @stacktraces.index { |stacktrace| stacktrace.match?(%r{\A(#{Rails.root}/app/controllers[./\w]+):(\d+):in `[()\w\s]+'\z}) }
98
100
  @file_name, controller_yield_index = @stacktraces[controller_file_index].scan(%r{\A(#{Rails.root}/app/controllers[./\w]+):(\d+):in `[()\w\s]+'\z}).flatten
99
101
  controller_yield_index = controller_yield_index.to_i
@@ -104,13 +106,25 @@ module BulletmarkRepairer
104
106
 
105
107
  controller_yield_index -= 1
106
108
  line = lines[controller_yield_index]
107
- # TODO: patch to local variables
108
109
  @instance_variable_name = line&.scan(/\b?(@[\w]+)\b?/)&.flatten&.last
109
110
  break if line.match?(/^\s+def [()\w\s=]+$/)
110
111
  end
111
112
  end
112
113
  @index = @instance_variable_name ? "#{@file_name}:#{controller_yield_index}" : nil
113
114
  end
115
+
116
+ return if @index
117
+
118
+ # TODO: Ignore files list
119
+ # TODO: Allow model files list
120
+ @retry = @stacktraces.any? do |stacktrace|
121
+ !stacktrace.match?(%r{\A(#{Rails.root}/app/models[./\w]+):(\d+):in `[()\w\s=!?]+'\z}) &&
122
+ stacktrace =~ %r{\A(#{Rails.root}/app[./\w]+):(\d+):in `[()\w\s=!?]+'\z}
123
+ end.tap do
124
+ @file_name = Regexp.last_match(1)
125
+ @line_no = Regexp.last_match(2)
126
+ end
127
+ @index = @retry ? "#{@file_name}:#{@line_no}" : nil
114
128
  end
115
129
  end
116
130
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BulletmarkRepairer
4
+ module ActiveRecord
5
+ module QueryMethod
6
+ def includes(*args)
7
+ Thread.current[:bulletmark_repaier_loaded_associations][model.name][:includes].add(args)
8
+ super(args)
9
+ end
10
+
11
+ def eager_load(*args)
12
+ Thread.current[:bulletmark_repaier_loaded_associations][model.name][:eager_load].add(args)
13
+ super(args)
14
+ end
15
+
16
+ def preload(*args)
17
+ Thread.current[:bulletmark_repaier_loaded_associations][model.name][:preload].add(args)
18
+ super(args)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -6,8 +6,8 @@ require 'parser/runner/ruby_rewrite'
6
6
 
7
7
  module BulletmarkRepairer
8
8
  class Patcher
9
- def self.execute(notifications:, controller:, action:)
10
- new(notifications: notifications, controller: controller, action: action).execute
9
+ def self.execute(notifications:, controller:, action:, loaded_associations:)
10
+ new(notifications: notifications, controller: controller, action: action, loaded_associations: loaded_associations).execute
11
11
  end
12
12
 
13
13
  def execute
@@ -24,9 +24,9 @@ module BulletmarkRepairer
24
24
 
25
25
  private
26
26
 
27
- def initialize(notifications:, controller:, action:)
27
+ def initialize(notifications:, controller:, action:, loaded_associations:)
28
28
  @markers = Markers.new(notifications, controller: controller, action: action)
29
- @associations_builder = BulletmarkRepairer::AssociationsBuilder.new
29
+ @associations_builder = BulletmarkRepairer::AssociationsBuilder.new(loaded_associations)
30
30
  end
31
31
  end
32
32
  end
@@ -7,6 +7,9 @@ module BulletmarkRepairer
7
7
  end
8
8
 
9
9
  def call(env)
10
+ Thread.current[:bulletmark_repaier_loaded_associations] = Hash.new do |hash, key|
11
+ hash[key] = { includes: Set.new, eager_load: Set.new, preload: Set.new }
12
+ end
10
13
  @app.call(env)
11
14
  ensure
12
15
  begin
@@ -14,12 +17,14 @@ module BulletmarkRepairer
14
17
  BulletmarkRepairer::Patcher.execute(
15
18
  notifications: Thread.current[:bullet_notification_collector],
16
19
  controller: env['action_dispatch.request.parameters']['controller'],
17
- action: env['action_dispatch.request.parameters']['action']
20
+ action: env['action_dispatch.request.parameters']['action'],
21
+ loaded_associations: Thread.current[:bulletmark_repaier_loaded_associations]
18
22
  )
19
23
  end
20
24
  rescue StandardError => e
21
25
  raise e if BulletmarkRepairer.config.debug?
22
26
  end
27
+ Thread.current[:bulletmark_repaier_loaded_associations].clear
23
28
  end
24
29
  end
25
30
  end
@@ -9,5 +9,10 @@ module BulletmarkRepairer
9
9
  require 'bulletmark_repairer/rack'
10
10
  app.middleware.insert_after Bullet::Rack, BulletmarkRepairer::Rack
11
11
  end
12
+
13
+ ActiveSupport.on_load(:active_record) do
14
+ require 'bulletmark_repairer/monkey_patches/active_record/query_method'
15
+ ::ActiveRecord::Relation.prepend(BulletmarkRepairer::ActiveRecord::QueryMethod)
16
+ end
12
17
  end
13
18
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RetryCorrector < Parser::TreeRewriter
4
+ def on_def(node)
5
+ return if patched?
6
+
7
+ node.children.each { |child_node| insert_includes(node: child_node) }
8
+ end
9
+
10
+ private
11
+
12
+ def patched?
13
+ @patched ||= false
14
+ end
15
+
16
+ def insert_includes(node:)
17
+ return if patched?
18
+ return if !node.respond_to?(:children) || node.children.empty?
19
+ return unless node.location.expression.line <= line_no && line_no <= node.location.expression.last_line
20
+
21
+ if node.type == :begin
22
+ node.children.each { |child_node| insert_includes(node: child_node) }
23
+ else
24
+ inserted = ".includes(#{associations})"
25
+ return if node.location.expression.source.include?(inserted)
26
+
27
+ insert_after node.location.expression, ".includes(#{associations})"
28
+ @patched = true
29
+ end
30
+ end
31
+
32
+ def line_no
33
+ __EMBEDDED_LINE_NO__
34
+ end
35
+
36
+ def associations
37
+ '__EMBEDDED_ASSOCIATIONS__'
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BulletmarkRepairer
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.5'
5
5
  end
@@ -6,6 +6,7 @@ require 'bulletmark_repairer/application_associations'
6
6
  require 'bulletmark_repairer/associations_builder'
7
7
  require 'bulletmark_repairer/configuration'
8
8
  require 'bulletmark_repairer/corrector_builder'
9
+ require 'bulletmark_repairer/loaded_associations'
9
10
  require 'bulletmark_repairer/markers'
10
11
  require 'bulletmark_repairer/patcher'
11
12
 
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.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - makicamel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-17 00:00:00.000000000 Z
11
+ date: 2023-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -88,10 +88,13 @@ files:
88
88
  - lib/bulletmark_repairer/configuration.rb
89
89
  - lib/bulletmark_repairer/controller_corrector.rb
90
90
  - lib/bulletmark_repairer/corrector_builder.rb
91
+ - lib/bulletmark_repairer/loaded_associations.rb
91
92
  - lib/bulletmark_repairer/markers.rb
93
+ - lib/bulletmark_repairer/monkey_patches/active_record/query_method.rb
92
94
  - lib/bulletmark_repairer/patcher.rb
93
95
  - lib/bulletmark_repairer/rack.rb
94
96
  - lib/bulletmark_repairer/railtie.rb
97
+ - lib/bulletmark_repairer/retry_corrector.rb
95
98
  - lib/bulletmark_repairer/version.rb
96
99
  - sig/bulletmark_repairer.rbs
97
100
  homepage: https://github.com/makicamel/bulletmark_repairer