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.
- checksums.yaml +4 -4
- data/README.md +105 -444
- 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 +14 -650
- data/lib/railsforge/cli_minimal.rb +8 -55
- data/lib/railsforge/doctor.rb +52 -225
- data/lib/railsforge/formatter.rb +102 -0
- data/lib/railsforge/issue.rb +23 -0
- data/lib/railsforge/loader.rb +4 -64
- data/lib/railsforge/version.rb +1 -1
- metadata +14 -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
data/lib/railsforge/generator.rb
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
# Generator module for RailsForge
|
|
2
|
-
# Handles Rails app creation and folder structure
|
|
3
|
-
|
|
4
|
-
require "fileutils"
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
# Generator module handles Rails app creation
|
|
8
|
-
module Generator
|
|
9
|
-
# Folders to create inside the Rails app
|
|
10
|
-
FOLDERS = %w[
|
|
11
|
-
app/services
|
|
12
|
-
app/queries
|
|
13
|
-
app/policies
|
|
14
|
-
app/forms
|
|
15
|
-
app/presenters
|
|
16
|
-
app/jobs
|
|
17
|
-
app/mailers
|
|
18
|
-
].freeze
|
|
19
|
-
|
|
20
|
-
# Error class for invalid app names
|
|
21
|
-
class InvalidAppNameError < StandardError; end
|
|
22
|
-
|
|
23
|
-
# Validates the app name to ensure it's valid for a Rails project
|
|
24
|
-
# @param name [String] The app name to validate
|
|
25
|
-
# @return [void]
|
|
26
|
-
# @raises [InvalidAppNameError] If name is invalid
|
|
27
|
-
def self.validate_app_name(name)
|
|
28
|
-
if name.nil? || name.strip.empty?
|
|
29
|
-
raise InvalidAppNameError, "App name cannot be empty"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Check for valid Ruby/Rails naming conventions
|
|
33
|
-
unless name =~ /\A[a-z][a-z0-9_]*\z/
|
|
34
|
-
raise InvalidAppNameError, "App name must start with a lowercase letter and contain only letters, numbers, and underscores"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Check for reserved Ruby words that would cause issues
|
|
38
|
-
reserved_words = %w[begin end if else elsif unless case when while until do for
|
|
39
|
-
class module def unless return yield break next redo rescue
|
|
40
|
-
require include extend raise attr attr_reader attr_writer
|
|
41
|
-
attr_accessor lambda proc]
|
|
42
|
-
if reserved_words.include?(name)
|
|
43
|
-
raise InvalidAppNameError, "'#{name}' is a reserved Ruby word and cannot be used as an app name"
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Creates a new Rails app with the specified name
|
|
48
|
-
# @param app_name [String] The name of the Rails app to create
|
|
49
|
-
# @param options [Hash] Optional flags: auth, admin, jobs
|
|
50
|
-
# @return [String] Success message
|
|
51
|
-
def self.create_app(app_name, options = {})
|
|
52
|
-
validate_app_name(app_name)
|
|
53
|
-
|
|
54
|
-
# Load profile if specified
|
|
55
|
-
profile = nil
|
|
56
|
-
if options[:profile]
|
|
57
|
-
profile = RailsForge::Profile.load(options[:profile])
|
|
58
|
-
puts "Using profile: #{profile['name']} - #{profile['description']}"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Check if Rails is installed
|
|
62
|
-
unless system("which rails > /dev/null 2>&1")
|
|
63
|
-
raise "Rails is not installed. Please install Rails first: gem install rails"
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
puts "Creating new Rails app: #{app_name}..."
|
|
67
|
-
|
|
68
|
-
# Create the Rails app using rails new command
|
|
69
|
-
rails_command = "rails new #{app_name} --skip-git --skip-test --skip-system-test"
|
|
70
|
-
unless system(rails_command)
|
|
71
|
-
raise "Failed to create Rails app. Please check your Rails installation."
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Create folders from profile or defaults
|
|
75
|
-
app_path = File.join(Dir.pwd, app_name)
|
|
76
|
-
if profile
|
|
77
|
-
RailsForge::Profile.create_folders(app_path, profile)
|
|
78
|
-
else
|
|
79
|
-
create_folders(app_path)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Merge profile defaults with options
|
|
83
|
-
if profile
|
|
84
|
-
profile_defaults = RailsForge::Profile.defaults(profile)
|
|
85
|
-
options = profile_defaults.merge(options)
|
|
86
|
-
|
|
87
|
-
# Auto-enable features from profile
|
|
88
|
-
profile_features = RailsForge::Profile.default_features(profile)
|
|
89
|
-
options[:auth] = "devise" if profile_features.include?("authentication") && !options[:auth]
|
|
90
|
-
options[:admin] = "activeadmin" if profile_features.include?("admin") && !options[:admin]
|
|
91
|
-
options[:jobs] = "sidekiq" if profile_features.include?("jobs") && !options[:jobs]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Install optional features based on flags
|
|
95
|
-
results = []
|
|
96
|
-
|
|
97
|
-
if options[:auth]
|
|
98
|
-
results << install_auth(app_path, options[:auth])
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
if options[:admin]
|
|
102
|
-
results << install_admin(app_path, options[:admin])
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
if options[:jobs]
|
|
106
|
-
results << install_jobs(app_path, options[:jobs])
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Build success message
|
|
110
|
-
message = "Rails app '#{app_name}' created successfully!"
|
|
111
|
-
message += "\n\n" + results.join("\n") if results.any?
|
|
112
|
-
message
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Installs authentication (Devise)
|
|
116
|
-
# @param app_path [String] Path to the Rails app
|
|
117
|
-
# @param auth_type [String] Type of auth (devise or jwt)
|
|
118
|
-
# @return [String] Success message
|
|
119
|
-
def self.install_auth(app_path, auth_type)
|
|
120
|
-
puts "\n→ Installing authentication: #{auth_type}..."
|
|
121
|
-
|
|
122
|
-
if auth_type == "devise"
|
|
123
|
-
# Add devise to Gemfile
|
|
124
|
-
gemfile_path = File.join(app_path, "Gemfile")
|
|
125
|
-
if File.exist?(gemfile_path)
|
|
126
|
-
content = File.read(gemfile_path)
|
|
127
|
-
unless content.include?("gem 'devise'")
|
|
128
|
-
File.open(gemfile_path, "a") do |f|
|
|
129
|
-
f.puts "\n# Authentication\ngem 'devise'"
|
|
130
|
-
end
|
|
131
|
-
puts " ✓ Added devise to Gemfile"
|
|
132
|
-
else
|
|
133
|
-
puts " ✓ devise already in Gemfile"
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Install devise
|
|
138
|
-
Dir.chdir(app_path) do
|
|
139
|
-
system("bundle install 2>/dev/null")
|
|
140
|
-
system("rails generate devise:install 2>/dev/null")
|
|
141
|
-
system("rails generate devise User 2>/dev/null")
|
|
142
|
-
system("rails db:migrate 2>/dev/null")
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
"✅ Authentication (Devise) installed successfully!"
|
|
146
|
-
else
|
|
147
|
-
"⚠️ JWT authentication not implemented yet. Use --auth devise"
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# Installs admin panel (ActiveAdmin)
|
|
152
|
-
# @param app_path [String] Path to the Rails app
|
|
153
|
-
# @param admin_type [String] Type of admin (activeadmin or dashboard)
|
|
154
|
-
# @return [String] Success message
|
|
155
|
-
def self.install_admin(app_path, admin_type)
|
|
156
|
-
puts "\n→ Installing admin panel: #{admin_type}..."
|
|
157
|
-
|
|
158
|
-
if admin_type == "activeadmin"
|
|
159
|
-
# Add activeadmin and its dependencies to Gemfile
|
|
160
|
-
gemfile_path = File.join(app_path, "Gemfile")
|
|
161
|
-
if File.exist?(gemfile_path)
|
|
162
|
-
content = File.read(gemfile_path)
|
|
163
|
-
unless content.include?("gem 'activeadmin'")
|
|
164
|
-
File.open(gemfile_path, "a") do |f|
|
|
165
|
-
f.puts "\n# Admin panel\ngem 'activeadmin'\ngem 'devise'"
|
|
166
|
-
end
|
|
167
|
-
puts " ✓ Added activeadmin to Gemfile"
|
|
168
|
-
else
|
|
169
|
-
puts " ✓ activeadmin already in Gemfile"
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Install activeadmin
|
|
174
|
-
Dir.chdir(app_path) do
|
|
175
|
-
system("bundle install 2>/dev/null")
|
|
176
|
-
system("rails generate active_admin:install 2>/dev/null")
|
|
177
|
-
system("rails db:migrate 2>/dev/null")
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Create admin user model if needed
|
|
181
|
-
admin_user_path = File.join(app_path, "app", "models", "admin_user.rb")
|
|
182
|
-
unless File.exist?(admin_user_path)
|
|
183
|
-
File.write(admin_user_path, <<~RUBY)
|
|
184
|
-
# AdminUser model for ActiveAdmin
|
|
185
|
-
class AdminUser < ApplicationRecord
|
|
186
|
-
# Include default devise modules
|
|
187
|
-
devise :database_authenticatable, :recoverable, :rememberable, :validatable
|
|
188
|
-
end
|
|
189
|
-
RUBY
|
|
190
|
-
puts " ✓ Created AdminUser model"
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
"✅ Admin panel (ActiveAdmin) installed successfully!"
|
|
194
|
-
else
|
|
195
|
-
"⚠️ Dashboard admin not implemented yet. Use --admin activeadmin"
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# Installs background jobs (Sidekiq + Redis)
|
|
200
|
-
# @param app_path [String] Path to the Rails app
|
|
201
|
-
# @param jobs_type [String] Type of jobs (sidekiq)
|
|
202
|
-
# @return [String] Success message
|
|
203
|
-
def self.install_jobs(app_path, jobs_type)
|
|
204
|
-
puts "\n→ Installing background jobs: #{jobs_type}..."
|
|
205
|
-
|
|
206
|
-
if jobs_type == "sidekiq"
|
|
207
|
-
# Add sidekiq to Gemfile
|
|
208
|
-
gemfile_path = File.join(app_path, "Gemfile")
|
|
209
|
-
if File.exist?(gemfile_path)
|
|
210
|
-
content = File.read(gemfile_path)
|
|
211
|
-
unless content.include?("gem 'sidekiq'")
|
|
212
|
-
File.open(gemfile_path, "a") do |f|
|
|
213
|
-
f.puts "\n# Background jobs\ngem 'sidekiq'\ngem 'redis', '~> 4.0.1'"
|
|
214
|
-
end
|
|
215
|
-
puts " ✓ Added sidekiq to Gemfile"
|
|
216
|
-
else
|
|
217
|
-
puts " ✓ sidekiq already in Gemfile"
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
# Install dependencies
|
|
222
|
-
Dir.chdir(app_path) do
|
|
223
|
-
system("bundle install 2>/dev/null")
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# Create config file for sidekiq
|
|
227
|
-
config_path = File.join(app_path, "config", "sidekiq.yml")
|
|
228
|
-
unless File.exist?(config_path)
|
|
229
|
-
FileUtils.mkdir_p(File.dirname(config_path))
|
|
230
|
-
File.write(config_path, <<~YAML)
|
|
231
|
-
:concurrency: 5
|
|
232
|
-
:queues:
|
|
233
|
-
- default
|
|
234
|
-
- mailers
|
|
235
|
-
YAML
|
|
236
|
-
puts " ✓ Created config/sidekiq.yml"
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Create initializers
|
|
240
|
-
init_path = File.join(app_path, "config", "initializers", "sidekiq.rb")
|
|
241
|
-
unless File.exist?(init_path)
|
|
242
|
-
File.write(init_path, <<~RUBY)
|
|
243
|
-
# Sidekiq configuration
|
|
244
|
-
Sidekiq.configure_server do |config|
|
|
245
|
-
config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/1') }
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
Sidekiq.configure_client do |config|
|
|
249
|
-
config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/1') }
|
|
250
|
-
end
|
|
251
|
-
RUBY
|
|
252
|
-
puts " ✓ Created config/initializers/sidekiq.rb"
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
# Update application.rb to use sidekiq for active job
|
|
256
|
-
app_rb_path = File.join(app_path, "config", "application.rb")
|
|
257
|
-
if File.exist?(app_rb_path)
|
|
258
|
-
content = File.read(app_rb_path)
|
|
259
|
-
unless content.include?("config.active_job.queue_adapter = :sidekiq")
|
|
260
|
-
content.gsub!(/config\.active_job\.queue_adapter = :.*/, "config.active_job.queue_adapter = :sidekiq")
|
|
261
|
-
File.write(app_rb_path, content)
|
|
262
|
-
puts " ✓ Configured ActiveJob to use Sidekiq"
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# Create example job
|
|
267
|
-
jobs_dir = File.join(app_path, "app", "jobs")
|
|
268
|
-
FileUtils.mkdir_p(jobs_dir)
|
|
269
|
-
example_job_path = File.join(jobs_dir, "example_job.rb")
|
|
270
|
-
unless File.exist?(example_job_path)
|
|
271
|
-
File.write(example_job_path, <<~RUBY)
|
|
272
|
-
# Example job using Sidekiq
|
|
273
|
-
class ExampleJob < ApplicationJob
|
|
274
|
-
queue_as :default
|
|
275
|
-
|
|
276
|
-
def perform(*args)
|
|
277
|
-
# Do something later
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
RUBY
|
|
281
|
-
puts " ✓ Created example job"
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
"✅ Background jobs (Sidekiq + Redis) installed successfully!"
|
|
285
|
-
else
|
|
286
|
-
"⚠️ Unknown jobs type: #{jobs_type}. Use --jobs sidekiq"
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
# Creates the additional folder structure inside the Rails app
|
|
291
|
-
# @param app_name [String] The name of the Rails app
|
|
292
|
-
# @return [void]
|
|
293
|
-
def self.create_folders(app_name)
|
|
294
|
-
app_path = File.join(Dir.pwd, app_name)
|
|
295
|
-
|
|
296
|
-
FOLDERS.each do |folder|
|
|
297
|
-
folder_path = File.join(app_path, folder)
|
|
298
|
-
|
|
299
|
-
if Dir.exist?(folder_path)
|
|
300
|
-
puts " Skipping #{folder} (already exists)"
|
|
301
|
-
else
|
|
302
|
-
Dir.mkdir(folder_path)
|
|
303
|
-
puts " Created #{folder}"
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
# Placeholder for gem functionality
|
|
310
|
-
def self.greet
|
|
311
|
-
"Welcome to RailsForge #{VERSION}!"
|
|
312
|
-
end
|
|
313
|
-
end
|
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
# API Generator for RailsForge
|
|
2
|
-
# Generates API resources with controllers, serializers, policies, services, and queries
|
|
3
|
-
|
|
4
|
-
require_relative 'base_generator'
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
module Generators
|
|
8
|
-
# ApiGenerator creates API resources
|
|
9
|
-
class ApiGenerator < BaseGenerator
|
|
10
|
-
# Error class for invalid resource names
|
|
11
|
-
class InvalidResourceNameError < StandardError; end
|
|
12
|
-
|
|
13
|
-
# Initialize the generator
|
|
14
|
-
# @param name [String] Resource name
|
|
15
|
-
# @param options [Hash] Generator options
|
|
16
|
-
def initialize(name, options = {})
|
|
17
|
-
super(name, options)
|
|
18
|
-
@namespace = options[:namespace] || "api"
|
|
19
|
-
@version = options[:version] || "v1"
|
|
20
|
-
@with_spec = options.fetch(:with_spec, true)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Generate API resource files
|
|
24
|
-
# @return [String] Success message
|
|
25
|
-
def generate
|
|
26
|
-
return "Not in a Rails application directory" unless @base_path
|
|
27
|
-
|
|
28
|
-
validate_name!(@name)
|
|
29
|
-
|
|
30
|
-
results = []
|
|
31
|
-
results << generate_controller
|
|
32
|
-
results << generate_serializer
|
|
33
|
-
results << generate_policy
|
|
34
|
-
results << generate_service
|
|
35
|
-
results << generate_query
|
|
36
|
-
results << generate_request_spec if @with_spec
|
|
37
|
-
|
|
38
|
-
"API resource '#{@name}' generated successfully with #{results.count} files!\n" + results.join("\n")
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Class method for CLI
|
|
42
|
-
def self.generate(resource_name, with_spec: true, version: "v1", namespace: "api")
|
|
43
|
-
new(resource_name, with_spec: with_spec, version: version, namespace: namespace).generate
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
# Validate resource name
|
|
49
|
-
def validate_name!(name)
|
|
50
|
-
raise InvalidResourceNameError, "Resource name cannot be empty" if name.nil? || name.strip.empty?
|
|
51
|
-
raise InvalidResourceNameError, "Name must match pattern: /\\A[A-Z][a-zA-Z0-9]*\\z/" unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Pluralize helper
|
|
55
|
-
def pluralize(word)
|
|
56
|
-
return word + 's' unless word.end_with?('s')
|
|
57
|
-
return word + 'es' if word.end_with?('sh') || word.end_with?('ch')
|
|
58
|
-
word + 's'
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Generate controller
|
|
62
|
-
def generate_controller
|
|
63
|
-
controller_dir = File.join(@base_path, "app", "controllers", @namespace, @version)
|
|
64
|
-
FileUtils.mkdir_p(controller_dir)
|
|
65
|
-
|
|
66
|
-
resource_plural = pluralize(@name)
|
|
67
|
-
resource_underscore = underscore
|
|
68
|
-
resource_underscore_plural = pluralize(resource_underscore)
|
|
69
|
-
|
|
70
|
-
file_name = "#{resource_underscore}_controller.rb"
|
|
71
|
-
file_path = File.join(controller_dir, file_name)
|
|
72
|
-
|
|
73
|
-
return " Skipping controller (already exists)" if File.exist?(file_path)
|
|
74
|
-
|
|
75
|
-
content = <<~RUBY
|
|
76
|
-
# API Controller for #{@name}
|
|
77
|
-
# Version: #{@version}
|
|
78
|
-
# Namespace: #{@namespace}
|
|
79
|
-
#
|
|
80
|
-
# Generates standard CRUD actions for REST API
|
|
81
|
-
class #{resource_plural}Controller < ApplicationController
|
|
82
|
-
before_action :set_#{resource_underscore}, only: [:show, :update, :destroy]
|
|
83
|
-
before_action :authenticate_user!, unless: :devise_controller?
|
|
84
|
-
|
|
85
|
-
# GET /#{resource_underscore_plural}
|
|
86
|
-
def index
|
|
87
|
-
@#{resource_underscore_plural} = #{@name}Query.call
|
|
88
|
-
render json: #{@name}Serializer.new(@#{resource_underscore_plural}).serializable_hash
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# GET /#{resource_underscore_plural}/:id
|
|
92
|
-
def show
|
|
93
|
-
render json: #{@name}Serializer.new(@#{resource_underscore}).serializable_hash
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# POST /#{resource_underscore_plural}
|
|
97
|
-
def create
|
|
98
|
-
@result = Create#{@name}Service.call(#{resource_underscore}_params)
|
|
99
|
-
|
|
100
|
-
if @result.success?
|
|
101
|
-
render json: #{@name}Serializer.new(@result.#{resource_underscore}).serializable_hash, status: :created
|
|
102
|
-
else
|
|
103
|
-
render json: { errors: @result.errors }, status: :unprocessable_entity
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# PATCH/PUT /#{resource_underscore_plural}/:id
|
|
108
|
-
def update
|
|
109
|
-
@result = Update#{@name}Service.call(@#{resource_underscore}, #{resource_underscore}_params)
|
|
110
|
-
|
|
111
|
-
if @result.success?
|
|
112
|
-
render json: #{@name}Serializer.new(@result.#{resource_underscore}).serializable_hash
|
|
113
|
-
else
|
|
114
|
-
render json: { errors: @result.errors }, status: :unprocessable_entity
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# DELETE /#{resource_underscore_plural}/:id
|
|
119
|
-
def destroy
|
|
120
|
-
@#{resource_underscore}.destroy
|
|
121
|
-
head :no_content
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
def set_#{resource_underscore}
|
|
127
|
-
@#{resource_underscore} = #{@name}.find(params[:id])
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def #{resource_underscore}_params
|
|
131
|
-
params.require(:#{resource_underscore}).permit(:name)
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
RUBY
|
|
135
|
-
|
|
136
|
-
File.write(file_path, content)
|
|
137
|
-
" Created app/controllers/#{@namespace}/#{@version}/#{file_name}"
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Generate serializer
|
|
141
|
-
def generate_serializer
|
|
142
|
-
serializer_dir = File.join(@base_path, "app", "serializers")
|
|
143
|
-
FileUtils.mkdir_p(serializer_dir)
|
|
144
|
-
|
|
145
|
-
file_name = "#{underscore}_serializer.rb"
|
|
146
|
-
file_path = File.join(serializer_dir, file_name)
|
|
147
|
-
|
|
148
|
-
return " Skipping serializer (already exists)" if File.exist?(file_path)
|
|
149
|
-
|
|
150
|
-
content = <<~RUBY
|
|
151
|
-
# Serializer for #{@name}
|
|
152
|
-
class #{@name}Serializer < ApplicationSerializer
|
|
153
|
-
attributes :id, :name, :created_at, :updated_at
|
|
154
|
-
end
|
|
155
|
-
RUBY
|
|
156
|
-
|
|
157
|
-
File.write(file_path, content)
|
|
158
|
-
" Created app/serializers/#{file_name}"
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# Generate policy
|
|
162
|
-
def generate_policy
|
|
163
|
-
policy_dir = File.join(@base_path, "app", "policies")
|
|
164
|
-
FileUtils.mkdir_p(policy_dir)
|
|
165
|
-
|
|
166
|
-
file_name = "#{underscore}_policy.rb"
|
|
167
|
-
file_path = File.join(policy_dir, file_name)
|
|
168
|
-
|
|
169
|
-
return " Skipping policy (already exists)" if File.exist?(file_path)
|
|
170
|
-
|
|
171
|
-
content = <<~RUBY
|
|
172
|
-
# Policy for #{@name}
|
|
173
|
-
class #{@name}Policy
|
|
174
|
-
attr_reader :user, :record
|
|
175
|
-
|
|
176
|
-
def initialize(user, record)
|
|
177
|
-
@user = user
|
|
178
|
-
@record = record
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def index?
|
|
182
|
-
true
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def show?
|
|
186
|
-
true
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def create?
|
|
190
|
-
user.present?
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def update?
|
|
194
|
-
user.present?
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def destroy?
|
|
198
|
-
user.present?
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
class Scope
|
|
202
|
-
def initialize(user, scope)
|
|
203
|
-
@user = user
|
|
204
|
-
@scope = scope
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def resolve
|
|
208
|
-
scope.all
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
RUBY
|
|
213
|
-
|
|
214
|
-
File.write(file_path, content)
|
|
215
|
-
" Created app/policies/#{file_name}"
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Generate service
|
|
219
|
-
def generate_service
|
|
220
|
-
service_dir = File.join(@base_path, "app", "services")
|
|
221
|
-
FileUtils.mkdir_p(service_dir)
|
|
222
|
-
res_underscore = underscore
|
|
223
|
-
|
|
224
|
-
# Create service
|
|
225
|
-
file_name = "create_#{res_underscore}_service.rb"
|
|
226
|
-
file_path = File.join(service_dir, file_name)
|
|
227
|
-
|
|
228
|
-
unless File.exist?(file_path)
|
|
229
|
-
content = <<~RUBY
|
|
230
|
-
# Service for creating #{@name}
|
|
231
|
-
class Create#{@name}Service
|
|
232
|
-
def initialize(params)
|
|
233
|
-
@params = params
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def call
|
|
237
|
-
@#{res_underscore} = #{@name}.create!(@params)
|
|
238
|
-
Result.new(success: true, #{res_underscore}: @#{res_underscore})
|
|
239
|
-
rescue => e
|
|
240
|
-
Result.new(success: false, errors: e.message)
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
class Result
|
|
244
|
-
attr_reader :#{res_underscore}, :errors
|
|
245
|
-
def initialize(success:, #{res_underscore}: nil, errors: nil)
|
|
246
|
-
@success = success
|
|
247
|
-
@#{res_underscore} = #{res_underscore}
|
|
248
|
-
@errors = errors
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
def success?
|
|
252
|
-
@success
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
RUBY
|
|
257
|
-
|
|
258
|
-
File.write(file_path, content)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# Update service
|
|
262
|
-
file_name = "update_#{res_underscore}_service.rb"
|
|
263
|
-
file_path = File.join(service_dir, file_name)
|
|
264
|
-
|
|
265
|
-
unless File.exist?(file_path)
|
|
266
|
-
content = <<~RUBY
|
|
267
|
-
# Service for updating #{@name}
|
|
268
|
-
class Update#{@name}Service
|
|
269
|
-
def initialize(#{res_underscore}, params)
|
|
270
|
-
@#{res_underscore} = #{res_underscore}
|
|
271
|
-
@params = params
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
def call
|
|
275
|
-
if @#{res_underscore}.update(@params)
|
|
276
|
-
Result.new(success: true, #{res_underscore}: @#{res_underscore})
|
|
277
|
-
else
|
|
278
|
-
Result.new(success: false, errors: @#{res_underscore}.errors.full_messages)
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
class Result
|
|
283
|
-
attr_reader :#{res_underscore}, :errors
|
|
284
|
-
def initialize(success:, #{res_underscore}: nil, errors: nil)
|
|
285
|
-
@success = success
|
|
286
|
-
@#{res_underscore} = #{res_underscore}
|
|
287
|
-
@errors = errors
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
def success?
|
|
291
|
-
@success
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
end
|
|
295
|
-
RUBY
|
|
296
|
-
|
|
297
|
-
File.write(file_path, content)
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
" Created app/services/create_#{res_underscore}_service.rb and update_#{res_underscore}_service.rb"
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
# Generate query
|
|
304
|
-
def generate_query
|
|
305
|
-
query_dir = File.join(@base_path, "app", "queries")
|
|
306
|
-
FileUtils.mkdir_p(query_dir)
|
|
307
|
-
|
|
308
|
-
file_name = "find_#{underscore}.rb"
|
|
309
|
-
file_path = File.join(query_dir, file_name)
|
|
310
|
-
|
|
311
|
-
return " Skipping query (already exists)" if File.exist?(file_path)
|
|
312
|
-
|
|
313
|
-
content = <<~RUBY
|
|
314
|
-
# Query for finding #{@name}
|
|
315
|
-
class Find#{@name}
|
|
316
|
-
def initialize(scope: nil)
|
|
317
|
-
@scope = scope || #{@name}.all
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def call
|
|
321
|
-
@scope
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
RUBY
|
|
325
|
-
|
|
326
|
-
File.write(file_path, content)
|
|
327
|
-
" Created app/queries/#{file_name}"
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
# Generate request spec
|
|
331
|
-
def generate_request_spec
|
|
332
|
-
spec_dir = File.join(@base_path, "spec", "requests", @namespace, @version)
|
|
333
|
-
FileUtils.mkdir_p(spec_dir)
|
|
334
|
-
|
|
335
|
-
resource_plural = pluralize(@name)
|
|
336
|
-
resource_underscore = underscore
|
|
337
|
-
|
|
338
|
-
file_name = "#{resource_underscore}_spec.rb"
|
|
339
|
-
file_path = File.join(spec_dir, file_name)
|
|
340
|
-
|
|
341
|
-
return " Skipping spec (already exists)" if File.exist?(file_path)
|
|
342
|
-
|
|
343
|
-
content = <<~RUBY
|
|
344
|
-
require 'rails_helper'
|
|
345
|
-
|
|
346
|
-
RSpec.describe "#{@namespace.capitalize}::#{@version.capitalize}::#{resource_plural}Controller" do
|
|
347
|
-
let(:user) { User.create!(name: "Test", email: "test@example.com") }
|
|
348
|
-
let(:#{resource_underscore}) { #{@name}.create!(name: "Test #{@name}") }
|
|
349
|
-
|
|
350
|
-
before do
|
|
351
|
-
sign_in user
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
describe "GET /index" do
|
|
355
|
-
it "returns a success response" do
|
|
356
|
-
get :index
|
|
357
|
-
expect(response).to be_successful
|
|
358
|
-
end
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
describe "GET /show" do
|
|
362
|
-
it "returns a success response" do
|
|
363
|
-
get :show, params: { id: #{resource_underscore}.id }
|
|
364
|
-
expect(response).to be_successful
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
describe "POST /create" do
|
|
369
|
-
it "creates a new #{resource_underscore}" do
|
|
370
|
-
expect {
|
|
371
|
-
post :create, params: { #{resource_underscore}: { name: "New #{@name}" } }
|
|
372
|
-
}.to change(#{@name}, :count).by(1)
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
describe "DELETE /destroy" do
|
|
377
|
-
it "deletes the #{resource_underscore}" do
|
|
378
|
-
#{resource_underscore}
|
|
379
|
-
expect {
|
|
380
|
-
delete :destroy, params: { id: #{resource_underscore}.id }
|
|
381
|
-
}.to change(#{@name}, :count).by(-1)
|
|
382
|
-
end
|
|
383
|
-
end
|
|
384
|
-
end
|
|
385
|
-
RUBY
|
|
386
|
-
|
|
387
|
-
File.write(file_path, content)
|
|
388
|
-
" Created spec/requests/#{@namespace}/#{@version}/#{file_name}"
|
|
389
|
-
end
|
|
390
|
-
end
|
|
391
|
-
end
|
|
392
|
-
end
|