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