callback_hell 0.2.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 +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +174 -0
- data/file_id.diz +19 -0
- data/lib/callback_hell/analyzers/callback_analyzer.rb +158 -0
- data/lib/callback_hell/analyzers/validation_analyzer.rb +82 -0
- data/lib/callback_hell/callback.rb +56 -0
- data/lib/callback_hell/collector.rb +96 -0
- data/lib/callback_hell/railtie.rb +7 -0
- data/lib/callback_hell/reports/base.rb +18 -0
- data/lib/callback_hell/reports/callbacks/github.rb +29 -0
- data/lib/callback_hell/reports/callbacks/line.rb +19 -0
- data/lib/callback_hell/reports/callbacks/table.rb +29 -0
- data/lib/callback_hell/reports/github_base.rb +67 -0
- data/lib/callback_hell/reports/line_base.rb +42 -0
- data/lib/callback_hell/reports/table_base.rb +92 -0
- data/lib/callback_hell/reports/validations/github.rb +19 -0
- data/lib/callback_hell/reports/validations/line.rb +24 -0
- data/lib/callback_hell/reports/validations/table.rb +19 -0
- data/lib/callback_hell/runner.rb +65 -0
- data/lib/callback_hell/stats.rb +92 -0
- data/lib/callback_hell/version.rb +5 -0
- data/lib/callback_hell.rb +28 -0
- data/lib/tasks/callback_hell.rake +145 -0
- metadata +184 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
module Reports
|
|
5
|
+
module Callbacks
|
|
6
|
+
class Line < LineBase
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def report_title
|
|
10
|
+
"Callback Hell callbacks report:"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def format_callback_name(callback)
|
|
14
|
+
"#{callback.kind}_#{callback.callback_group}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
module Reports
|
|
5
|
+
module Callbacks
|
|
6
|
+
class Table < TableBase
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def report_title
|
|
10
|
+
"Callback Hell callbacks report"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def format_group_name(callback)
|
|
14
|
+
"#{timing_symbol(callback.kind)}/#{callback.callback_group}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def timing_symbol(timing)
|
|
18
|
+
case timing
|
|
19
|
+
when :before, "before" then "⇥"
|
|
20
|
+
when :after, "after" then "↦"
|
|
21
|
+
when :around, "around" then "↔"
|
|
22
|
+
else
|
|
23
|
+
" "
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
module Reports
|
|
5
|
+
class GithubBase < Base
|
|
6
|
+
def generate
|
|
7
|
+
output = ["::group::#{report_title}"]
|
|
8
|
+
|
|
9
|
+
@stats.by_model.each_with_index do |(model_name, _), index|
|
|
10
|
+
output << "" if index > 0
|
|
11
|
+
output << "::group::#{model_name}"
|
|
12
|
+
|
|
13
|
+
# Add total row first
|
|
14
|
+
total_stats = @stats.stats_for(model_name)
|
|
15
|
+
output << format_group(
|
|
16
|
+
"all",
|
|
17
|
+
total_stats[:total],
|
|
18
|
+
total_stats[:own],
|
|
19
|
+
total_stats[:inherited],
|
|
20
|
+
total_stats[:rails],
|
|
21
|
+
total_stats[:association_generated],
|
|
22
|
+
total_stats[:attribute_generated],
|
|
23
|
+
total_stats[:gems],
|
|
24
|
+
total_stats[:conditional]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Group and sort callbacks
|
|
28
|
+
grouped_callbacks = @stats.by_model[model_name].group_by { |cb| format_group_name(cb) }
|
|
29
|
+
|
|
30
|
+
grouped_callbacks.keys.sort.each do |group_name|
|
|
31
|
+
group_callbacks = grouped_callbacks[group_name]
|
|
32
|
+
output << format_group(
|
|
33
|
+
group_name,
|
|
34
|
+
group_callbacks.size,
|
|
35
|
+
group_callbacks.count { |cb| cb.origin == :own },
|
|
36
|
+
group_callbacks.count(&:inherited),
|
|
37
|
+
group_callbacks.count { |cb| cb.origin == :rails },
|
|
38
|
+
group_callbacks.count(&:association_generated),
|
|
39
|
+
group_callbacks.count(&:attribute_generated),
|
|
40
|
+
group_callbacks.count { |cb| cb.origin == :gems },
|
|
41
|
+
group_callbacks.count(&:conditional)
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
output << "::endgroup::"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
output << "::endgroup::"
|
|
49
|
+
output.join("\n")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def report_title
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def format_group(name, total, own, inherited, rails, association_generated, attribute_generated, gems, conditional)
|
|
59
|
+
"::debug::kind=#{name} total=#{total} own=#{own} inherited=#{inherited} rails=#{rails} association_generated=#{association_generated} attribute_generated=#{attribute_generated} gems=#{gems} conditional=#{conditional}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def format_group_name(callback)
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
module Reports
|
|
5
|
+
class LineBase < Base
|
|
6
|
+
def generate
|
|
7
|
+
output = [report_title]
|
|
8
|
+
|
|
9
|
+
@stats.by_model.each do |model_name, model_callbacks|
|
|
10
|
+
output << "\n#{model_name}:"
|
|
11
|
+
model_callbacks.sort_by(&:kind).each do |callback|
|
|
12
|
+
output << format_callback(callback)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
output.join("\n")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def report_title
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def format_callback(callback)
|
|
26
|
+
[
|
|
27
|
+
" #{format_callback_name(callback)}",
|
|
28
|
+
"method_name: #{callback.human_method_name}",
|
|
29
|
+
"origin: #{callback.origin}",
|
|
30
|
+
"association_generated: #{callback.association_generated ? "yes" : "no"}",
|
|
31
|
+
"attribute_generated: #{callback.attribute_generated ? "yes" : "no"}",
|
|
32
|
+
"inherited: #{callback.inherited ? "inherited" : "own"}",
|
|
33
|
+
"conditional: #{callback.conditional ? "yes" : "no"}"
|
|
34
|
+
].compact.join(", ")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def format_callback_name(callback)
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "table_tennis"
|
|
4
|
+
|
|
5
|
+
module CallbackHell
|
|
6
|
+
module Reports
|
|
7
|
+
class TableBase < Base
|
|
8
|
+
def generate
|
|
9
|
+
headers = ["Model", "Kind", "Total", "Own", "Inherited"]
|
|
10
|
+
headers << "Rails" if stats.rails?
|
|
11
|
+
headers << "Associations" if stats.associations?
|
|
12
|
+
headers << "Attributes" if stats.attributes?
|
|
13
|
+
headers.concat(["Gems", "Conditional"])
|
|
14
|
+
|
|
15
|
+
headers_mapping = headers.index_by { |h| h.parameterize.to_sym }
|
|
16
|
+
|
|
17
|
+
table = TableTennis.new(generate_rows(headers_mapping.keys)) do |t|
|
|
18
|
+
t.title = report_title
|
|
19
|
+
t.headers = headers_mapping
|
|
20
|
+
t.placeholder = ""
|
|
21
|
+
t.color_scales = {total: :gr}
|
|
22
|
+
|
|
23
|
+
# Don't shrink table if we run in a test environment,
|
|
24
|
+
# we need full labels and values to test
|
|
25
|
+
t.layout = false if defined?(Rails) && Rails.env.test?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
table.to_s
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def report_title
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def generate_rows(headers)
|
|
38
|
+
rows = []
|
|
39
|
+
@stats.by_model.each_with_index do |(model_name, _), index|
|
|
40
|
+
# TODO: not supported by table_tennis
|
|
41
|
+
# rows << :separator if index > 0
|
|
42
|
+
rows.concat(model_rows(model_name))
|
|
43
|
+
end
|
|
44
|
+
rows.map { headers.zip(_1).to_h }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def model_rows(model_name)
|
|
48
|
+
rows = []
|
|
49
|
+
total_stats = @stats.stats_for(model_name)
|
|
50
|
+
|
|
51
|
+
# Add total row
|
|
52
|
+
rows << [
|
|
53
|
+
model_name,
|
|
54
|
+
"all",
|
|
55
|
+
total_stats[:total],
|
|
56
|
+
total_stats[:own],
|
|
57
|
+
total_stats[:inherited],
|
|
58
|
+
stats.rails? ? total_stats[:rails] : nil,
|
|
59
|
+
stats.associations? ? total_stats[:association_generated] : nil,
|
|
60
|
+
stats.attributes? ? total_stats[:attribute_generated] : nil,
|
|
61
|
+
total_stats[:gems],
|
|
62
|
+
total_stats[:conditional]
|
|
63
|
+
].compact
|
|
64
|
+
|
|
65
|
+
# Group and sort callbacks
|
|
66
|
+
grouped_callbacks = @stats.by_model[model_name].group_by { |cb| format_group_name(cb) }
|
|
67
|
+
|
|
68
|
+
grouped_callbacks.keys.sort.each do |group_name|
|
|
69
|
+
group_callbacks = grouped_callbacks[group_name]
|
|
70
|
+
rows << [
|
|
71
|
+
"",
|
|
72
|
+
group_name,
|
|
73
|
+
group_callbacks.size,
|
|
74
|
+
group_callbacks.count { |cb| cb.origin == :own },
|
|
75
|
+
group_callbacks.count(&:inherited),
|
|
76
|
+
stats.rails? ? group_callbacks.count { |cb| cb.origin == :rails } : nil,
|
|
77
|
+
stats.associations? ? group_callbacks.count(&:association_generated) : nil,
|
|
78
|
+
stats.attributes? ? group_callbacks.count(&:attribute_generated) : nil,
|
|
79
|
+
group_callbacks.count { |cb| cb.origin == :gems },
|
|
80
|
+
group_callbacks.count(&:conditional)
|
|
81
|
+
].compact
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
rows
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def format_group_name(callback)
|
|
88
|
+
raise NotImplementedError
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
module Reports
|
|
5
|
+
module Validations
|
|
6
|
+
class Github < GithubBase
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def report_title
|
|
10
|
+
"Callback Hell validations report"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def format_group_name(callback)
|
|
14
|
+
callback.validation_type
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
module Reports
|
|
5
|
+
module Validations
|
|
6
|
+
class Line < LineBase
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def report_title
|
|
10
|
+
"Callback Hell validations report:"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def format_callback_name(callback)
|
|
14
|
+
if callback.method_name.is_a?(Symbol) || callback.method_name.is_a?(String)
|
|
15
|
+
type = callback.validation_type
|
|
16
|
+
(type == "custom") ? "custom (#{callback.method_name})" : type
|
|
17
|
+
else
|
|
18
|
+
callback.validation_type
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
module Reports
|
|
5
|
+
module Validations
|
|
6
|
+
class Table < TableBase
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def report_title
|
|
10
|
+
"Callback Hell validations report"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def format_group_name(callback)
|
|
14
|
+
callback.validation_type
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
class Runner
|
|
5
|
+
DEFAULT_FORMAT = :table
|
|
6
|
+
|
|
7
|
+
def self.run(format: DEFAULT_FORMAT, model: nil, paths: nil, kind: :callbacks, **opts)
|
|
8
|
+
new(format: format, model: model, paths: paths, kind: kind, **opts).run
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(format:, model:, paths:, kind: :callbacks, sort_by: :size, sort_order: :desc, mode: :default)
|
|
12
|
+
@format = (format || DEFAULT_FORMAT).to_sym
|
|
13
|
+
@model_name = model
|
|
14
|
+
@paths = paths
|
|
15
|
+
@kind = kind
|
|
16
|
+
@sort_by = sort_by
|
|
17
|
+
@sort_order = sort_order
|
|
18
|
+
@mode = mode
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
if @kind == :report
|
|
23
|
+
[:callbacks, :validations].map do |ckind|
|
|
24
|
+
generate_report(collect_callbacks(ckind), ckind)
|
|
25
|
+
end.join("\n\n")
|
|
26
|
+
else
|
|
27
|
+
generate_report(collect_callbacks(@kind), @kind)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def collect_callbacks(ckind)
|
|
34
|
+
Collector.new(find_model_class, paths: @paths, kind: ckind, mode: @mode).collect
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def generate_report(callbacks, ckind)
|
|
38
|
+
find_reporter_class(ckind).new(
|
|
39
|
+
callbacks, sort_by: @sort_by, sort_order: @sort_order,
|
|
40
|
+
mode: @mode, kind: ckind
|
|
41
|
+
).generate
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_reporter_class(ckind)
|
|
45
|
+
namespace = ckind.to_s.capitalize
|
|
46
|
+
format = @format.to_s.capitalize
|
|
47
|
+
class_name = "CallbackHell::Reports::#{namespace}::#{format}"
|
|
48
|
+
class_name.constantize
|
|
49
|
+
rescue NameError
|
|
50
|
+
raise CallbackHell::Error, "Unknown format: #{@format} for #{ckind}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def find_model_class
|
|
54
|
+
return unless @model_name
|
|
55
|
+
|
|
56
|
+
if @model_name.match?(/^[A-Z]/)
|
|
57
|
+
@model_name.constantize
|
|
58
|
+
else
|
|
59
|
+
@model_name.classify.constantize
|
|
60
|
+
end
|
|
61
|
+
rescue NameError
|
|
62
|
+
raise CallbackHell::Error, "Cannot find model: #{@model_name}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CallbackHell
|
|
4
|
+
class Stats
|
|
5
|
+
COUNTERS = %i[
|
|
6
|
+
total own inherited rails gems conditional
|
|
7
|
+
association_generated attribute_generated
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
10
|
+
SORT = %i[size name].freeze
|
|
11
|
+
SORT_ORDER = %i[desc asc].freeze
|
|
12
|
+
|
|
13
|
+
MODE = %i[default full].freeze
|
|
14
|
+
|
|
15
|
+
attr_reader :callbacks
|
|
16
|
+
private attr_reader :sort_by, :sort_order
|
|
17
|
+
|
|
18
|
+
def initialize(callbacks, sort_by: :size, sort_order: :desc, mode: :default, kind: :callbacks)
|
|
19
|
+
@callbacks = callbacks
|
|
20
|
+
@stats_cache = {}
|
|
21
|
+
@sort_by = sort_by
|
|
22
|
+
raise ArgumentError, "Invalid sort_by: #{@sort_by}. Available: #{SORT.join(", ")}" unless SORT.include?(@sort_by)
|
|
23
|
+
@sort_order = sort_order
|
|
24
|
+
raise ArgumentError, "Invalid sort_order: #{@sort_order}. Available: #{SORT_ORDER.join(", ")}" unless SORT_ORDER.include?(@sort_order)
|
|
25
|
+
@mode = mode
|
|
26
|
+
raise ArgumentError, "Invalid mode: #{@mode}. Available: #{MODE.join(", ")}" unless MODE.include?(@mode)
|
|
27
|
+
@kind = kind
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def by_model
|
|
31
|
+
@by_model ||= callbacks.group_by { |cb| cb.model.name }.sort_by do |name, callbacks|
|
|
32
|
+
if sort_by == :size
|
|
33
|
+
callbacks.size
|
|
34
|
+
elsif sort_by == :name
|
|
35
|
+
name
|
|
36
|
+
end
|
|
37
|
+
end.tap do |sorted|
|
|
38
|
+
sorted.reverse! if sort_order == :desc
|
|
39
|
+
end.to_h
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def stats_for(model_name)
|
|
43
|
+
@stats_cache[model_name] ||= begin
|
|
44
|
+
model_callbacks = by_model[model_name]
|
|
45
|
+
return {} unless model_callbacks
|
|
46
|
+
|
|
47
|
+
collect_stats(model_callbacks)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def stats_for_group(model_name, group)
|
|
52
|
+
key = "#{model_name}_#{group}"
|
|
53
|
+
@stats_cache[key] ||= begin
|
|
54
|
+
model_callbacks = by_model[model_name]&.select { |cb| cb.callback_group == group }
|
|
55
|
+
return {} unless model_callbacks
|
|
56
|
+
|
|
57
|
+
collect_stats(model_callbacks)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def rails?
|
|
62
|
+
@mode == :full || @kind == :validations
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def associations?
|
|
66
|
+
@mode == :full
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def attributes?
|
|
70
|
+
@mode == :full
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def collect_stats(callbacks)
|
|
76
|
+
callbacks.each_with_object(initial_stats) do |cb, stats|
|
|
77
|
+
stats[:total] += 1
|
|
78
|
+
stats[:own] += 1 if cb.origin == :own
|
|
79
|
+
stats[:inherited] += 1 if cb.inherited
|
|
80
|
+
stats[:rails] += 1 if cb.origin == :rails
|
|
81
|
+
stats[:gems] += 1 if cb.origin == :gems
|
|
82
|
+
stats[:conditional] += 1 if cb.conditional
|
|
83
|
+
stats[:association_generated] += 1 if cb.association_generated
|
|
84
|
+
stats[:attribute_generated] += 1 if cb.attribute_generated
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def initial_stats
|
|
89
|
+
COUNTERS.to_h { |counter| [counter, 0] }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
|
|
5
|
+
module CallbackHell
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
def self.loader # @private
|
|
9
|
+
@loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
|
|
10
|
+
loader.ignore("#{__dir__}/tasks")
|
|
11
|
+
loader.setup
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.collect_callbacks(*models, **options)
|
|
16
|
+
collector = Collector.new(**options)
|
|
17
|
+
collector.collect(models)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.collect_validations(*models, **options)
|
|
21
|
+
collector = Collector.new(**options, kind: :validations)
|
|
22
|
+
collector.collect(models)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
CallbackHell.loader
|
|
27
|
+
|
|
28
|
+
require "callback_hell/railtie" if defined?(Rails::Railtie)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rake"
|
|
4
|
+
require "rails"
|
|
5
|
+
|
|
6
|
+
namespace :ch do
|
|
7
|
+
desc <<~DESC
|
|
8
|
+
Generate callbacks report for Active Record models.
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
format=table|line|github Report format (default: table)
|
|
12
|
+
model=ModelName Filter by model name (optional). Can be
|
|
13
|
+
specified as constant
|
|
14
|
+
name (MessageThread) or file path
|
|
15
|
+
(message_thread, admin/message_thread)
|
|
16
|
+
sort=total|name Sort by score or name (default: score:desc)
|
|
17
|
+
mode=default|full Default mode collects only user-defined callbacks,
|
|
18
|
+
full includes association-, attribute-, or validation-generated
|
|
19
|
+
callbacks
|
|
20
|
+
path=DIR1,DIR2 Additional model directories (comma-separated)
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
# Show all models callbacks in table format (default)
|
|
24
|
+
rake ch:callbacks
|
|
25
|
+
DESC
|
|
26
|
+
task callbacks: :environment do
|
|
27
|
+
opts = {
|
|
28
|
+
kind: :callbacks,
|
|
29
|
+
format: ENV["format"],
|
|
30
|
+
model: ENV["model"],
|
|
31
|
+
mode: :default
|
|
32
|
+
}.compact
|
|
33
|
+
|
|
34
|
+
if ENV["path"]
|
|
35
|
+
opts[:paths] = ENV["path"]&.split(",")&.map { |p| Rails.root.join(p) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if ENV["sort"]
|
|
39
|
+
sort_by, sort_order = ENV["sort"].split(":")
|
|
40
|
+
opts[:sort_by] = sort_by.to_sym
|
|
41
|
+
sort_order ||= ((sort_by == "size") ? :desc : :asc)
|
|
42
|
+
opts[:sort_order] = sort_order.to_sym
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if ENV["mode"]
|
|
46
|
+
raise ArgumentError, "Mode must be either default or full" unless ENV["mode"].in?(%w[default full])
|
|
47
|
+
opts[:mode] = ENV["mode"].to_sym
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
puts CallbackHell::Runner.run(**opts)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
desc <<~DESC
|
|
54
|
+
Generate validations report for Active Record models.
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
format=table|line|github Report format (default: table)
|
|
58
|
+
model=ModelName Filter by model name (optional). Can be
|
|
59
|
+
specified as constant
|
|
60
|
+
name (MessageThread) or file path
|
|
61
|
+
(message_thread, admin/message_thread)
|
|
62
|
+
sort=total|name Sort by score or name (default: score:desc)
|
|
63
|
+
mode=default|full Default mode collects only user-defined validations,
|
|
64
|
+
full includes association- or attribute-generated
|
|
65
|
+
validations
|
|
66
|
+
path=DIR1,DIR2 Additional model directories (comma-separated)
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
# Show all models validations in table format (default)
|
|
70
|
+
rake ch:validations
|
|
71
|
+
DESC
|
|
72
|
+
task validations: :environment do
|
|
73
|
+
opts = {
|
|
74
|
+
kind: :validations,
|
|
75
|
+
format: ENV["format"],
|
|
76
|
+
model: ENV["model"]
|
|
77
|
+
}.compact
|
|
78
|
+
|
|
79
|
+
if ENV["path"]
|
|
80
|
+
opts[:paths] = ENV["path"]&.split(",")&.map { |p| Rails.root.join(p) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if ENV["sort"]
|
|
84
|
+
sort_by, sort_order = ENV["sort"].split(":")
|
|
85
|
+
opts[:sort_by] = sort_by.to_sym
|
|
86
|
+
sort_order ||= ((sort_by == "size") ? :desc : :asc)
|
|
87
|
+
opts[:sort_order] = sort_order.to_sym
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if ENV["mode"]
|
|
91
|
+
raise ArgumentError, "Mode must be either default or full" unless ENV["mode"].in?(%w[default full])
|
|
92
|
+
opts[:mode] = ENV["mode"].to_sym
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
puts CallbackHell::Runner.run(**opts)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
desc <<~DESC
|
|
99
|
+
Generate combined report (callbacks and validations) for Active Record models.
|
|
100
|
+
|
|
101
|
+
Options:
|
|
102
|
+
format=table|line|github Report format (default: table)
|
|
103
|
+
model=ModelName Filter by model name (optional). Can be
|
|
104
|
+
specified as constant
|
|
105
|
+
name (MessageThread) or file path
|
|
106
|
+
(message_thread, admin/message_thread)
|
|
107
|
+
sort=total|name Sort by score or name (default: score:desc)
|
|
108
|
+
mode=default|full Default mode collects only user-defined callbacks,
|
|
109
|
+
full includes association-, attribute-, or validation-generated
|
|
110
|
+
callbacks/validations
|
|
111
|
+
path=DIR1,DIR2 Additional model directories (comma-separated)
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
rake ch:report
|
|
115
|
+
DESC
|
|
116
|
+
task report: :environment do
|
|
117
|
+
opts = {
|
|
118
|
+
kind: :report,
|
|
119
|
+
format: ENV["format"],
|
|
120
|
+
model: ENV["model"]
|
|
121
|
+
}.compact
|
|
122
|
+
|
|
123
|
+
if ENV["path"]
|
|
124
|
+
opts[:paths] = ENV["path"]&.split(",")&.map { |p| Rails.root.join(p) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if ENV["sort"]
|
|
128
|
+
sort_by, sort_order = ENV["sort"].split(":")
|
|
129
|
+
opts[:sort_by] = sort_by.to_sym
|
|
130
|
+
sort_order ||= ((sort_by == "size") ? :desc : :asc)
|
|
131
|
+
opts[:sort_order] = sort_order.to_sym
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if ENV["mode"]
|
|
135
|
+
raise ArgumentError, "Mode must be either default or full" unless ENV["mode"].in?(%w[default full])
|
|
136
|
+
opts[:mode] = ENV["mode"].to_sym
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
puts CallbackHell::Runner.run(**opts)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Top-level convenience task
|
|
144
|
+
desc "Generate callback and validation analysis report"
|
|
145
|
+
task ch: "ch:report"
|