rails_lens 0.0.0 → 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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/LICENSE.txt +2 -2
  4. data/README.md +393 -9
  5. data/exe/rails_lens +25 -0
  6. data/lib/rails_lens/analyzers/association_analyzer.rb +111 -0
  7. data/lib/rails_lens/analyzers/base.rb +35 -0
  8. data/lib/rails_lens/analyzers/best_practices_analyzer.rb +114 -0
  9. data/lib/rails_lens/analyzers/column_analyzer.rb +97 -0
  10. data/lib/rails_lens/analyzers/composite_keys.rb +62 -0
  11. data/lib/rails_lens/analyzers/database_constraints.rb +35 -0
  12. data/lib/rails_lens/analyzers/delegated_types.rb +124 -0
  13. data/lib/rails_lens/analyzers/enums.rb +34 -0
  14. data/lib/rails_lens/analyzers/error_handling.rb +66 -0
  15. data/lib/rails_lens/analyzers/foreign_key_analyzer.rb +47 -0
  16. data/lib/rails_lens/analyzers/generated_columns.rb +56 -0
  17. data/lib/rails_lens/analyzers/index_analyzer.rb +128 -0
  18. data/lib/rails_lens/analyzers/inheritance.rb +212 -0
  19. data/lib/rails_lens/analyzers/notes.rb +325 -0
  20. data/lib/rails_lens/analyzers/performance_analyzer.rb +110 -0
  21. data/lib/rails_lens/annotation_pipeline.rb +87 -0
  22. data/lib/rails_lens/cli.rb +170 -0
  23. data/lib/rails_lens/cli_error_handler.rb +86 -0
  24. data/lib/rails_lens/commands.rb +164 -0
  25. data/lib/rails_lens/connection.rb +133 -0
  26. data/lib/rails_lens/erd/column_type_formatter.rb +32 -0
  27. data/lib/rails_lens/erd/domain_color_mapper.rb +40 -0
  28. data/lib/rails_lens/erd/mysql_column_type_formatter.rb +19 -0
  29. data/lib/rails_lens/erd/postgresql_column_type_formatter.rb +19 -0
  30. data/lib/rails_lens/erd/visualizer.rb +301 -0
  31. data/lib/rails_lens/errors.rb +78 -0
  32. data/lib/rails_lens/extension_loader.rb +261 -0
  33. data/lib/rails_lens/extensions/base.rb +194 -0
  34. data/lib/rails_lens/extensions/closure_tree_ext.rb +157 -0
  35. data/lib/rails_lens/file_insertion_helper.rb +168 -0
  36. data/lib/rails_lens/mailer/annotator.rb +226 -0
  37. data/lib/rails_lens/mailer/extractor.rb +201 -0
  38. data/lib/rails_lens/model_detector.rb +241 -0
  39. data/lib/rails_lens/parsers/class_info.rb +46 -0
  40. data/lib/rails_lens/parsers/module_info.rb +33 -0
  41. data/lib/rails_lens/parsers/parser_result.rb +55 -0
  42. data/lib/rails_lens/parsers/prism_parser.rb +90 -0
  43. data/lib/rails_lens/parsers.rb +10 -0
  44. data/lib/rails_lens/providers/association_notes_provider.rb +11 -0
  45. data/lib/rails_lens/providers/base.rb +37 -0
  46. data/lib/rails_lens/providers/best_practices_notes_provider.rb +11 -0
  47. data/lib/rails_lens/providers/column_notes_provider.rb +11 -0
  48. data/lib/rails_lens/providers/composite_keys_provider.rb +11 -0
  49. data/lib/rails_lens/providers/database_constraints_provider.rb +11 -0
  50. data/lib/rails_lens/providers/delegated_types_provider.rb +11 -0
  51. data/lib/rails_lens/providers/enums_provider.rb +11 -0
  52. data/lib/rails_lens/providers/extension_notes_provider.rb +20 -0
  53. data/lib/rails_lens/providers/extensions_provider.rb +22 -0
  54. data/lib/rails_lens/providers/foreign_key_notes_provider.rb +11 -0
  55. data/lib/rails_lens/providers/generated_columns_provider.rb +11 -0
  56. data/lib/rails_lens/providers/index_notes_provider.rb +20 -0
  57. data/lib/rails_lens/providers/inheritance_provider.rb +23 -0
  58. data/lib/rails_lens/providers/notes_provider_base.rb +25 -0
  59. data/lib/rails_lens/providers/performance_notes_provider.rb +11 -0
  60. data/lib/rails_lens/providers/schema_provider.rb +48 -0
  61. data/lib/rails_lens/providers/section_provider_base.rb +28 -0
  62. data/lib/rails_lens/railtie.rb +17 -0
  63. data/lib/rails_lens/rake_bootstrapper.rb +18 -0
  64. data/lib/rails_lens/route/annotator.rb +268 -0
  65. data/lib/rails_lens/route/extractor.rb +133 -0
  66. data/lib/rails_lens/route/parser.rb +59 -0
  67. data/lib/rails_lens/schema/adapters/base.rb +345 -0
  68. data/lib/rails_lens/schema/adapters/database_info.rb +118 -0
  69. data/lib/rails_lens/schema/adapters/mysql.rb +279 -0
  70. data/lib/rails_lens/schema/adapters/postgresql.rb +197 -0
  71. data/lib/rails_lens/schema/adapters/sqlite3.rb +96 -0
  72. data/lib/rails_lens/schema/annotation.rb +144 -0
  73. data/lib/rails_lens/schema/annotation_manager.rb +202 -0
  74. data/lib/rails_lens/tasks/annotate.rake +35 -0
  75. data/lib/rails_lens/tasks/erd.rake +24 -0
  76. data/lib/rails_lens/tasks/mailers.rake +27 -0
  77. data/lib/rails_lens/tasks/routes.rake +27 -0
  78. data/lib/rails_lens/tasks/schema.rake +108 -0
  79. data/lib/rails_lens/version.rb +5 -0
  80. data/lib/rails_lens.rb +138 -5
  81. metadata +215 -11
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module RailsLens
6
+ class ModelDetector
7
+ class << self
8
+ def detect_models(options = {})
9
+ # Always eager load all models
10
+ eager_load_models
11
+
12
+ # Find all ActiveRecord models (always use ActiveRecord::Base as it's always defined)
13
+ models = find_descendants_of(ActiveRecord::Base)
14
+
15
+ # Filter and sort models
16
+ models = filter_models(models, options)
17
+ models.sort_by { |model| model.name || '' }
18
+ end
19
+
20
+ def model_for_table(table_name)
21
+ detect_models.find { |model| model.table_name == table_name }
22
+ end
23
+
24
+ def abstract_models
25
+ detect_models.select(&:abstract_class?)
26
+ end
27
+
28
+ def concrete_models
29
+ detect_models.reject(&:abstract_class?)
30
+ end
31
+
32
+ def sti_base_models
33
+ concrete_models.select { |model| has_sti_column?(model) }
34
+ end
35
+
36
+ def sti_child_models
37
+ concrete_models.select { |model| model.superclass != ActiveRecord::Base && concrete_models.include?(model.superclass) }
38
+ end
39
+
40
+ private
41
+
42
+ def eager_load_models
43
+ # Zeitwerk is always available in Rails 7+
44
+ Zeitwerk::Loader.eager_load_all
45
+ end
46
+
47
+ def find_descendants_of(base_class)
48
+ base_class.descendants.select do |klass|
49
+ klass.name && !klass.name.start_with?('HABTM_')
50
+ end
51
+ end
52
+
53
+ def filter_models(models, options)
54
+ # Data provenance trace - log filtering decisions
55
+ trace_filtering = options[:trace_filtering] || ENV.fetch('RAILS_LENS_TRACE_FILTERING', nil)
56
+
57
+ original_count = models.size
58
+ Rails.logger.debug { "[ModelDetector] Starting with #{original_count} models" } if trace_filtering
59
+
60
+ # Remove anonymous classes and non-class objects
61
+ before_count = models.size
62
+ models = models.select { |model| model.is_a?(Class) && model.name.present? }
63
+ log_filter_step('Anonymous/unnamed class removal', before_count, models.size, trace_filtering)
64
+
65
+ # Filter by namespace if specified
66
+ if options[:namespace]
67
+ namespace = options[:namespace].to_s
68
+ before_count = models.size
69
+ models = models.select { |model| model.name.start_with?(namespace) }
70
+ log_filter_step("Namespace filter (#{namespace})", before_count, models.size, trace_filtering)
71
+ end
72
+
73
+ # Exclude specific models
74
+ if options[:exclude]
75
+ exclude_patterns = Array(options[:exclude])
76
+ before_count = models.size
77
+ models = models.reject do |model|
78
+ excluded = exclude_patterns.any? do |pattern|
79
+ case pattern
80
+ when Regexp
81
+ model.name.match?(pattern)
82
+ when String
83
+ model.name == pattern || model.table_name == pattern
84
+ else
85
+ false
86
+ end
87
+ end
88
+ if excluded && trace_filtering
89
+ Rails.logger.debug do
90
+ "[ModelDetector] Excluding #{model.name}: matched exclude pattern"
91
+ end
92
+ end
93
+ excluded
94
+ end
95
+ log_filter_step('Exclude patterns', before_count, models.size, trace_filtering)
96
+ end
97
+
98
+ # Include only specific models
99
+ if options[:include]
100
+ include_patterns = Array(options[:include])
101
+ before_count = models.size
102
+ models = models.select do |model|
103
+ included = include_patterns.any? do |pattern|
104
+ case pattern
105
+ when Regexp
106
+ model.name.match?(pattern)
107
+ when String
108
+ model.name == pattern || model.table_name == pattern
109
+ else
110
+ false
111
+ end
112
+ end
113
+ if included && trace_filtering
114
+ Rails.logger.debug do
115
+ "[ModelDetector] Including #{model.name}: matched include pattern"
116
+ end
117
+ end
118
+ if !included && trace_filtering
119
+ Rails.logger.debug { "[ModelDetector] Excluding #{model.name}: did not match include patterns" }
120
+ end
121
+ included
122
+ end
123
+ log_filter_step('Include patterns', before_count, models.size, trace_filtering)
124
+ end
125
+
126
+ # Exclude abstract models and models without valid tables
127
+ before_count = models.size
128
+ models = filter_models_concurrently(models, trace_filtering)
129
+ log_filter_step('Abstract/invalid table removal', before_count, models.size, trace_filtering)
130
+
131
+ # Exclude tables from configuration
132
+ excluded_tables = RailsLens.config.schema[:exclude_tables]
133
+ before_count = models.size
134
+ models = models.reject do |model|
135
+ begin
136
+ excluded = excluded_tables.include?(model.table_name)
137
+ if excluded && trace_filtering
138
+ Rails.logger.debug do
139
+ "[ModelDetector] Excluding #{model.name}: table '#{model.table_name}' in exclude_tables config"
140
+ end
141
+ end
142
+ excluded
143
+ rescue ActiveRecord::ConnectionNotDefined
144
+ # This can happen in multi-db setups if the connection is not yet established
145
+ # We will assume the model should be kept in this case
146
+ if trace_filtering
147
+ Rails.logger.debug do
148
+ "[ModelDetector] Keeping #{model.name}: connection not defined, assuming keep"
149
+ end
150
+ end
151
+ false
152
+ end
153
+ rescue ActiveRecord::StatementInvalid => e
154
+ if trace_filtering
155
+ Rails.logger.debug do
156
+ "[ModelDetector] Keeping #{model.name}: database error checking exclude_tables - #{e.message}"
157
+ end
158
+ end
159
+ false
160
+ end
161
+ log_filter_step('Configuration exclude_tables', before_count, models.size, trace_filtering)
162
+
163
+ if trace_filtering
164
+ Rails.logger.debug do
165
+ "[ModelDetector] Final result: #{models.size} models after all filtering"
166
+ end
167
+ end
168
+ Rails.logger.debug { "[ModelDetector] Final models: #{models.map(&:name).join(', ')}" } if trace_filtering
169
+
170
+ models
171
+ end
172
+
173
+ def log_filter_step(step_name, before_count, after_count, trace_filtering)
174
+ return unless trace_filtering
175
+
176
+ filtered_count = before_count - after_count
177
+ if filtered_count.positive?
178
+ Rails.logger.debug do
179
+ "[ModelDetector] #{step_name}: filtered out #{filtered_count} models (#{before_count} -> #{after_count})"
180
+ end
181
+ else
182
+ Rails.logger.debug { "[ModelDetector] #{step_name}: no models filtered (#{after_count} remain)" }
183
+ end
184
+ end
185
+
186
+ def filter_models_concurrently(models, trace_filtering)
187
+ # Use concurrent futures to check table existence in parallel
188
+ futures = models.map do |model|
189
+ Concurrent::Future.execute do
190
+ should_exclude = false
191
+ reason = nil
192
+
193
+ begin
194
+ # Skip abstract models
195
+ if model.abstract_class?
196
+ should_exclude = true
197
+ reason = 'abstract class'
198
+ # Skip models without configured tables
199
+ elsif !model.table_name
200
+ should_exclude = true
201
+ reason = 'no table name'
202
+ # Skip models whose tables don't exist
203
+ elsif !model.table_exists?
204
+ should_exclude = true
205
+ reason = "table '#{model.table_name}' does not exist"
206
+ else
207
+ reason = "table '#{model.table_name}' exists"
208
+ end
209
+ rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotDefined => e
210
+ should_exclude = true
211
+ reason = "database error checking model - #{e.message}"
212
+ rescue NameError, NoMethodError => e
213
+ should_exclude = true
214
+ reason = "method error checking model - #{e.message}"
215
+ end
216
+
217
+ if trace_filtering
218
+ action = should_exclude ? 'Excluding' : 'Keeping'
219
+ Rails.logger.debug { "[ModelDetector] #{action} #{model.name}: #{reason}" }
220
+ end
221
+
222
+ { model: model, exclude: should_exclude }
223
+ end
224
+ end
225
+
226
+ # Wait for all futures to complete and filter results
227
+ results = futures.map(&:value!)
228
+ results.reject { |result| result[:exclude] }.pluck(:model)
229
+ end
230
+
231
+ def has_sti_column?(model)
232
+ return false unless model.table_exists?
233
+
234
+ sti_column = model.inheritance_column
235
+ model.column_names.include?(sti_column)
236
+ rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotDefined
237
+ false
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Parsers
5
+ class ClassInfo
6
+ attr_reader :name, :line_number, :column, :end_line, :namespace
7
+
8
+ def initialize(name:, line_number:, column:, end_line:, namespace: nil)
9
+ @name = name
10
+ @line_number = line_number
11
+ @column = column
12
+ @end_line = end_line
13
+ @namespace = namespace
14
+ end
15
+
16
+ def full_name
17
+ if namespace.present?
18
+ "#{namespace}::#{name}"
19
+ else
20
+ name
21
+ end
22
+ end
23
+
24
+ def matches?(class_name)
25
+ class_name_str = class_name.to_s
26
+
27
+ # Exact matches
28
+ return true if name == class_name_str
29
+ return true if full_name == class_name_str
30
+
31
+ # Handle simple name match only if no namespace specified in query
32
+ return true if class_name_str.exclude?('::') && (name == class_name_str)
33
+
34
+ false
35
+ end
36
+
37
+ def to_s
38
+ full_name
39
+ end
40
+
41
+ def inspect
42
+ "#<ClassInfo name=#{name} line=#{line_number} namespace=#{namespace}>"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Parsers
5
+ class ModuleInfo
6
+ attr_reader :name, :line_number, :column, :end_line, :namespace
7
+
8
+ def initialize(name:, line_number:, column:, end_line:, namespace: nil)
9
+ @name = name
10
+ @line_number = line_number
11
+ @column = column
12
+ @end_line = end_line
13
+ @namespace = namespace
14
+ end
15
+
16
+ def full_name
17
+ if namespace.present?
18
+ "#{namespace}::#{name}"
19
+ else
20
+ name
21
+ end
22
+ end
23
+
24
+ def to_s
25
+ full_name
26
+ end
27
+
28
+ def inspect
29
+ "#<ModuleInfo name=#{name} line=#{line_number} namespace=#{namespace}>"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Parsers
5
+ class ParserResult
6
+ attr_reader :classes, :modules, :file_path
7
+
8
+ def initialize(classes:, modules:, file_path:)
9
+ @classes = classes
10
+ @modules = modules
11
+ @file_path = file_path
12
+ end
13
+
14
+ def find_class(name)
15
+ classes.find { |cls| cls.matches?(name) }
16
+ end
17
+
18
+ def find_module(name)
19
+ modules.find { |mod| mod.name == name || mod.full_name == name }
20
+ end
21
+
22
+ def class_names
23
+ classes.map(&:full_name)
24
+ end
25
+
26
+ def module_names
27
+ modules.map(&:full_name)
28
+ end
29
+
30
+ def empty?
31
+ classes.empty? && modules.empty?
32
+ end
33
+
34
+ def to_s
35
+ lines = ["File: #{file_path}"]
36
+
37
+ unless modules.empty?
38
+ lines << 'Modules:'
39
+ modules.each { |mod| lines << " #{mod}" }
40
+ end
41
+
42
+ unless classes.empty?
43
+ lines << 'Classes:'
44
+ classes.each { |cls| lines << " #{cls}" }
45
+ end
46
+
47
+ lines.join("\n")
48
+ end
49
+
50
+ def inspect
51
+ "#<ParserResult file=#{file_path} classes=#{classes.size} modules=#{modules.size}>"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+
5
+ module RailsLens
6
+ module Parsers
7
+ class PrismParser
8
+ def self.parse_file(file_path)
9
+ source = File.read(file_path)
10
+ parsed = Prism.parse(source)
11
+
12
+ classes = []
13
+ modules = []
14
+
15
+ traverse_node(parsed.value, classes, modules)
16
+
17
+ ParserResult.new(
18
+ classes: classes,
19
+ modules: modules,
20
+ file_path: file_path
21
+ )
22
+ rescue Prism::ParseError, Errno::ENOENT, IOError
23
+ # Return empty result on parse errors
24
+ ParserResult.new(
25
+ classes: [],
26
+ modules: [],
27
+ file_path: file_path
28
+ )
29
+ end
30
+
31
+ def self.traverse_node(node, classes, modules, namespace = [])
32
+ return unless node
33
+
34
+ case node
35
+ when Prism::ClassNode
36
+ class_name = extract_constant_name(node.constant_path)
37
+
38
+ classes << ClassInfo.new(
39
+ name: class_name,
40
+ line_number: node.location.start_line,
41
+ column: node.location.start_column,
42
+ end_line: node.location.end_line,
43
+ namespace: namespace.join('::').presence
44
+ )
45
+
46
+ # Process nested classes and modules
47
+ traverse_children(node, classes, modules, namespace + [class_name])
48
+
49
+ when Prism::ModuleNode
50
+ module_name = extract_constant_name(node.constant_path)
51
+
52
+ modules << ModuleInfo.new(
53
+ name: module_name,
54
+ line_number: node.location.start_line,
55
+ column: node.location.start_column,
56
+ end_line: node.location.end_line,
57
+ namespace: namespace.join('::').presence
58
+ )
59
+
60
+ # Process nested classes and modules
61
+ traverse_children(node, classes, modules, namespace + [module_name])
62
+
63
+ else
64
+ # For other node types, recursively traverse children
65
+ traverse_children(node, classes, modules, namespace)
66
+ end
67
+ end
68
+
69
+ def self.traverse_children(node, classes, modules, namespace = [])
70
+ return unless node.respond_to?(:child_nodes)
71
+
72
+ node.child_nodes.each do |child_node|
73
+ traverse_node(child_node, classes, modules, namespace)
74
+ end
75
+ end
76
+
77
+ def self.extract_constant_name(constant_path)
78
+ case constant_path
79
+ when Prism::ConstantReadNode
80
+ constant_path.name.to_s
81
+ when Prism::ConstantPathNode
82
+ # For nested constants like A::B::C, extract the last part
83
+ extract_constant_name(constant_path.child)
84
+ else
85
+ constant_path.to_s
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Parsers
5
+ # Main entry point for parsing functionality
6
+ def self.parse_file(file_path)
7
+ PrismParser.parse_file(file_path)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class AssociationNotesProvider < NotesProviderBase
6
+ def analyzer_class
7
+ Analyzers::AssociationAnalyzer
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ # Base class for all annotation content providers
6
+ class Base
7
+ # Returns the type of content this provider generates
8
+ # :schema - Primary schema information (only one allowed)
9
+ # :section - A named section with structured content
10
+ # :notes - Analysis notes and recommendations
11
+ def type
12
+ raise NotImplementedError, "#{self.class} must implement #type"
13
+ end
14
+
15
+ # Returns true if this provider should process the given model
16
+ def applicable?(_model_class)
17
+ true
18
+ end
19
+
20
+ # Processes the model and returns content
21
+ # For :schema type - returns a string with the schema content
22
+ # For :section type - returns a hash with { title: String, content: String } or nil
23
+ # For :notes type - returns an array of note strings
24
+ def process(model_class)
25
+ raise NotImplementedError, "#{self.class} must implement #process"
26
+ end
27
+
28
+ protected
29
+
30
+ def model_has_table?(model_class)
31
+ !model_class.abstract_class? && model_class.table_exists?
32
+ rescue StandardError
33
+ false
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class BestPracticesNotesProvider < NotesProviderBase
6
+ def analyzer_class
7
+ Analyzers::BestPracticesAnalyzer
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class ColumnNotesProvider < NotesProviderBase
6
+ def analyzer_class
7
+ Analyzers::ColumnAnalyzer
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class CompositeKeysProvider < SectionProviderBase
6
+ def analyzer_class
7
+ Analyzers::CompositeKeys
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class DatabaseConstraintsProvider < SectionProviderBase
6
+ def analyzer_class
7
+ Analyzers::DatabaseConstraints
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class DelegatedTypesProvider < SectionProviderBase
6
+ def analyzer_class
7
+ Analyzers::DelegatedTypes
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class EnumsProvider < SectionProviderBase
6
+ def analyzer_class
7
+ Analyzers::Enums
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class ExtensionNotesProvider < Base
6
+ def type
7
+ :notes
8
+ end
9
+
10
+ def applicable?(model_class)
11
+ RailsLens.config.extensions[:enabled] && model_has_table?(model_class)
12
+ end
13
+
14
+ def process(model_class)
15
+ results = ExtensionLoader.apply_extensions(model_class)
16
+ results[:notes]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class ExtensionsProvider < Base
6
+ def type
7
+ :section
8
+ end
9
+
10
+ def process(model_class)
11
+ results = ExtensionLoader.apply_extensions(model_class)
12
+
13
+ return nil if results[:annotations].empty?
14
+
15
+ {
16
+ title: '== Extensions',
17
+ content: results[:annotations].join("\n")
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class ForeignKeyNotesProvider < NotesProviderBase
6
+ def analyzer_class
7
+ Analyzers::ForeignKeyAnalyzer
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class GeneratedColumnsProvider < SectionProviderBase
6
+ def analyzer_class
7
+ Analyzers::GeneratedColumns
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Providers
5
+ class IndexNotesProvider < Base
6
+ def type
7
+ :notes
8
+ end
9
+
10
+ def applicable?(model_class)
11
+ model_has_table?(model_class)
12
+ end
13
+
14
+ def process(model_class)
15
+ analyzer = Analyzers::IndexAnalyzer.new(model_class)
16
+ analyzer.analyze
17
+ end
18
+ end
19
+ end
20
+ end