railsforge 1.0.2 → 2.0.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +105 -444
  3. data/lib/railsforge/analyzers/controller_analyzer.rb +29 -55
  4. data/lib/railsforge/analyzers/database_analyzer.rb +16 -30
  5. data/lib/railsforge/analyzers/metrics_analyzer.rb +8 -22
  6. data/lib/railsforge/analyzers/model_analyzer.rb +29 -46
  7. data/lib/railsforge/analyzers/performance_analyzer.rb +34 -94
  8. data/lib/railsforge/analyzers/refactor_analyzer.rb +77 -57
  9. data/lib/railsforge/analyzers/security_analyzer.rb +34 -91
  10. data/lib/railsforge/analyzers/spec_analyzer.rb +17 -31
  11. data/lib/railsforge/cli.rb +14 -650
  12. data/lib/railsforge/cli_minimal.rb +8 -55
  13. data/lib/railsforge/doctor.rb +52 -225
  14. data/lib/railsforge/formatter.rb +102 -0
  15. data/lib/railsforge/issue.rb +23 -0
  16. data/lib/railsforge/loader.rb +4 -64
  17. data/lib/railsforge/version.rb +1 -1
  18. metadata +14 -82
  19. data/lib/railsforge/api_generator.rb +0 -397
  20. data/lib/railsforge/audit.rb +0 -289
  21. data/lib/railsforge/config.rb +0 -181
  22. data/lib/railsforge/database_analyzer.rb +0 -300
  23. data/lib/railsforge/feature_generator.rb +0 -560
  24. data/lib/railsforge/generator.rb +0 -313
  25. data/lib/railsforge/generators/api_generator.rb +0 -392
  26. data/lib/railsforge/generators/base_generator.rb +0 -75
  27. data/lib/railsforge/generators/demo_generator.rb +0 -307
  28. data/lib/railsforge/generators/devops_generator.rb +0 -287
  29. data/lib/railsforge/generators/form_generator.rb +0 -180
  30. data/lib/railsforge/generators/job_generator.rb +0 -176
  31. data/lib/railsforge/generators/monitoring_generator.rb +0 -134
  32. data/lib/railsforge/generators/policy_generator.rb +0 -220
  33. data/lib/railsforge/generators/presenter_generator.rb +0 -173
  34. data/lib/railsforge/generators/query_generator.rb +0 -174
  35. data/lib/railsforge/generators/serializer_generator.rb +0 -166
  36. data/lib/railsforge/generators/service_generator.rb +0 -122
  37. data/lib/railsforge/generators/stimulus_controller_generator.rb +0 -129
  38. data/lib/railsforge/generators/test_generator.rb +0 -289
  39. data/lib/railsforge/generators/view_component_generator.rb +0 -169
  40. data/lib/railsforge/graph.rb +0 -270
  41. data/lib/railsforge/mailer_generator.rb +0 -191
  42. data/lib/railsforge/plugins/plugin_loader.rb +0 -60
  43. data/lib/railsforge/plugins.rb +0 -30
  44. data/lib/railsforge/profiles.rb +0 -99
  45. data/lib/railsforge/refactor_analyzer.rb +0 -401
  46. data/lib/railsforge/refactor_controller.rb +0 -277
  47. data/lib/railsforge/refactors/refactor_controller.rb +0 -117
  48. data/lib/railsforge/template_loader.rb +0 -105
  49. data/lib/railsforge/templates/v1/form/spec_template.rb +0 -18
  50. data/lib/railsforge/templates/v1/form/template.rb +0 -28
  51. data/lib/railsforge/templates/v1/job/spec_template.rb +0 -17
  52. data/lib/railsforge/templates/v1/job/template.rb +0 -13
  53. data/lib/railsforge/templates/v1/policy/spec_template.rb +0 -41
  54. data/lib/railsforge/templates/v1/policy/template.rb +0 -57
  55. data/lib/railsforge/templates/v1/presenter/spec_template.rb +0 -12
  56. data/lib/railsforge/templates/v1/presenter/template.rb +0 -13
  57. data/lib/railsforge/templates/v1/query/spec_template.rb +0 -12
  58. data/lib/railsforge/templates/v1/query/template.rb +0 -16
  59. data/lib/railsforge/templates/v1/serializer/spec_template.rb +0 -13
  60. data/lib/railsforge/templates/v1/serializer/template.rb +0 -11
  61. data/lib/railsforge/templates/v1/service/spec_template.rb +0 -12
  62. data/lib/railsforge/templates/v1/service/template.rb +0 -25
  63. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +0 -35
  64. data/lib/railsforge/templates/v1/view_component/template.rb +0 -24
  65. data/lib/railsforge/templates/v2/job/template.rb +0 -49
  66. data/lib/railsforge/templates/v2/query/template.rb +0 -66
  67. data/lib/railsforge/templates/v2/service/spec_template.rb +0 -33
  68. data/lib/railsforge/templates/v2/service/template.rb +0 -71
  69. data/lib/railsforge/templates/v3/job/template.rb +0 -72
  70. data/lib/railsforge/templates/v3/query/spec_template.rb +0 -54
  71. data/lib/railsforge/templates/v3/query/template.rb +0 -115
  72. data/lib/railsforge/templates/v3/service/spec_template.rb +0 -51
  73. data/lib/railsforge/templates/v3/service/template.rb +0 -93
  74. data/lib/railsforge/wizard.rb +0 -265
  75. data/lib/railsforge/wizard_tui.rb +0 -286
@@ -1,401 +0,0 @@
1
- module RailsForge
2
- # RefactorAnalyzer module handles refactoring suggestions and code extraction
3
- module RefactorAnalyzer
4
- # Error class for refactoring issues
5
- class RefactorError < StandardError; end
6
-
7
- # Configuration thresholds
8
- CONTROLLER_MAX_LINES = 150
9
- CONTROLLER_MAX_METHODS = 10
10
- MODEL_MAX_LINES = 200
11
- MODEL_MAX_METHOD_LINES = 15
12
-
13
- # Analyzes controllers for refactoring opportunities
14
- # @param base_path [String] Rails app root path
15
- # @return [Array<Hash>] Array of refactoring suggestions
16
- def self.analyze_controllers(base_path = nil)
17
- base_path ||= find_rails_app_path
18
- raise RefactorError, "Not in a Rails application directory" unless base_path
19
-
20
- controllers_dir = File.join(base_path, "app", "controllers")
21
- return [] unless Dir.exist?(controllers_dir)
22
-
23
- results = []
24
- Dir.glob(File.join(controllers_dir, "**", "*_controller.rb")).each do |file|
25
- result = analyze_controller_file(file)
26
- results << result if result[:needs_refactoring]
27
- end
28
- results
29
- end
30
-
31
- # Analyzes models for refactoring opportunities
32
- # @param base_path [String] Rails app root path
33
- # @return [Array<Hash>] Array of refactoring suggestions
34
- def self.analyze_models(base_path = nil)
35
- base_path ||= find_rails_app_path
36
- raise RefactorError, "Not in a Rails application directory" unless base_path
37
-
38
- models_dir = File.join(base_path, "app", "models")
39
- return [] unless Dir.exist?(models_dir)
40
-
41
- results = []
42
- Dir.glob(File.join(models_dir, "**", "*.rb")).each do |file|
43
- next if file.end_with?("_application.rb")
44
- result = analyze_model_file(file)
45
- results << result if result[:needs_refactoring]
46
- end
47
- results
48
- end
49
-
50
- # Analyzes a controller file
51
- # @param file_path [String] Path to controller file
52
- # @return [Hash] Analysis results
53
- def self.analyze_controller_file(file_path)
54
- content = File.read(file_path)
55
- lines = content.lines.count
56
- methods = extract_methods(content)
57
-
58
- issues = []
59
- suggestions = []
60
-
61
- if lines > CONTROLLER_MAX_LINES
62
- issues << "Controller exceeds #{CONTROLLER_MAX_LINES} lines (currently #{lines})"
63
- suggestions << "Consider moving business logic into a Service object"
64
- end
65
-
66
- if methods.count > CONTROLLER_MAX_METHODS
67
- issues << "Controller has #{methods.count} methods (recommended: #{CONTROLLER_MAX_METHODS} or less)"
68
- suggestions << "Consider extracting some actions into separate controllers or using a Service"
69
- end
70
-
71
- # Find long methods that could be extracted
72
- methods.each do |method|
73
- if method[:lines] > MODEL_MAX_METHOD_LINES
74
- suggestions << "Method `#{method[:name]}` has #{method[:lines]} lines - consider extracting to Service"
75
- end
76
- end
77
-
78
- {
79
- type: :controller,
80
- file: File.basename(file_path),
81
- path: file_path,
82
- lines: lines,
83
- methods: methods,
84
- issues: issues,
85
- suggestions: suggestions,
86
- needs_refactoring: issues.any? || suggestions.any?
87
- }
88
- end
89
-
90
- # Analyzes a model file
91
- # @param file_path [String] Path to model file
92
- # @return [Hash] Analysis results
93
- def self.analyze_model_file(file_path)
94
- content = File.read(file_path)
95
- lines = content.lines.count
96
- methods = extract_methods(content)
97
-
98
- issues = []
99
- suggestions = []
100
-
101
- if lines > MODEL_MAX_LINES
102
- issues << "Model exceeds #{MODEL_MAX_LINES} lines (currently #{lines})"
103
- suggestions << "Consider extracting scopes into Query objects or validations to a Form"
104
- end
105
-
106
- # Find long methods
107
- methods.each do |method|
108
- if method[:lines] > MODEL_MAX_METHOD_LINES
109
- suggestions << "Method `#{method[:name]}` has #{method[:lines]} lines - consider extracting to a Service"
110
- end
111
- end
112
-
113
- {
114
- type: :model,
115
- file: File.basename(file_path),
116
- path: file_path,
117
- lines: lines,
118
- methods: methods,
119
- issues: issues,
120
- suggestions: suggestions,
121
- needs_refactoring: issues.any? || suggestions.any?
122
- }
123
- end
124
-
125
- # Extracts method names and line counts from content
126
- # @param content [String] Ruby code content
127
- # @return [Array<Hash>] Array of method info
128
- def self.extract_methods(content)
129
- methods = []
130
-
131
- # Match def method_name or def self.method_name
132
- content.scan(/def\s+(self\.)?([a-z_][a-zA-Z_]*)/) do |prefix, name|
133
- methods << {
134
- name: name,
135
- is_class_method: prefix == "self.",
136
- lines: 1 # Simplified - just mark as present
137
- }
138
- end
139
-
140
- methods
141
- end
142
-
143
- # Counts lines in a method
144
- # @param content [String] Ruby code content
145
- # @param method_name [String] Method name
146
- # @param start_line [Integer] Starting line number
147
- # @return [Integer] Number of lines
148
- def self.count_method_lines(content, method_name, start_line)
149
- # Find the end of the method
150
- lines = content.lines
151
- end_pos = content.length
152
-
153
- # Look for next def or class or end
154
- rest = content.lines[start_line..-1].join
155
- if rest =~ /\n\s*def\s+(self\.)?[a-z_]/i
156
- end_pos = $~.begin(0)
157
- elsif rest =~ /\n\s*(class|module)\s+/
158
- end_pos = $~.begin(0)
159
- elsif rest =~ /\n\s*end\s*$/
160
- end_pos = $~.begin(0)
161
- end
162
-
163
- content[start_line..end_pos].lines.count
164
- end
165
-
166
- # Generates a service file for extracted logic
167
- # @param name [String] Service name
168
- # @param logic [String] Logic to extract
169
- # @param base_path [String] Rails app root
170
- # @return [String] Path to created file
171
- def self.generate_service(name, logic, base_path = nil)
172
- base_path ||= find_rails_app_path
173
- raise RefactorError, "Not in a Rails application directory" unless base_path
174
-
175
- service_dir = File.join(base_path, "app", "services")
176
- FileUtils.mkdir_p(service_dir)
177
-
178
- file_name = "#{name.underscore}_service.rb"
179
- file_path = File.join(service_dir, file_name)
180
-
181
- if File.exist?(file_path)
182
- puts " Skipping service (already exists)"
183
- return file_path
184
- end
185
-
186
- content = <<~RUBY
187
- # Service class for #{name}
188
- # Extracted from controller/model logic
189
- #
190
- # Usage:
191
- # #{name}Service.call(params)
192
- class #{name}Service
193
- def initialize(**args)
194
- @args = args
195
- end
196
-
197
- def call
198
- # Extracted logic:
199
- # #{logic.gsub("\n", "\n # ")}
200
- end
201
- end
202
- RUBY
203
-
204
- File.write(file_path, content)
205
- puts " Created app/services/#{file_name}"
206
- file_path
207
- end
208
-
209
- # Generates a query file for extracted logic
210
- # @param name [String] Query name
211
- # @param scope [String] Scope logic
212
- # @param base_path [String] Rails app root
213
- # @return [String] Path to created file
214
- def self.generate_query(name, scope, base_path = nil)
215
- base_path ||= find_rails_app_path
216
- raise RefactorError, "Not in a Rails application directory" unless base_path
217
-
218
- query_dir = File.join(base_path, "app", "queries")
219
- FileUtils.mkdir_p(query_dir)
220
-
221
- file_name = "find_#{name.underscore}.rb"
222
- file_path = File.join(query_dir, file_name)
223
-
224
- if File.exist?(file_path)
225
- puts " Skipping query (already exists)"
226
- return file_path
227
- end
228
-
229
- content = <<~RUBY
230
- # Query class for #{name}
231
- # Extracted scope/query logic
232
- #
233
- # Usage:
234
- # Find#{name}.call
235
- class Find#{name}
236
- def initialize(scope: nil)
237
- @scope = scope || #{name}.all
238
- end
239
-
240
- def call
241
- # Extracted scope:
242
- # #{scope.gsub("\n", "\n # ")}
243
- @scope
244
- end
245
- end
246
- RUBY
247
-
248
- File.write(file_path, content)
249
- puts " Created app/queries/#{file_name}"
250
- file_path
251
- end
252
-
253
- # Generates an RSpec test for a service
254
- # @param name [String] Service name
255
- # @param base_path [String] Rails app root
256
- # @return [String] Path to created file
257
- def self.generate_service_spec(name, base_path = nil)
258
- base_path ||= find_rails_app_path
259
- raise RefactorError, "Not in a Rails application directory" unless base_path
260
-
261
- spec_dir = File.join(base_path, "spec", "services")
262
- FileUtils.mkdir_p(spec_dir)
263
-
264
- file_name = "#{name.underscore}_service_spec.rb"
265
- file_path = File.join(spec_dir, file_name)
266
-
267
- if File.exist?(file_path)
268
- puts " Skipping spec (already exists)"
269
- return file_path
270
- end
271
-
272
- content = <<~RUBY
273
- require 'rails_helper'
274
-
275
- RSpec.describe #{name}Service do
276
- let(:params) { {} }
277
- subject { described_class.new(params) }
278
-
279
- describe '#call' do
280
- it 'returns successful result' do
281
- expect(subject.call).to be_truthy
282
- end
283
- end
284
- end
285
- RUBY
286
-
287
- File.write(file_path, content)
288
- puts " Created spec/services/#{file_name}"
289
- file_path
290
- end
291
-
292
- # Generates an RSpec test for a query
293
- # @param name [String] Query name
294
- # @param base_path [String] Rails app root
295
- # @return [String] Path to created file
296
- def self.generate_query_spec(name, base_path = nil)
297
- base_path ||= find_rails_app_path
298
- raise RefactorError, "Not in a Rails application directory" unless base_path
299
-
300
- spec_dir = File.join(base_path, "spec", "queries")
301
- FileUtils.mkdir_p(spec_dir)
302
-
303
- file_name = "find_#{name.underscore}_spec.rb"
304
- file_path = File.join(spec_dir, file_name)
305
-
306
- if File.exist?(file_path)
307
- puts " Skipping spec (already exists)"
308
- return file_path
309
- end
310
-
311
- content = <<~RUBY
312
- require 'rails_helper'
313
-
314
- RSpec.describe Find#{name} do
315
- let(:scope) { #{name}.all }
316
- subject { described_class.new(scope: scope) }
317
-
318
- describe '#call' do
319
- it 'returns scope' do
320
- expect(subject.call).to eq(scope)
321
- end
322
- end
323
- end
324
- RUBY
325
-
326
- File.write(file_path, content)
327
- puts " Created spec/queries/#{file_name}"
328
- file_path
329
- end
330
-
331
- # Extracts code from a controller/model and creates a service
332
- # @param file_path [String] Source file path
333
- # @param method_names [Array<String>] Methods to extract
334
- # @param service_name [String] Name for new service
335
- # @return [Hash] Results
336
- def self.extract_to_service(file_path, method_names, service_name)
337
- content = File.read(file_path)
338
-
339
- extracted_logic = []
340
- method_names.each do |method_name|
341
- # Find method in content
342
- if content.include?("def #{method_name}")
343
- # Extract method and its body
344
- method_match = content.match(/def #{method_name}.*?(\n\s*end\n)/m)
345
- extracted_logic << method_match[0] if method_match
346
- end
347
- end
348
-
349
- base_path = find_rails_app_path
350
- generate_service(service_name, extracted_logic.join("\n"), base_path)
351
- generate_service_spec(service_name, base_path)
352
-
353
- {
354
- service: "app/services/#{service_name.underscore}_service.rb",
355
- spec: "spec/services/#{service_name.underscore}_service_spec.rb"
356
- }
357
- end
358
-
359
- # Prints refactoring report
360
- # @param results [Array<Hash>] Analysis results
361
- def self.print_report(results)
362
- return puts "\n✓ No refactoring needed!" if results.empty?
363
-
364
- puts "\n" + "=" * 60
365
- puts "REFACTORING REPORT"
366
- puts "=" * 60
367
-
368
- results.each do |result|
369
- puts "\n📁 #{result[:file]} (#{result[:type]})"
370
- puts " Lines: #{result[:lines]}"
371
-
372
- if result[:issues].any?
373
- puts "\n ⚠️ Issues:"
374
- result[:issues].each { |issue| puts " • #{issue}" }
375
- end
376
-
377
- if result[:suggestions].any?
378
- puts "\n 💡 Suggestions:"
379
- result[:suggestions].each { |sug| puts " • #{sug}" }
380
- end
381
- end
382
-
383
- puts "\n" + "=" * 60
384
- puts "Total files needing refactoring: #{results.count}"
385
- puts "=" * 60
386
- end
387
-
388
- # Finds Rails app root path
389
- # @return [String, nil] Rails app path
390
- def self.find_rails_app_path
391
- path = Dir.pwd
392
- 10.times do
393
- return path if File.exist?(File.join(path, "config", "application.rb"))
394
- parent = File.dirname(path)
395
- break if parent == path
396
- path = parent
397
- end
398
- nil
399
- end
400
- end
401
- end
@@ -1,277 +0,0 @@
1
- # RefactorController module for RailsForge
2
- # Automatically refactors fat controllers by extracting service objects
3
-
4
- require 'fileutils'
5
-
6
- module RailsForge
7
- # RefactorController class extracts services from fat controller methods
8
- class RefactorController
9
- # Minimum lines to consider a method for extraction
10
- MIN_METHOD_LINES = 15
11
-
12
- # Initialize the refactor
13
- def initialize(base_path = nil)
14
- @base_path = base_path || find_rails_app_path
15
- raise RefactorControllerError, "Not in a Rails application directory" unless @base_path
16
- end
17
-
18
- # Refactor a specific controller
19
- # @param controller_name [String] Name of controller to refactor
20
- # @return [Hash] Refactoring results
21
- def refactor(controller_name)
22
- controller_file = find_controller_file(controller_name)
23
-
24
- unless controller_file
25
- raise RefactorControllerError, "Controller '#{controller_name}' not found"
26
- end
27
-
28
- puts "Analyzing controller: #{controller_name}"
29
- puts ""
30
-
31
- # Read controller content
32
- content = File.read(controller_file)
33
-
34
- # Find methods that can be extracted
35
- long_methods = find_long_methods(content)
36
-
37
- if long_methods.empty?
38
- puts "No methods found that need extraction (min #{MIN_METHOD_LINES} lines)"
39
- return { extracted: [], controller: controller_name }
40
- end
41
-
42
- puts "Found #{long_methods.count} method(s) to extract:"
43
- long_methods.each do |method|
44
- puts " - #{method[:name]} (#{method[:lines]} lines)"
45
- end
46
- puts ""
47
-
48
- # Extract each method to a service
49
- extracted = []
50
- long_methods.each do |method|
51
- service = extract_method_to_service(controller_name, method)
52
- if service
53
- update_controller_method(controller_file, method, service)
54
- extracted << service
55
- end
56
- end
57
-
58
- puts "Extraction complete!"
59
- puts " Created #{extracted.count} service(s)"
60
- puts ""
61
-
62
- { extracted: extracted, controller: controller_name, methods: long_methods }
63
- end
64
-
65
- # Preview what would be extracted without making changes
66
- # @param controller_name [String] Name of controller to analyze
67
- # @return [Array] Methods that would be extracted
68
- def preview(controller_name)
69
- controller_file = find_controller_file(controller_name)
70
-
71
- unless controller_file
72
- raise RefactorControllerError, "Controller '#{controller_name}' not found"
73
- end
74
-
75
- content = File.read(controller_file)
76
- find_long_methods(content)
77
- end
78
-
79
- private
80
-
81
- # Find the controller file path
82
- def find_rails_app_path
83
- path = Dir.pwd
84
- max_depth = 10
85
-
86
- max_depth.times do
87
- return path if File.exist?(File.join(path, "config", "application.rb"))
88
- parent = File.dirname(path)
89
- break if parent == path
90
- path = parent
91
- end
92
-
93
- nil
94
- end
95
-
96
- # Find controller file by name
97
- def find_controller_file(controller_name)
98
- controllers_dir = File.join(@base_path, "app", "controllers")
99
-
100
- # Try different variations
101
- [
102
- "#{controller_name.underscore}_controller.rb",
103
- "#{controller_name.underscore}_controller.rb"
104
- ].each do |filename|
105
- path = File.join(controllers_dir, filename)
106
- return path if File.exist?(path)
107
- end
108
-
109
- # Search recursively
110
- Dir.glob(File.join(controllers_dir, "**", "*_controller.rb")).each do |file|
111
- return file if File.basename(file).include?(controller_name.underscore)
112
- end
113
-
114
- nil
115
- end
116
-
117
- # Find methods that exceed the line threshold
118
- def find_long_methods(content)
119
- methods = []
120
- lines = content.lines
121
-
122
- # Simple regex to find method definitions
123
- in_method = false
124
- method_start = 0
125
- method_name = nil
126
- method_lines = []
127
-
128
- lines.each_with_index do |line, index|
129
- # Match def method_name or def self.method_name
130
- if line =~ /\bdef\s+(self\.)?(\w+)/
131
- if in_method
132
- # Close previous method
133
- methods << {
134
- name: method_name,
135
- start: method_start,
136
- lines: method_lines.count,
137
- content: method_lines.join
138
- }
139
- end
140
-
141
- in_method = true
142
- method_start = index
143
- method_name = $2
144
- method_lines = [line]
145
- elsif in_method
146
- if line.strip.start_with?("end") && method_lines.last =~ /\b(end|else|elsif|when)\b/
147
- # This might be the end of the method
148
- method_lines << line
149
-
150
- # Check if this is the closing end
151
- depth = method_lines.join.scan(/\b(begin|class|module|def|do|if|case)\b/).count
152
- end_depth = method_lines.join.scan(/\bend\b/).count
153
-
154
- if depth == end_depth && depth > 0
155
- methods << {
156
- name: method_name,
157
- start: method_start,
158
- lines: method_lines.count,
159
- content: method_lines.join
160
- }
161
- in_method = false
162
- method_lines = []
163
- end
164
- else
165
- method_lines << line
166
- end
167
- end
168
- end
169
-
170
- # Close last method if still open
171
- if in_method && method_lines.any?
172
- methods << {
173
- name: method_name,
174
- start: method_start,
175
- lines: method_lines.count,
176
- content: method_lines.join
177
- }
178
- end
179
-
180
- # Filter to only long methods
181
- methods.select { |m| m[:lines] >= MIN_METHOD_LINES }
182
- end
183
-
184
- # Extract a method to a service object
185
- def extract_method_to_service(controller_name, method_info)
186
- service_dir = File.join(@base_path, "app", "services", controller_name.underscore)
187
- FileUtils.mkdir_p(service_dir)
188
-
189
- service_name = "#{method_info[:name]}_service"
190
- service_class_name = "#{controller_name}#{method_info[:name].camelize}Service"
191
-
192
- service_path = File.join(service_dir, "#{service_name}.rb")
193
-
194
- if File.exist?(service_path)
195
- puts " Skipping #{service_name} (already exists)"
196
- return nil
197
- end
198
-
199
- # Generate service content
200
- service_content = generate_service_content(service_class_name, method_info)
201
-
202
- File.write(service_path, service_content)
203
- puts " Created app/services/#{controller_name.underscore}/#{service_name}.rb"
204
-
205
- { name: service_name, class: service_class_name, path: service_path }
206
- end
207
-
208
- # Generate service object content
209
- def generate_service_content(class_name, method_info)
210
- <<~RUBY
211
- # Service extracted from controller method: #{method_info[:name]}
212
- # Original method had #{method_info[:lines]} lines
213
- class #{class_name}
214
- def initialize(params = {})
215
- @params = params
216
- end
217
-
218
- def call
219
- # TODO: Refactor extracted logic here
220
- # Original method content:
221
- # #{method_info[:content].lines.first(5).join('# ')}
222
- end
223
-
224
- def self.call(params = {})
225
- new(params).call
226
- end
227
- end
228
- RUBY
229
- end
230
-
231
- # Update the controller method to use the service
232
- def update_controller_method(controller_file, method_info, service)
233
- content = File.read(controller_file)
234
-
235
- # Replace method body with service call
236
- new_method = <<~RUBY
237
- def #{method_info[:name]}
238
- result = #{service[:class]}.call(params)
239
- end
240
- RUBY
241
-
242
- # Simple replacement - in production would use proper parsing
243
- lines = content.lines
244
-
245
- # Find and replace the method
246
- modified_lines = []
247
- in_target_method = false
248
- method_depth = 0
249
-
250
- lines.each_with_index do |line, index|
251
- if line =~ /\bdef\s+(self\.)?#{method_info[:name]}\b/
252
- in_target_method = true
253
- modified_lines << new_method
254
- next
255
- end
256
-
257
- if in_target_method
258
- method_depth += 1 if line =~ /\b(def|class|module|if|case|do)\b/
259
- method_depth -= 1 if line =~ /\bend\b/ && method_depth > 0
260
-
261
- if method_depth == 0
262
- in_target_method = false
263
- end
264
- next # Skip original method lines
265
- end
266
-
267
- modified_lines << line
268
- end
269
-
270
- File.write(controller_file, modified_lines.join)
271
- puts " Updated controller method to use service"
272
- end
273
-
274
- # Error class
275
- class RefactorControllerError < StandardError; end
276
- end
277
- end