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.
- checksums.yaml +4 -4
- data/README.md +129 -435
- data/lib/railsforge/analyzers/controller_analyzer.rb +29 -55
- data/lib/railsforge/analyzers/database_analyzer.rb +16 -30
- data/lib/railsforge/analyzers/metrics_analyzer.rb +8 -22
- data/lib/railsforge/analyzers/model_analyzer.rb +29 -46
- data/lib/railsforge/analyzers/performance_analyzer.rb +34 -94
- data/lib/railsforge/analyzers/refactor_analyzer.rb +77 -57
- data/lib/railsforge/analyzers/security_analyzer.rb +34 -91
- data/lib/railsforge/analyzers/spec_analyzer.rb +17 -31
- data/lib/railsforge/cli.rb +28 -645
- data/lib/railsforge/cli_minimal.rb +10 -55
- data/lib/railsforge/diff.rb +57 -0
- data/lib/railsforge/doctor.rb +52 -225
- data/lib/railsforge/formatter.rb +128 -0
- data/lib/railsforge/issue.rb +23 -0
- data/lib/railsforge/loader.rb +5 -64
- data/lib/railsforge/version.rb +1 -1
- metadata +15 -82
- data/lib/railsforge/api_generator.rb +0 -397
- data/lib/railsforge/audit.rb +0 -289
- data/lib/railsforge/config.rb +0 -181
- data/lib/railsforge/database_analyzer.rb +0 -300
- data/lib/railsforge/feature_generator.rb +0 -560
- data/lib/railsforge/generator.rb +0 -313
- data/lib/railsforge/generators/api_generator.rb +0 -392
- data/lib/railsforge/generators/base_generator.rb +0 -75
- data/lib/railsforge/generators/demo_generator.rb +0 -307
- data/lib/railsforge/generators/devops_generator.rb +0 -287
- data/lib/railsforge/generators/form_generator.rb +0 -180
- data/lib/railsforge/generators/job_generator.rb +0 -176
- data/lib/railsforge/generators/monitoring_generator.rb +0 -134
- data/lib/railsforge/generators/policy_generator.rb +0 -220
- data/lib/railsforge/generators/presenter_generator.rb +0 -173
- data/lib/railsforge/generators/query_generator.rb +0 -174
- data/lib/railsforge/generators/serializer_generator.rb +0 -166
- data/lib/railsforge/generators/service_generator.rb +0 -122
- data/lib/railsforge/generators/stimulus_controller_generator.rb +0 -129
- data/lib/railsforge/generators/test_generator.rb +0 -289
- data/lib/railsforge/generators/view_component_generator.rb +0 -169
- data/lib/railsforge/graph.rb +0 -270
- data/lib/railsforge/mailer_generator.rb +0 -191
- data/lib/railsforge/plugins/plugin_loader.rb +0 -60
- data/lib/railsforge/plugins.rb +0 -30
- data/lib/railsforge/profiles.rb +0 -99
- data/lib/railsforge/refactor_analyzer.rb +0 -401
- data/lib/railsforge/refactor_controller.rb +0 -277
- data/lib/railsforge/refactors/refactor_controller.rb +0 -117
- data/lib/railsforge/template_loader.rb +0 -105
- data/lib/railsforge/templates/v1/form/spec_template.rb +0 -18
- data/lib/railsforge/templates/v1/form/template.rb +0 -28
- data/lib/railsforge/templates/v1/job/spec_template.rb +0 -17
- data/lib/railsforge/templates/v1/job/template.rb +0 -13
- data/lib/railsforge/templates/v1/policy/spec_template.rb +0 -41
- data/lib/railsforge/templates/v1/policy/template.rb +0 -57
- data/lib/railsforge/templates/v1/presenter/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/presenter/template.rb +0 -13
- data/lib/railsforge/templates/v1/query/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/query/template.rb +0 -16
- data/lib/railsforge/templates/v1/serializer/spec_template.rb +0 -13
- data/lib/railsforge/templates/v1/serializer/template.rb +0 -11
- data/lib/railsforge/templates/v1/service/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/service/template.rb +0 -25
- data/lib/railsforge/templates/v1/stimulus_controller/template.rb +0 -35
- data/lib/railsforge/templates/v1/view_component/template.rb +0 -24
- data/lib/railsforge/templates/v2/job/template.rb +0 -49
- data/lib/railsforge/templates/v2/query/template.rb +0 -66
- data/lib/railsforge/templates/v2/service/spec_template.rb +0 -33
- data/lib/railsforge/templates/v2/service/template.rb +0 -71
- data/lib/railsforge/templates/v3/job/template.rb +0 -72
- data/lib/railsforge/templates/v3/query/spec_template.rb +0 -54
- data/lib/railsforge/templates/v3/query/template.rb +0 -115
- data/lib/railsforge/templates/v3/service/spec_template.rb +0 -51
- data/lib/railsforge/templates/v3/service/template.rb +0 -93
- data/lib/railsforge/wizard.rb +0 -265
- data/lib/railsforge/wizard_tui.rb +0 -286
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# Form generator for RailsForge
|
|
2
|
-
# Generates form object files
|
|
3
|
-
|
|
4
|
-
require_relative 'base_generator'
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
module Generators
|
|
8
|
-
# FormGenerator creates form files
|
|
9
|
-
class FormGenerator < BaseGenerator
|
|
10
|
-
# Error class for invalid form names
|
|
11
|
-
class InvalidFormNameError < StandardError; end
|
|
12
|
-
|
|
13
|
-
# Template version
|
|
14
|
-
TEMPLATE_VERSION = "v1"
|
|
15
|
-
|
|
16
|
-
# Initialize the generator
|
|
17
|
-
# @param name [String] Form name
|
|
18
|
-
# @param options [Hash] Generator options
|
|
19
|
-
def initialize(name, options = {})
|
|
20
|
-
super(name, options)
|
|
21
|
-
@template_version = options[:template_version] || TEMPLATE_VERSION
|
|
22
|
-
@with_spec = options.fetch(:with_spec, true)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Generate form files
|
|
26
|
-
# @return [String] Success message
|
|
27
|
-
def generate
|
|
28
|
-
return "Not in a Rails application directory" unless @base_path
|
|
29
|
-
|
|
30
|
-
validate_name!(@name)
|
|
31
|
-
|
|
32
|
-
results = []
|
|
33
|
-
results << generate_form
|
|
34
|
-
|
|
35
|
-
if @with_spec
|
|
36
|
-
results << generate_spec
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
"Form '#{@name}' generated successfully!\n" + results.join("\n")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Class method for CLI
|
|
43
|
-
def self.generate(form_name, with_spec: true, template_version: "v1")
|
|
44
|
-
new(form_name, with_spec: with_spec, template_version: template_version).generate
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
# Validate form name
|
|
50
|
-
def validate_name!(name)
|
|
51
|
-
raise InvalidFormNameError, "Form name cannot be empty" if name.nil? || name.strip.empty?
|
|
52
|
-
raise InvalidFormNameError, "Name must match pattern: /\\A[A-Z][a-zA-Z0-9]*\\z/" unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Generate form file
|
|
56
|
-
def generate_form
|
|
57
|
-
form_dir = File.join(@base_path, "app", "forms")
|
|
58
|
-
FileUtils.mkdir_p(form_dir)
|
|
59
|
-
|
|
60
|
-
file_name = "#{underscore}_form.rb"
|
|
61
|
-
file_path = File.join(form_dir, file_name)
|
|
62
|
-
|
|
63
|
-
return " Skipping form (already exists)" if File.exist?(file_path)
|
|
64
|
-
|
|
65
|
-
content = load_template
|
|
66
|
-
content = apply_template(content)
|
|
67
|
-
|
|
68
|
-
File.write(file_path, content)
|
|
69
|
-
" Created app/forms/#{file_name}"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Generate spec file
|
|
73
|
-
def generate_spec
|
|
74
|
-
spec_dir = File.join(@base_path, "spec", "forms")
|
|
75
|
-
FileUtils.mkdir_p(spec_dir)
|
|
76
|
-
|
|
77
|
-
file_name = "#{underscore}_form_spec.rb"
|
|
78
|
-
file_path = File.join(spec_dir, file_name)
|
|
79
|
-
|
|
80
|
-
return " Skipping spec (already exists)" if File.exist?(file_path)
|
|
81
|
-
|
|
82
|
-
content = load_spec_template
|
|
83
|
-
content = apply_template(content)
|
|
84
|
-
|
|
85
|
-
File.write(file_path, content)
|
|
86
|
-
" Created spec/forms/#{file_name}"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Load template content
|
|
90
|
-
def load_template
|
|
91
|
-
template_path = File.join(
|
|
92
|
-
File.dirname(__FILE__),
|
|
93
|
-
"..",
|
|
94
|
-
"templates",
|
|
95
|
-
@template_version,
|
|
96
|
-
"form",
|
|
97
|
-
"template.rb"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
if File.exist?(template_path)
|
|
101
|
-
File.read(template_path)
|
|
102
|
-
else
|
|
103
|
-
default_template
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Load spec template
|
|
108
|
-
def load_spec_template
|
|
109
|
-
spec_path = File.join(
|
|
110
|
-
File.dirname(__FILE__),
|
|
111
|
-
"..",
|
|
112
|
-
"templates",
|
|
113
|
-
@template_version,
|
|
114
|
-
"form",
|
|
115
|
-
"spec_template.rb"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
if File.exist?(spec_path)
|
|
119
|
-
File.read(spec_path)
|
|
120
|
-
else
|
|
121
|
-
default_spec_template
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Default template
|
|
126
|
-
def default_template
|
|
127
|
-
<<~RUBY
|
|
128
|
-
# Form class for #{underscore}
|
|
129
|
-
# Encapsulates form validation and processing
|
|
130
|
-
class #{camelize}Form
|
|
131
|
-
include ActiveModel::Model
|
|
132
|
-
|
|
133
|
-
attr_accessor :name, :email
|
|
134
|
-
|
|
135
|
-
validates :name, presence: true
|
|
136
|
-
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
137
|
-
|
|
138
|
-
def save
|
|
139
|
-
return false unless valid?
|
|
140
|
-
|
|
141
|
-
# TODO: Implement save logic
|
|
142
|
-
true
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
RUBY
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Default spec template
|
|
149
|
-
def default_spec_template
|
|
150
|
-
<<~RUBY
|
|
151
|
-
require 'rails_helper'
|
|
152
|
-
|
|
153
|
-
RSpec.describe #{camelize}Form do
|
|
154
|
-
describe 'validations' do
|
|
155
|
-
it 'validates presence of name' do
|
|
156
|
-
form = described_class.new(name: nil, email: 'test@example.com')
|
|
157
|
-
expect(form).not_to be_valid
|
|
158
|
-
expect(form.errors[:name]).to be_present
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
it 'validates email format' do
|
|
162
|
-
form = described_class.new(name: 'Test', email: 'invalid')
|
|
163
|
-
expect(form).not_to be_valid
|
|
164
|
-
expect(form.errors[:email]).to be_present
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
RUBY
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Apply template variables
|
|
172
|
-
def apply_template(content)
|
|
173
|
-
content
|
|
174
|
-
.gsub("<%= name %>", @name)
|
|
175
|
-
.gsub("<%= name.camelize %>", camelize)
|
|
176
|
-
.gsub("<%= name.underscore %>", underscore)
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
end
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
# Job generator for RailsForge
|
|
2
|
-
# Generates background job files
|
|
3
|
-
|
|
4
|
-
require_relative 'base_generator'
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
module Generators
|
|
8
|
-
# JobGenerator creates job files
|
|
9
|
-
class JobGenerator < BaseGenerator
|
|
10
|
-
# Error class for invalid job names
|
|
11
|
-
class InvalidJobNameError < StandardError; end
|
|
12
|
-
|
|
13
|
-
# Template version
|
|
14
|
-
TEMPLATE_VERSION = "v1"
|
|
15
|
-
|
|
16
|
-
# Initialize the generator
|
|
17
|
-
# @param name [String] Job name
|
|
18
|
-
# @param options [Hash] Generator options
|
|
19
|
-
def initialize(name, options = {})
|
|
20
|
-
super(name, options)
|
|
21
|
-
@template_version = options[:template_version] || TEMPLATE_VERSION
|
|
22
|
-
@with_spec = options.fetch(:with_spec, true)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Generate job files
|
|
26
|
-
# @return [String] Success message
|
|
27
|
-
def generate
|
|
28
|
-
return "Not in a Rails application directory" unless @base_path
|
|
29
|
-
|
|
30
|
-
validate_name!(@name)
|
|
31
|
-
|
|
32
|
-
results = []
|
|
33
|
-
results << generate_job
|
|
34
|
-
|
|
35
|
-
if @with_spec
|
|
36
|
-
results << generate_spec
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
"Job '#{@name}' generated successfully!\n" + results.join("\n")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Class method for CLI
|
|
43
|
-
def self.generate(job_name, with_spec: true, template_version: "v1")
|
|
44
|
-
new(job_name, with_spec: with_spec, template_version: template_version).generate
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
# Validate job name
|
|
50
|
-
def validate_name!(name)
|
|
51
|
-
raise InvalidJobNameError, "Job name cannot be empty" if name.nil? || name.strip.empty?
|
|
52
|
-
raise InvalidJobNameError, "Name must match pattern: /\\A[A-Z][a-zA-Z0-9]*\\z/" unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Generate job file
|
|
56
|
-
def generate_job
|
|
57
|
-
job_dir = File.join(@base_path, "app", "jobs")
|
|
58
|
-
FileUtils.mkdir_p(job_dir)
|
|
59
|
-
|
|
60
|
-
file_name = "#{underscore}_job.rb"
|
|
61
|
-
file_path = File.join(job_dir, file_name)
|
|
62
|
-
|
|
63
|
-
return " Skipping job (already exists)" if File.exist?(file_path)
|
|
64
|
-
|
|
65
|
-
content = load_template
|
|
66
|
-
content = apply_template(content)
|
|
67
|
-
|
|
68
|
-
File.write(file_path, content)
|
|
69
|
-
" Created app/jobs/#{file_name}"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Generate spec file
|
|
73
|
-
def generate_spec
|
|
74
|
-
spec_dir = File.join(@base_path, "spec", "jobs")
|
|
75
|
-
FileUtils.mkdir_p(spec_dir)
|
|
76
|
-
|
|
77
|
-
file_name = "#{underscore}_job_spec.rb"
|
|
78
|
-
file_path = File.join(spec_dir, file_name)
|
|
79
|
-
|
|
80
|
-
return " Skipping spec (already exists)" if File.exist?(file_path)
|
|
81
|
-
|
|
82
|
-
content = load_spec_template
|
|
83
|
-
content = apply_template(content)
|
|
84
|
-
|
|
85
|
-
File.write(file_path, content)
|
|
86
|
-
" Created spec/jobs/#{file_name}"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Load template content
|
|
90
|
-
def load_template
|
|
91
|
-
template_path = File.join(
|
|
92
|
-
File.dirname(__FILE__),
|
|
93
|
-
"..",
|
|
94
|
-
"templates",
|
|
95
|
-
@template_version,
|
|
96
|
-
"job",
|
|
97
|
-
"template.rb"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
if File.exist?(template_path)
|
|
101
|
-
File.read(template_path)
|
|
102
|
-
else
|
|
103
|
-
default_template
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Load spec template
|
|
108
|
-
def load_spec_template
|
|
109
|
-
spec_path = File.join(
|
|
110
|
-
File.dirname(__FILE__),
|
|
111
|
-
"..",
|
|
112
|
-
"templates",
|
|
113
|
-
@template_version,
|
|
114
|
-
"job",
|
|
115
|
-
"spec_template.rb"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
if File.exist?(spec_path)
|
|
119
|
-
File.read(spec_path)
|
|
120
|
-
else
|
|
121
|
-
default_spec_template
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Default template
|
|
126
|
-
def default_template
|
|
127
|
-
<<~RUBY
|
|
128
|
-
# Background job for #{underscore}
|
|
129
|
-
# Handles async processing
|
|
130
|
-
#
|
|
131
|
-
# Usage:
|
|
132
|
-
# #{camelize}Job.perform_later(record)
|
|
133
|
-
# #{camelize}Job.perform_now(record)
|
|
134
|
-
class #{camelize}Job < ApplicationJob
|
|
135
|
-
queue_as :default
|
|
136
|
-
|
|
137
|
-
def perform(*args)
|
|
138
|
-
# TODO: Implement job logic
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
RUBY
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Default spec template
|
|
145
|
-
def default_spec_template
|
|
146
|
-
<<~RUBY
|
|
147
|
-
require 'rails_helper'
|
|
148
|
-
|
|
149
|
-
RSpec.describe #{camelize}Job do
|
|
150
|
-
let(:args) { [] }
|
|
151
|
-
|
|
152
|
-
it 'enqueues the job' do
|
|
153
|
-
expect {
|
|
154
|
-
described_class.perform_later(*args)
|
|
155
|
-
}.to have_enqueued_job(described_class)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
describe '#perform' do
|
|
159
|
-
it 'performs the job' do
|
|
160
|
-
expect { subject.perform(*args) }.not_to raise_error
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
RUBY
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Apply template variables
|
|
168
|
-
def apply_template(content)
|
|
169
|
-
content
|
|
170
|
-
.gsub("<%= name %>", @name)
|
|
171
|
-
.gsub("<%= name.camelize %>", camelize)
|
|
172
|
-
.gsub("<%= name.underscore %>", underscore)
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
end
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# Monitoring generator for RailsForge
|
|
2
|
-
# Generates Sentry and Lograge configurations
|
|
3
|
-
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
module Generators
|
|
8
|
-
# Monitoring generator
|
|
9
|
-
class MonitoringGenerator < BaseGenerator
|
|
10
|
-
# Initialize the generator
|
|
11
|
-
def initialize(name = "monitoring", options = {})
|
|
12
|
-
super(name, options)
|
|
13
|
-
@sentry_dsn = options[:sentry_dsn] || ""
|
|
14
|
-
@environment = options[:environment] || (defined?(Rails) && Rails.env) || 'development'
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Generate monitoring configs
|
|
18
|
-
def generate
|
|
19
|
-
return "Not in a Rails application directory" unless @base_path
|
|
20
|
-
|
|
21
|
-
results = []
|
|
22
|
-
results << create_sentry_initializer
|
|
23
|
-
results << create_lograge_config
|
|
24
|
-
results << create_log_formatter
|
|
25
|
-
results << create_environments_config
|
|
26
|
-
|
|
27
|
-
results.join("\n")
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
# Create Sentry initializer
|
|
33
|
-
def create_sentry_initializer
|
|
34
|
-
init_dir = File.join(@base_path, "config", "initializers")
|
|
35
|
-
FileUtils.mkdir_p(init_dir)
|
|
36
|
-
|
|
37
|
-
sentry_path = File.join(init_dir, "sentry.rb")
|
|
38
|
-
return "Skipping Sentry initializer (already exists)" if File.exist?(sentry_path)
|
|
39
|
-
|
|
40
|
-
content = <<~RUBY
|
|
41
|
-
# Sentry error tracking configuration
|
|
42
|
-
# Install sentry-ruby and add to Gemfile: gem 'sentry-ruby', '~> 4.0'
|
|
43
|
-
Sentry.init do |config|
|
|
44
|
-
config.dsn = ENV.fetch('SENTRY_DSN', '#{@sentry_dsn}')
|
|
45
|
-
config.breadcrumb_logger = :active_support
|
|
46
|
-
config.environments = %w[staging production]
|
|
47
|
-
config.traces_sample_rate = 0.1
|
|
48
|
-
config.before_send = lambda do |event, hint|
|
|
49
|
-
# Filter out specific errors
|
|
50
|
-
if hint[:exception]
|
|
51
|
-
# Add custom filtering logic here
|
|
52
|
-
end
|
|
53
|
-
event
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
RUBY
|
|
57
|
-
|
|
58
|
-
File.write(sentry_path, content)
|
|
59
|
-
"Created config/initializers/sentry.rb"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Create Lograge configuration
|
|
63
|
-
def create_lograge_config
|
|
64
|
-
init_dir = File.join(@base_path, "config", "initializers")
|
|
65
|
-
FileUtils.mkdir_p(init_dir)
|
|
66
|
-
|
|
67
|
-
lograge_path = File.join(init_dir, "lograge.rb")
|
|
68
|
-
return "Skipping Lograge initializer (already exists)" if File.exist?(lograge_path)
|
|
69
|
-
|
|
70
|
-
content = <<~RUBY
|
|
71
|
-
# Lograge configuration for structured logging
|
|
72
|
-
# Add to Gemfile: gem 'lograge'
|
|
73
|
-
Rails.application.configure do
|
|
74
|
-
config.lograge.enabled = true
|
|
75
|
-
config.lograge.base_controller_class = 'ActionController::API'
|
|
76
|
-
config.lograge.custom_options = lambda do |event|
|
|
77
|
-
{
|
|
78
|
-
# Add custom data to logs
|
|
79
|
-
host: Socket.gethostname,
|
|
80
|
-
pid: Process.pid,
|
|
81
|
-
# Add timing data
|
|
82
|
-
request_id: event.payload[:request_id],
|
|
83
|
-
user_id: event.payload[:user_id]
|
|
84
|
-
}
|
|
85
|
-
end
|
|
86
|
-
config.lograge.formatter = Lograge::Formatters::Json.new
|
|
87
|
-
end
|
|
88
|
-
RUBY
|
|
89
|
-
|
|
90
|
-
File.write(lograge_path, content)
|
|
91
|
-
"Created config/initializers/lograge.rb"
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Create log formatter
|
|
95
|
-
def create_log_formatter
|
|
96
|
-
config_path = File.join(@base_path, "config")
|
|
97
|
-
|
|
98
|
-
# Check if application.rb exists
|
|
99
|
-
app_rb = File.join(config_path, "application.rb")
|
|
100
|
-
return "Not a Rails app" unless File.exist?(app_rb)
|
|
101
|
-
|
|
102
|
-
content = File.read(app_rb)
|
|
103
|
-
|
|
104
|
-
unless content.include?("config.log_formatter")
|
|
105
|
-
# Add log formatter to application.rb
|
|
106
|
-
File.write(app_rb, content.gsub(/class Application < Rails::Application/,
|
|
107
|
-
"class Application < Rails::Application\n config.log_formatter = proc { |severity, datetime, progname, msg| \"#{datetime.utc_iso8601} #{severity}: #{msg}\\n\" }"))
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
"Updated application.rb with custom log formatter"
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Create environment-specific configs
|
|
114
|
-
def create_environments_config
|
|
115
|
-
env_dir = File.join(@base_path, "config", "environments")
|
|
116
|
-
return "Not a Rails app" unless Dir.exist?(env_dir)
|
|
117
|
-
|
|
118
|
-
results = []
|
|
119
|
-
|
|
120
|
-
# Production config
|
|
121
|
-
prod_path = File.join(env_dir, "production.rb")
|
|
122
|
-
if File.exist?(prod_path)
|
|
123
|
-
content = File.read(prod_path)
|
|
124
|
-
unless content.include?("Lograge") || content.include?("Sentry")
|
|
125
|
-
File.write(prod_path, content + "\n\n# Monitoring\nconfig.lograge.enabled = true\n")
|
|
126
|
-
results << "Updated production.rb"
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
results.any? ? results.join("\n") : "No environment updates needed"
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
# Policy generator for RailsForge
|
|
2
|
-
# Generates policy object files (for Pundit)
|
|
3
|
-
|
|
4
|
-
require_relative 'base_generator'
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
module Generators
|
|
8
|
-
# PolicyGenerator creates policy files
|
|
9
|
-
class PolicyGenerator < BaseGenerator
|
|
10
|
-
# Error class for invalid policy names
|
|
11
|
-
class InvalidPolicyNameError < StandardError; end
|
|
12
|
-
|
|
13
|
-
# Template version
|
|
14
|
-
TEMPLATE_VERSION = "v1"
|
|
15
|
-
|
|
16
|
-
# Initialize the generator
|
|
17
|
-
# @param name [String] Policy name
|
|
18
|
-
# @param options [Hash] Generator options
|
|
19
|
-
def initialize(name, options = {})
|
|
20
|
-
super(name, options)
|
|
21
|
-
@template_version = options[:template_version] || TEMPLATE_VERSION
|
|
22
|
-
@with_spec = options.fetch(:with_spec, true)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Generate policy files
|
|
26
|
-
# @return [String] Success message
|
|
27
|
-
def generate
|
|
28
|
-
return "Not in a Rails application directory" unless @base_path
|
|
29
|
-
|
|
30
|
-
validate_name!(@name)
|
|
31
|
-
|
|
32
|
-
results = []
|
|
33
|
-
results << generate_policy
|
|
34
|
-
|
|
35
|
-
if @with_spec
|
|
36
|
-
results << generate_spec
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
"Policy '#{@name}' generated successfully!\n" + results.join("\n")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Class method for CLI
|
|
43
|
-
def self.generate(policy_name, with_spec: true, template_version: "v1")
|
|
44
|
-
new(policy_name, with_spec: with_spec, template_version: template_version).generate
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
# Validate policy name
|
|
50
|
-
def validate_name!(name)
|
|
51
|
-
raise InvalidPolicyNameError, "Policy name cannot be empty" if name.nil? || name.strip.empty?
|
|
52
|
-
raise InvalidPolicyNameError, "Name must match pattern: /\\A[A-Z][a-zA-Z0-9]*\\z/" unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Generate policy file
|
|
56
|
-
def generate_policy
|
|
57
|
-
policy_dir = File.join(@base_path, "app", "policies")
|
|
58
|
-
FileUtils.mkdir_p(policy_dir)
|
|
59
|
-
|
|
60
|
-
file_name = "#{underscore}_policy.rb"
|
|
61
|
-
file_path = File.join(policy_dir, file_name)
|
|
62
|
-
|
|
63
|
-
return " Skipping policy (already exists)" if File.exist?(file_path)
|
|
64
|
-
|
|
65
|
-
content = load_template
|
|
66
|
-
content = apply_template(content)
|
|
67
|
-
|
|
68
|
-
File.write(file_path, content)
|
|
69
|
-
" Created app/policies/#{file_name}"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Generate spec file
|
|
73
|
-
def generate_spec
|
|
74
|
-
spec_dir = File.join(@base_path, "spec", "policies")
|
|
75
|
-
FileUtils.mkdir_p(spec_dir)
|
|
76
|
-
|
|
77
|
-
file_name = "#{underscore}_policy_spec.rb"
|
|
78
|
-
file_path = File.join(spec_dir, file_name)
|
|
79
|
-
|
|
80
|
-
return " Skipping spec (already exists)" if File.exist?(file_path)
|
|
81
|
-
|
|
82
|
-
content = load_spec_template
|
|
83
|
-
content = apply_template(content)
|
|
84
|
-
|
|
85
|
-
File.write(file_path, content)
|
|
86
|
-
" Created spec/policies/#{file_name}"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Load template content
|
|
90
|
-
def load_template
|
|
91
|
-
template_path = File.join(
|
|
92
|
-
File.dirname(__FILE__),
|
|
93
|
-
"..",
|
|
94
|
-
"templates",
|
|
95
|
-
@template_version,
|
|
96
|
-
"policy",
|
|
97
|
-
"template.rb"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
if File.exist?(template_path)
|
|
101
|
-
File.read(template_path)
|
|
102
|
-
else
|
|
103
|
-
default_template
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Load spec template
|
|
108
|
-
def load_spec_template
|
|
109
|
-
spec_path = File.join(
|
|
110
|
-
File.dirname(__FILE__),
|
|
111
|
-
"..",
|
|
112
|
-
"templates",
|
|
113
|
-
@template_version,
|
|
114
|
-
"policy",
|
|
115
|
-
"spec_template.rb"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
if File.exist?(spec_path)
|
|
119
|
-
File.read(spec_path)
|
|
120
|
-
else
|
|
121
|
-
default_spec_template
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Default template
|
|
126
|
-
def default_template
|
|
127
|
-
<<~RUBY
|
|
128
|
-
# Policy class for #{underscore}
|
|
129
|
-
# Defines authorization rules using Pundit
|
|
130
|
-
class #{camelize}Policy
|
|
131
|
-
attr_reader :user, :record
|
|
132
|
-
|
|
133
|
-
def initialize(user, record)
|
|
134
|
-
@user = user
|
|
135
|
-
@record = record
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def index?
|
|
139
|
-
true
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def show?
|
|
143
|
-
true
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def create?
|
|
147
|
-
user.present?
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def new?
|
|
151
|
-
create?
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def update?
|
|
155
|
-
user.present? && record.user == user
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def edit?
|
|
159
|
-
update?
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def destroy?
|
|
163
|
-
user.present? && record.user == user
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
class Scope
|
|
167
|
-
def initialize(user, scope)
|
|
168
|
-
@user = user
|
|
169
|
-
@scope = scope
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def resolve
|
|
173
|
-
scope.all
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
RUBY
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Default spec template
|
|
181
|
-
def default_spec_template
|
|
182
|
-
<<~RUBY
|
|
183
|
-
require 'rails_helper'
|
|
184
|
-
|
|
185
|
-
RSpec.describe #{camelize}Policy do
|
|
186
|
-
let(:user) { User.new }
|
|
187
|
-
let(:record) { #{camelize}.new }
|
|
188
|
-
|
|
189
|
-
subject { described_class.new(user, record) }
|
|
190
|
-
|
|
191
|
-
it { should permit(:index) }
|
|
192
|
-
it { should permit(:show) }
|
|
193
|
-
it { should permit(:new) }
|
|
194
|
-
it { should permit(:create) }
|
|
195
|
-
|
|
196
|
-
context 'when user is the owner' do
|
|
197
|
-
let(:user) { record.user }
|
|
198
|
-
|
|
199
|
-
it { should permit(:update) }
|
|
200
|
-
it { should permit(:destroy) }
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
context 'when user is not the owner' do
|
|
204
|
-
it { should_not permit(:update) }
|
|
205
|
-
it { should_not permit(:destroy) }
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
RUBY
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Apply template variables
|
|
212
|
-
def apply_template(content)
|
|
213
|
-
content
|
|
214
|
-
.gsub("<%= name %>", @name)
|
|
215
|
-
.gsub("<%= name.camelize %>", camelize)
|
|
216
|
-
.gsub("<%= name.underscore %>", underscore)
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
end
|