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