railsforge 1.0.2 → 2.1.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +129 -435
  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 +28 -645
  12. data/lib/railsforge/cli_minimal.rb +10 -55
  13. data/lib/railsforge/diff.rb +57 -0
  14. data/lib/railsforge/doctor.rb +52 -225
  15. data/lib/railsforge/formatter.rb +128 -0
  16. data/lib/railsforge/issue.rb +23 -0
  17. data/lib/railsforge/loader.rb +5 -64
  18. data/lib/railsforge/version.rb +1 -1
  19. metadata +15 -82
  20. data/lib/railsforge/api_generator.rb +0 -397
  21. data/lib/railsforge/audit.rb +0 -289
  22. data/lib/railsforge/config.rb +0 -181
  23. data/lib/railsforge/database_analyzer.rb +0 -300
  24. data/lib/railsforge/feature_generator.rb +0 -560
  25. data/lib/railsforge/generator.rb +0 -313
  26. data/lib/railsforge/generators/api_generator.rb +0 -392
  27. data/lib/railsforge/generators/base_generator.rb +0 -75
  28. data/lib/railsforge/generators/demo_generator.rb +0 -307
  29. data/lib/railsforge/generators/devops_generator.rb +0 -287
  30. data/lib/railsforge/generators/form_generator.rb +0 -180
  31. data/lib/railsforge/generators/job_generator.rb +0 -176
  32. data/lib/railsforge/generators/monitoring_generator.rb +0 -134
  33. data/lib/railsforge/generators/policy_generator.rb +0 -220
  34. data/lib/railsforge/generators/presenter_generator.rb +0 -173
  35. data/lib/railsforge/generators/query_generator.rb +0 -174
  36. data/lib/railsforge/generators/serializer_generator.rb +0 -166
  37. data/lib/railsforge/generators/service_generator.rb +0 -122
  38. data/lib/railsforge/generators/stimulus_controller_generator.rb +0 -129
  39. data/lib/railsforge/generators/test_generator.rb +0 -289
  40. data/lib/railsforge/generators/view_component_generator.rb +0 -169
  41. data/lib/railsforge/graph.rb +0 -270
  42. data/lib/railsforge/mailer_generator.rb +0 -191
  43. data/lib/railsforge/plugins/plugin_loader.rb +0 -60
  44. data/lib/railsforge/plugins.rb +0 -30
  45. data/lib/railsforge/profiles.rb +0 -99
  46. data/lib/railsforge/refactor_analyzer.rb +0 -401
  47. data/lib/railsforge/refactor_controller.rb +0 -277
  48. data/lib/railsforge/refactors/refactor_controller.rb +0 -117
  49. data/lib/railsforge/template_loader.rb +0 -105
  50. data/lib/railsforge/templates/v1/form/spec_template.rb +0 -18
  51. data/lib/railsforge/templates/v1/form/template.rb +0 -28
  52. data/lib/railsforge/templates/v1/job/spec_template.rb +0 -17
  53. data/lib/railsforge/templates/v1/job/template.rb +0 -13
  54. data/lib/railsforge/templates/v1/policy/spec_template.rb +0 -41
  55. data/lib/railsforge/templates/v1/policy/template.rb +0 -57
  56. data/lib/railsforge/templates/v1/presenter/spec_template.rb +0 -12
  57. data/lib/railsforge/templates/v1/presenter/template.rb +0 -13
  58. data/lib/railsforge/templates/v1/query/spec_template.rb +0 -12
  59. data/lib/railsforge/templates/v1/query/template.rb +0 -16
  60. data/lib/railsforge/templates/v1/serializer/spec_template.rb +0 -13
  61. data/lib/railsforge/templates/v1/serializer/template.rb +0 -11
  62. data/lib/railsforge/templates/v1/service/spec_template.rb +0 -12
  63. data/lib/railsforge/templates/v1/service/template.rb +0 -25
  64. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +0 -35
  65. data/lib/railsforge/templates/v1/view_component/template.rb +0 -24
  66. data/lib/railsforge/templates/v2/job/template.rb +0 -49
  67. data/lib/railsforge/templates/v2/query/template.rb +0 -66
  68. data/lib/railsforge/templates/v2/service/spec_template.rb +0 -33
  69. data/lib/railsforge/templates/v2/service/template.rb +0 -71
  70. data/lib/railsforge/templates/v3/job/template.rb +0 -72
  71. data/lib/railsforge/templates/v3/query/spec_template.rb +0 -54
  72. data/lib/railsforge/templates/v3/query/template.rb +0 -115
  73. data/lib/railsforge/templates/v3/service/spec_template.rb +0 -51
  74. data/lib/railsforge/templates/v3/service/template.rb +0 -93
  75. data/lib/railsforge/wizard.rb +0 -265
  76. data/lib/railsforge/wizard_tui.rb +0 -286
@@ -1,117 +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
- module Refactors
8
- # RefactorController class extracts services from fat controller methods
9
- class RefactorController
10
- MIN_METHOD_LINES = 15
11
-
12
- def initialize(base_path = nil)
13
- @base_path = base_path || find_rails_app_path
14
- raise RefactorControllerError, "Not in a Rails application" unless @base_path
15
- end
16
-
17
- # Refactor a specific controller
18
- def refactor(controller_name)
19
- controller_file = find_controller_file(controller_name)
20
- raise RefactorControllerError, "Controller not found" unless controller_file
21
-
22
- content = File.read(controller_file)
23
- long_methods = find_long_methods(content)
24
-
25
- if long_methods.empty?
26
- puts "No methods to extract"
27
- return { extracted: [] }
28
- end
29
-
30
- puts "Found #{long_methods.count} method(s) to extract"
31
-
32
- extracted = []
33
- long_methods.each do |method|
34
- service = extract_to_service(controller_name, method)
35
- extracted << service if service
36
- end
37
-
38
- { extracted: extracted }
39
- end
40
-
41
- private
42
-
43
- def find_rails_app_path
44
- path = Dir.pwd
45
- 10.times do
46
- return path if File.exist?(File.join(path, "config", "application.rb"))
47
- parent = File.dirname(path)
48
- break if parent == path
49
- path = parent
50
- end
51
- nil
52
- end
53
-
54
- def find_controller_file(name)
55
- dir = File.join(@base_path, "app", "controllers")
56
- return nil unless Dir.exist?(dir)
57
-
58
- Dir.glob(File.join(dir, "**", "*_controller.rb")).find do |f|
59
- File.basename(f).include?(name.underscore)
60
- end
61
- end
62
-
63
- def find_long_methods(content)
64
- methods = []
65
- lines = content.lines
66
- in_method = false
67
- method_lines = []
68
- method_name = nil
69
-
70
- lines.each_with_index do |line, i|
71
- if line =~ /\bdef\s+(\w+)/
72
- methods << { name: method_name, lines: method_lines.count } if in_method
73
- in_method = true
74
- method_name = $1
75
- method_lines = [line]
76
- elsif in_method
77
- method_lines << line
78
- if line.strip == "end" && method_lines.count > 1
79
- methods << { name: method_name, lines: method_lines.count, content: method_lines.join }
80
- in_method = false
81
- end
82
- end
83
- end
84
-
85
- methods.select { |m| m[:lines] >= MIN_METHOD_LINES }
86
- end
87
-
88
- def extract_to_service(controller_name, method_info)
89
- service_dir = File.join(@base_path, "app", "services", controller_name.underscore)
90
- FileUtils.mkdir_p(service_dir)
91
-
92
- service_name = "#{method_info[:name]}_service"
93
- service_path = File.join(service_dir, "#{service_name}.rb")
94
-
95
- return nil if File.exist?(service_path)
96
-
97
- File.write(service_path, <<~RUBY)
98
- # Extracted from controller: #{controller_name}
99
- class #{controller_name}#{method_info[:name].capitalize}Service
100
- def initialize(params = {})
101
- @params = params
102
- end
103
-
104
- def call
105
- # TODO: Implement extracted logic
106
- end
107
- end
108
- RUBY
109
-
110
- puts " Created app/services/#{controller_name.underscore}/#{service_name}.rb"
111
- { name: service_name }
112
- end
113
-
114
- class RefactorControllerError < StandardError; end
115
- end
116
- end
117
- end
@@ -1,105 +0,0 @@
1
- # TemplateLoader module handles loading template versions
2
- require 'fileutils'
3
-
4
- module RailsForge
5
- # Template module for managing generator templates
6
- module Template
7
- # Error class for template loading issues
8
- class TemplateError < StandardError; end
9
-
10
- # Template directory path
11
- TEMPLATES_DIR = File.expand_path('../templates', __FILE__)
12
-
13
- # Default template version
14
- DEFAULT_VERSION = "v1"
15
-
16
- # Get available template versions
17
- # @return [Array<String>] Array of version names
18
- def self.available_versions
19
- return [] unless Dir.exist?(TEMPLATES_DIR)
20
-
21
- Dir.glob(File.join(TEMPLATES_DIR, 'v*')).map do |dir|
22
- File.basename(dir)
23
- end.sort
24
- end
25
-
26
- # Load template for a specific generator and version
27
- # @param generator_type [String] Type of generator (service, query, etc.)
28
- # @param version [String] Template version (e.g., "v1")
29
- # @return [Hash] Template configuration
30
- def self.load_template(generator_type, version = DEFAULT_VERSION)
31
- template_path = File.join(TEMPLATES_DIR, version, generator_type, "template.rb")
32
-
33
- unless File.exist?(template_path)
34
- raise TemplateError, "Template not found for #{generator_type} version #{version}"
35
- end
36
-
37
- {
38
- template_path: template_path,
39
- version: version,
40
- generator_type: generator_type
41
- }
42
- end
43
-
44
- # Get template content
45
- # @param generator_type [String] Type of generator
46
- # @param version [String] Template version
47
- # @return [String] Template content
48
- def self.get_content(generator_type, version = DEFAULT_VERSION)
49
- template_info = load_template(generator_type, version)
50
- File.read(template_info[:template_path])
51
- end
52
-
53
- # Check if template exists
54
- # @param generator_type [String] Type of generator
55
- # @param version [String] Template version
56
- # @return [Boolean] True if template exists
57
- def self.exists?(generator_type, version = DEFAULT_VERSION)
58
- template_path = File.join(TEMPLATES_DIR, version, generator_type, "template.rb")
59
- File.exist?(template_path)
60
- end
61
-
62
- # List templates in a version
63
- # @param version [String] Template version
64
- # @return [Array<String>] List of generator types
65
- def self.list_templates(version = DEFAULT_VERSION)
66
- version_path = File.join(TEMPLATES_DIR, version)
67
- return [] unless Dir.exist?(version_path)
68
-
69
- Dir.glob(File.join(version_path, '*')).select do |dir|
70
- File.directory?(dir)
71
- end.map do |dir|
72
- File.basename(dir)
73
- end
74
- end
75
-
76
- # Show available versions with their templates
77
- # @return [String] Formatted list
78
- def self.show_available
79
- output = "Available template versions:\n\n"
80
-
81
- available_versions.each do |version|
82
- output += " #{version}:\n"
83
- templates = list_templates(version)
84
- templates.each do |template|
85
- output += " - #{template}\n"
86
- end
87
- output += "\n"
88
- end
89
-
90
- output
91
- end
92
-
93
- # Get spec template content
94
- # @param generator_type [String] Type of generator
95
- # @param version [String] Template version
96
- # @return [String] Spec template content
97
- def self.get_spec_content(generator_type, version = DEFAULT_VERSION)
98
- spec_path = File.join(TEMPLATES_DIR, version, generator_type, "spec_template.rb")
99
-
100
- return nil unless File.exist?(spec_path)
101
-
102
- File.read(spec_path)
103
- end
104
- end
105
- end
@@ -1,18 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe <%= name.camelize %>Form do
4
- let(:params) { {} }
5
- subject { described_class.new(<%= name.underscore %>_params: params) }
6
-
7
- describe '#valid?' do
8
- it 'is valid with valid params' do
9
- expect(subject.valid?).to be_truthy
10
- end
11
- end
12
-
13
- describe '#save' do
14
- it 'saves successfully' do
15
- expect(subject.save).to be_truthy
16
- end
17
- end
18
- end
@@ -1,28 +0,0 @@
1
- # Form class for <%= name %>
2
- # Encapsulates form validation logic
3
- #
4
- # Usage:
5
- # form = <%= name.camelize %>Form.new(params)
6
- # if form.valid?
7
- # form.save
8
- # end
9
- class <%= name.camelize %>Form
10
- include ActiveModel::Model
11
-
12
- attr_accessor <%= name.underscore %>_attributes
13
-
14
- validate :validate_<%= name.underscore %>
15
-
16
- def save
17
- return false unless valid?
18
-
19
- # TODO: Implement save logic
20
- true
21
- end
22
-
23
- private
24
-
25
- def validate_<%= name.underscore %>
26
- # TODO: Add validations
27
- end
28
- end
@@ -1,17 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe <%= name.camelize %>Job do
4
- let(:args) { [] }
5
-
6
- it 'enqueues the job' do
7
- expect {
8
- described_class.perform_later(*args)
9
- }.to have_enqueued_job(described_class)
10
- end
11
-
12
- describe '#perform' do
13
- it 'performs the job' do
14
- expect { subject.perform(*args) }.not_to raise_error
15
- end
16
- end
17
- end
@@ -1,13 +0,0 @@
1
- # Background job for <%= name %>
2
- # Handles async processing
3
- #
4
- # Usage:
5
- # <%= name.camelize %>Job.perform_later(record)
6
- # <%= name.camelize %>Job.perform_now(record)
7
- class <%= name.camelize %>Job < ApplicationJob
8
- queue_as :default
9
-
10
- def perform(*args)
11
- # TODO: Implement job logic
12
- end
13
- end
@@ -1,41 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe <%= name.camelize %>Policy do
4
- let(:user) { nil }
5
- let(:record) { <%= name.camelize %>.new }
6
- subject { described_class.new(user, record) }
7
-
8
- describe '#index?' do
9
- it 'allows everyone' do
10
- expect(subject.index?).to be_truthy
11
- end
12
- end
13
-
14
- describe '#show?' do
15
- it 'allows everyone' do
16
- expect(subject.show?).to be_truthy
17
- end
18
- end
19
-
20
- describe '#create?' do
21
- it 'requires user' do
22
- expect(subject.create?).to be_falsey
23
- end
24
-
25
- it 'allows authenticated user' do
26
- user = User.new
27
- expect(described_class.new(user, record).create?).to be_truthy
28
- end
29
- end
30
-
31
- describe 'Scope' do
32
- let(:scope) { <%= name.camelize %>.all }
33
- subject { described_class::Scope.new(user, scope) }
34
-
35
- describe '#resolve' do
36
- it 'returns all records' do
37
- expect(subject.resolve).to eq(scope.all)
38
- end
39
- end
40
- end
41
- end
@@ -1,57 +0,0 @@
1
- # Policy class for <%= name %>
2
- # Handles authorization logic
3
- #
4
- # Usage:
5
- # authorize <%= name.underscore %>
6
- # policy_scope(<%= name.underscore %>)
7
- class <%= name.camelize %>Policy
8
- attr_reader :user, :record
9
-
10
- def initialize(user, record)
11
- @user = user
12
- @record = record
13
- end
14
-
15
- def index?
16
- true
17
- end
18
-
19
- def show?
20
- true
21
- end
22
-
23
- def create?
24
- user.present?
25
- end
26
-
27
- def new?
28
- create?
29
- end
30
-
31
- def update?
32
- user.present?
33
- end
34
-
35
- def edit?
36
- update?
37
- end
38
-
39
- def destroy?
40
- user.present?
41
- end
42
-
43
- class Scope
44
- def initialize(user, scope)
45
- @user = user
46
- @scope = scope
47
- end
48
-
49
- def resolve
50
- scope.all
51
- end
52
-
53
- private
54
-
55
- attr_reader :user, :scope
56
- end
57
- end
@@ -1,12 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe <%= name.camelize %>Presenter do
4
- let(:<%= name.underscore %>) { <%= name.camelize %>.new }
5
- subject { described_class.new(<%= name.underscore %>) }
6
-
7
- describe 'initialization' do
8
- it 'initializes successfully' do
9
- expect(subject).to be_a(described_class)
10
- end
11
- end
12
- end
@@ -1,13 +0,0 @@
1
- # Presenter class for <%= name %>
2
- # Handles view presentation logic
3
- #
4
- # Usage:
5
- # presenter = <%= name.camelize %>Presenter.new(@<%= name.underscore %>)
6
- # presenter.full_name
7
- class <%= name.camelize %>Presenter
8
- def initialize(<%= name.underscore %>)
9
- @<%= name.underscore %> = <%= name.underscore %>
10
- end
11
-
12
- # TODO: Add presenter methods
13
- end
@@ -1,12 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe <%= name.camelize %>Query do
4
- let(:scope) { <%= name.camelize %>.all }
5
- subject { described_class.new(scope: scope) }
6
-
7
- describe '#call' do
8
- it 'returns scope' do
9
- expect(subject.call).to eq(scope)
10
- end
11
- end
12
- end
@@ -1,16 +0,0 @@
1
- # Query class for <%= name %>
2
- # Encapsulates database queries
3
- #
4
- # Usage:
5
- # result = <%= name.camelize %>Query.call
6
- # result = <%= name.camelize %>Query.call(scope: User.active)
7
- class <%= name.camelize %>Query
8
- def initialize(scope: nil)
9
- @scope = scope || <%= name.camelize %>.all
10
- end
11
-
12
- def call
13
- # TODO: Implement query logic
14
- @scope
15
- end
16
- end
@@ -1,13 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe <%= name.camelize %>Serializer do
4
- let(:<%= name.underscore %>) { <%= name.camelize %>.new<% attributes.each do |attr| %>
5
- <%= name.underscore %>.<%= attr %> = 'test'<% end %> }
6
- subject { described_class.new(<%= name.underscore %>) }
7
-
8
- describe 'serialization' do
9
- it 'includes id' do
10
- expect(subject.as_json[:id]).to eq(<%= name.underscore %>.id)
11
- end
12
- end
13
- end
@@ -1,11 +0,0 @@
1
- # Serializer class for <%= name %>
2
- # Handles JSON serialization
3
- #
4
- # Usage:
5
- # render json: <%= name.camelize %>Serializer.new(<%= name.underscore %>)
6
- class <%= name.camelize %>Serializer < ApplicationSerializer
7
- attributes :id<% attributes.each do |attr| %>
8
- :<%= attr %><% end %>
9
-
10
- # TODO: Add custom methods
11
- end
@@ -1,12 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe <%= name.camelize %>Service do
4
- let(:params) { {} }
5
- subject { described_class.new(params) }
6
-
7
- describe '#call' do
8
- it 'returns successful result' do
9
- expect(subject.call).to be_truthy
10
- end
11
- end
12
- end
@@ -1,25 +0,0 @@
1
- # Service class for <%= name %>
2
- # Encapsulates business logic
3
- #
4
- # Usage:
5
- # result = <%= name.camelize %>Service.call(params)
6
- #
7
- # @example Basic service
8
- # class CreateUserService
9
- # def initialize(user_params)
10
- # @user_params = user_params
11
- # end
12
- #
13
- # def call
14
- # User.create!(@user_params)
15
- # end
16
- # end
17
- class <%= name.camelize %>Service
18
- def initialize(<%= name.underscore %>_params = {})
19
- @params = <%= name.underscore %>_params
20
- end
21
-
22
- def call
23
- # TODO: Implement service logic
24
- end
25
- end
@@ -1,35 +0,0 @@
1
- // Stimulus controller: <%= class_name %>
2
- import { Controller } from "@hotwired/stimulus"
3
-
4
- export default class <%= class_name %> extends Controller {
5
- // Define static targets
6
- static targets = ["output", "input"]
7
-
8
- // Connect lifecycle
9
- connect() {
10
- console.log("<%= class_name %> connected")
11
- }
12
-
13
- // Disconnect lifecycle
14
- disconnect() {
15
- console.log("<%= class_name %> disconnected")
16
- }
17
-
18
- // Example action
19
- // Usage: data-action="<%= underscore_name %>#greet"
20
- greet() {
21
- console.log("Hello from <%= class_name %>!")
22
- }
23
-
24
- // Example: Handle input changes
25
- // Usage: data-action="input-><%= underscore_name %>#handleInput"
26
- handleInput(event) {
27
- const value = event.target.value
28
- console.log("Input value:", value)
29
- }
30
-
31
- // Example: Toggle visibility
32
- toggle() {
33
- this.outputTarget.classList.toggle("hidden")
34
- }
35
- }
@@ -1,24 +0,0 @@
1
- # ViewComponent template for <%= class_name %>
2
- class <%= class_name %> < ViewComponent::Base
3
- # Define component properties
4
- # renders_maybe :some_child
5
-
6
- # Initialize with data
7
- # @param title [String] Component title
8
- def initialize(title: "", classes: "")
9
- @title = title
10
- @classes = classes
11
- end
12
-
13
- # Check if title is present
14
- # @return [Boolean]
15
- def title?
16
- @title.present?
17
- end
18
-
19
- # CSS classes for the component
20
- # @return [String]
21
- def classes
22
- "component #{@classes}"
23
- end
24
- end
@@ -1,49 +0,0 @@
1
- # Job template v2
2
- # Enhanced job with error handling and callbacks
3
- class <%= class_name %> < ApplicationJob
4
- # Configure queue
5
- queue_as :default
6
-
7
- # Retry configuration
8
- retry_on StandardError, wait: :exponentially_longer, attempts: 5
9
-
10
- # Discard configuration
11
- discard_on ActiveJob::DeserializationError
12
-
13
- # Perform the job
14
- # @param args [Array] Job arguments
15
- def perform(*args)
16
- # TODO: Implement job logic
17
- Rails.logger.info "Executing #{self.class.name}"
18
-
19
- # Example:
20
- # SomeService.call(*args)
21
- end
22
-
23
- # Called before job execution
24
- # @return [void]
25
- def before_perform
26
- Rails.logger.info "Starting #{self.class.name}"
27
- end
28
-
29
- # Called after job execution
30
- # @return [void]
31
- def after_perform
32
- Rails.logger.info "Completed #{self.class.name}"
33
- end
34
-
35
- # Called on job failure
36
- # @param exception [StandardError] The exception that was raised
37
- # @return [void]
38
- def on_failure(exception)
39
- Rails.logger.error "#{self.class.name} failed: #{exception.message}"
40
- # Notify error tracking service (e.g., Sentry, Bugsnag)
41
- end
42
-
43
- # Called when job is retried
44
- # @param exception [StandardError] The exception that caused the retry
45
- # @return [void]
46
- def on_retry(exception)
47
- Rails.logger.warn "#{self.class.name} retrying: #{exception.message}"
48
- end
49
- end