bulletmark_repairer 0.1.3 → 0.1.5

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