railsforge 1.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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +528 -0
  4. data/bin/railsforge +8 -0
  5. data/lib/railsforge/analyzers/base_analyzer.rb +41 -0
  6. data/lib/railsforge/analyzers/controller_analyzer.rb +83 -0
  7. data/lib/railsforge/analyzers/database_analyzer.rb +55 -0
  8. data/lib/railsforge/analyzers/metrics_analyzer.rb +55 -0
  9. data/lib/railsforge/analyzers/model_analyzer.rb +74 -0
  10. data/lib/railsforge/analyzers/performance_analyzer.rb +161 -0
  11. data/lib/railsforge/analyzers/refactor_analyzer.rb +118 -0
  12. data/lib/railsforge/analyzers/security_analyzer.rb +169 -0
  13. data/lib/railsforge/analyzers/spec_analyzer.rb +58 -0
  14. data/lib/railsforge/api_generator.rb +397 -0
  15. data/lib/railsforge/audit.rb +289 -0
  16. data/lib/railsforge/cli.rb +671 -0
  17. data/lib/railsforge/config.rb +181 -0
  18. data/lib/railsforge/database_analyzer.rb +300 -0
  19. data/lib/railsforge/doctor.rb +250 -0
  20. data/lib/railsforge/feature_generator.rb +560 -0
  21. data/lib/railsforge/generator.rb +313 -0
  22. data/lib/railsforge/generators/base_generator.rb +70 -0
  23. data/lib/railsforge/generators/demo_generator.rb +307 -0
  24. data/lib/railsforge/generators/devops_generator.rb +287 -0
  25. data/lib/railsforge/generators/monitoring_generator.rb +134 -0
  26. data/lib/railsforge/generators/service_generator.rb +122 -0
  27. data/lib/railsforge/generators/stimulus_controller_generator.rb +129 -0
  28. data/lib/railsforge/generators/test_generator.rb +289 -0
  29. data/lib/railsforge/generators/view_component_generator.rb +169 -0
  30. data/lib/railsforge/graph.rb +270 -0
  31. data/lib/railsforge/loader.rb +56 -0
  32. data/lib/railsforge/mailer_generator.rb +191 -0
  33. data/lib/railsforge/plugins/plugin_loader.rb +60 -0
  34. data/lib/railsforge/plugins.rb +30 -0
  35. data/lib/railsforge/profiles/admin_app.yml +49 -0
  36. data/lib/railsforge/profiles/api_only.yml +47 -0
  37. data/lib/railsforge/profiles/blog.yml +47 -0
  38. data/lib/railsforge/profiles/standard.yml +44 -0
  39. data/lib/railsforge/profiles.rb +99 -0
  40. data/lib/railsforge/refactor_analyzer.rb +401 -0
  41. data/lib/railsforge/refactor_controller.rb +277 -0
  42. data/lib/railsforge/refactors/refactor_controller.rb +117 -0
  43. data/lib/railsforge/template_loader.rb +105 -0
  44. data/lib/railsforge/templates/v1/form/spec_template.rb +18 -0
  45. data/lib/railsforge/templates/v1/form/template.rb +28 -0
  46. data/lib/railsforge/templates/v1/job/spec_template.rb +17 -0
  47. data/lib/railsforge/templates/v1/job/template.rb +13 -0
  48. data/lib/railsforge/templates/v1/policy/spec_template.rb +41 -0
  49. data/lib/railsforge/templates/v1/policy/template.rb +57 -0
  50. data/lib/railsforge/templates/v1/presenter/spec_template.rb +12 -0
  51. data/lib/railsforge/templates/v1/presenter/template.rb +13 -0
  52. data/lib/railsforge/templates/v1/query/spec_template.rb +12 -0
  53. data/lib/railsforge/templates/v1/query/template.rb +16 -0
  54. data/lib/railsforge/templates/v1/serializer/spec_template.rb +13 -0
  55. data/lib/railsforge/templates/v1/serializer/template.rb +11 -0
  56. data/lib/railsforge/templates/v1/service/spec_template.rb +12 -0
  57. data/lib/railsforge/templates/v1/service/template.rb +25 -0
  58. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +35 -0
  59. data/lib/railsforge/templates/v1/view_component/template.rb +24 -0
  60. data/lib/railsforge/templates/v2/job/template.rb +49 -0
  61. data/lib/railsforge/templates/v2/query/template.rb +66 -0
  62. data/lib/railsforge/templates/v2/service/spec_template.rb +33 -0
  63. data/lib/railsforge/templates/v2/service/template.rb +71 -0
  64. data/lib/railsforge/templates/v3/job/template.rb +72 -0
  65. data/lib/railsforge/templates/v3/query/spec_template.rb +54 -0
  66. data/lib/railsforge/templates/v3/query/template.rb +115 -0
  67. data/lib/railsforge/templates/v3/service/spec_template.rb +51 -0
  68. data/lib/railsforge/templates/v3/service/template.rb +84 -0
  69. data/lib/railsforge/version.rb +5 -0
  70. data/lib/railsforge/wizard.rb +265 -0
  71. data/lib/railsforge/wizard_tui.rb +286 -0
  72. data/lib/railsforge.rb +13 -0
  73. metadata +216 -0
@@ -0,0 +1,313 @@
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
@@ -0,0 +1,70 @@
1
+ # Base generator class for RailsForge
2
+ # All generators inherit from this class
3
+
4
+ require 'fileutils'
5
+
6
+ module RailsForge
7
+ module Generators
8
+ # Base class for all generators
9
+ class BaseGenerator
10
+ # Error class for invalid names
11
+ class InvalidNameError < StandardError; end
12
+
13
+ # Default template version
14
+ TEMPLATE_VERSION = "v1".freeze
15
+
16
+ # Initialize generator
17
+ # @param name [String] Name for the generated object
18
+ # @param options [Hash] Generator options
19
+ def initialize(name, options = {})
20
+ @name = name
21
+ @options = options
22
+ @base_path = find_rails_app_path
23
+ end
24
+
25
+ # Find Rails app path
26
+ # @return [String, nil] Rails app root path
27
+ def find_rails_app_path
28
+ path = Dir.pwd
29
+ max_depth = 10
30
+
31
+ max_depth.times do
32
+ return path if File.exist?(File.join(path, "config", "application.rb"))
33
+ parent = File.dirname(path)
34
+ break if parent == path
35
+ path = parent
36
+ end
37
+
38
+ nil
39
+ end
40
+
41
+ # Validate name
42
+ # @param name [String] Name to validate
43
+ # @param pattern [Regexp] Pattern to match
44
+ # @param error_class [Class] Error class to raise
45
+ def validate_name!(name, pattern, error_class)
46
+ raise error_class, "#{self.class.name.split('::').last} name cannot be empty" if name.nil? || name.strip.empty?
47
+ raise error_class, "Name must match pattern: #{pattern}" unless name =~ pattern
48
+ end
49
+
50
+ # Convert to underscore
51
+ # @return [String] Underscored name
52
+ def underscore
53
+ @name.to_s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
54
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
55
+ .downcase
56
+ end
57
+
58
+ # Convert to camelize
59
+ # @return [String] Camelized name
60
+ def camelize
61
+ @name.to_s.split('_').map(&:capitalize).join
62
+ end
63
+
64
+ # Generate the file
65
+ def generate
66
+ raise NotImplementedError, "Subclasses must implement #generate"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,307 @@
1
+ # Demo project generator for RailsForge
2
+ # Creates a complete demo Rails project
3
+
4
+ require 'fileutils'
5
+
6
+ module RailsForge
7
+ module Generators
8
+ # Demo generator creates a sample Rails project
9
+ class DemoGenerator < BaseGenerator
10
+ # Initialize the generator
11
+ def initialize(name, options = {})
12
+ super(name, options)
13
+ end
14
+
15
+ # Generate demo project
16
+ def generate
17
+ return "Not in a Rails application directory" unless @base_path
18
+
19
+ results = []
20
+ results << create_readme
21
+ results << create_sample_services
22
+ results << create_sample_queries
23
+ results << create_sample_jobs
24
+ results << create_sample_serializers
25
+ results << create_sample_policies
26
+ results << create_sample_forms
27
+
28
+ results.join("\n")
29
+ end
30
+
31
+ private
32
+
33
+ # Create README.md
34
+ def create_readme
35
+ readme_path = File.join(@base_path, "README.md")
36
+ return "Skipping README (already exists)" if File.exist?(readme_path)
37
+
38
+ content = <<~MD
39
+ # #{@name.titleize}
40
+
41
+ Generated with RailsForge
42
+
43
+ ## Setup
44
+
45
+ ```bash
46
+ bundle install
47
+ rails db:create db:migrate
48
+ rails server
49
+ ```
50
+
51
+ ## Available Generators
52
+
53
+ Run `railsforge generate --help` to see all available generators.
54
+
55
+ ### Services
56
+ ```bash
57
+ railsforge generate service UserService
58
+ ```
59
+
60
+ ### Queries
61
+ ```bash
62
+ railsforge generate query FindUsers
63
+ ```
64
+
65
+ ### Jobs
66
+ ```bash
67
+ railsforge generate job ProcessData
68
+ ```
69
+
70
+ ## Analyzers
71
+
72
+ Run code analysis:
73
+ ```bash
74
+ railsforge analyze
75
+ railsforge analyze security
76
+ railsforge analyze performance
77
+ ```
78
+
79
+ ## Project Structure
80
+
81
+ - `app/services/` - Service objects
82
+ - `app/queries/` - Query objects
83
+ - `app/jobs/` - Background jobs
84
+ - `app/policies/` - Policy objects
85
+ - `app/forms/` - Form objects
86
+ - `app/serializers/` - JSON serializers
87
+ MD
88
+
89
+ File.write(readme_path, content)
90
+ "Created README.md"
91
+ end
92
+
93
+ # Create sample services
94
+ def create_sample_services
95
+ service_dir = File.join(@base_path, "app", "services")
96
+ FileUtils.mkdir_p(service_dir)
97
+
98
+ samples = %w[user_service notification_service payment_service]
99
+ created = []
100
+
101
+ samples.each do |name|
102
+ path = File.join(service_dir, "#{name}.rb")
103
+ next if File.exist?(path)
104
+
105
+ File.write(path, service_template(name.classify))
106
+ created << name
107
+ end
108
+
109
+ created.any? ? "Created #{created.count} sample services" : "Services already exist"
110
+ end
111
+
112
+ # Create sample queries
113
+ def create_sample_queries
114
+ query_dir = File.join(@base_path, "app", "queries")
115
+ FileUtils.mkdir_p(query_dir)
116
+
117
+ samples = %w[find_active_users find_recent_orders]
118
+ created = []
119
+
120
+ samples.each do |name|
121
+ path = File.join(query_dir, "#{name}.rb")
122
+ next if File.exist?(path)
123
+
124
+ File.write(path, query_template(name.classify))
125
+ created << name
126
+ end
127
+
128
+ created.any? ? "Created #{created.count} sample queries" : "Queries already exist"
129
+ end
130
+
131
+ # Create sample jobs
132
+ def create_sample_jobs
133
+ job_dir = File.join(@base_path, "app", "jobs")
134
+ FileUtils.mkdir_p(job_dir)
135
+
136
+ samples = %w[process_order_job send_email_job]
137
+ created = []
138
+
139
+ samples.each do |name|
140
+ path = File.join(job_dir, "#{name}.rb")
141
+ next if File.exist?(path)
142
+
143
+ File.write(path, job_template(name.classify))
144
+ created << name
145
+ end
146
+
147
+ created.any? ? "Created #{created.count} sample jobs" : "Jobs already exist"
148
+ end
149
+
150
+ # Create sample serializers
151
+ def create_sample_serializers
152
+ serializer_dir = File.join(@base_path, "app", "serializers")
153
+ FileUtils.mkdir_p(serializer_dir)
154
+
155
+ samples = %w[user_serializer article_serializer]
156
+ created = []
157
+
158
+ samples.each do |name|
159
+ path = File.join(serializer_dir, "#{name}.rb")
160
+ next if File.exist?(path)
161
+
162
+ File.write(path, serializer_template(name.classify))
163
+ created << name
164
+ end
165
+
166
+ created.any? ? "Created #{created.count} sample serializers" : "Serializers already exist"
167
+ end
168
+
169
+ # Create sample policies
170
+ def create_sample_policies
171
+ policy_dir = File.join(@base_path, "app", "policies")
172
+ FileUtils.mkdir_p(policy_dir)
173
+
174
+ samples = %w[user_policy article_policy]
175
+ created = []
176
+
177
+ samples.each do |name|
178
+ path = File.join(policy_dir, "#{name}.rb")
179
+ next if File.exist?(path)
180
+
181
+ File.write(path, policy_template(name.classify))
182
+ created << name
183
+ end
184
+
185
+ created.any? ? "Created #{created.count} sample policies" : "Policies already exist"
186
+ end
187
+
188
+ # Create sample forms
189
+ def create_sample_forms
190
+ form_dir = File.join(@base_path, "app", "forms")
191
+ FileUtils.mkdir_p(form_dir)
192
+
193
+ samples = %w[contact_form signup_form]
194
+ created = []
195
+
196
+ samples.each do |name|
197
+ path = File.join(form_dir, "#{name}.rb")
198
+ next if File.exist?(path)
199
+
200
+ File.write(path, form_template(name.classify))
201
+ created << name
202
+ end
203
+
204
+ created.any? ? "Created #{created.count} sample forms" : "Forms already exist"
205
+ end
206
+
207
+ # Templates
208
+ def service_template(name)
209
+ <<~RUBY
210
+ class #{name}
211
+ def initialize(params = {})
212
+ @params = params
213
+ end
214
+
215
+ def call
216
+ # TODO: Implement service logic
217
+ { success: true }
218
+ end
219
+
220
+ def self.call(params = {})
221
+ new(params).call
222
+ end
223
+ end
224
+ RUBY
225
+ end
226
+
227
+ def query_template(name)
228
+ <<~RUBY
229
+ class #{name}
230
+ def initialize(relation = nil)
231
+ @relation = relation || default_scope
232
+ end
233
+
234
+ def call
235
+ @relation
236
+ end
237
+
238
+ private
239
+
240
+ def default_scope
241
+ # TODO: Replace with actual model
242
+ OpenStruct.all
243
+ end
244
+ end
245
+ RUBY
246
+ end
247
+
248
+ def job_template(name)
249
+ <<~RUBY
250
+ class #{name} < ApplicationJob
251
+ queue_as :default
252
+
253
+ def perform(*args)
254
+ # TODO: Implement job logic
255
+ end
256
+ end
257
+ RUBY
258
+ end
259
+
260
+ def serializer_template(name)
261
+ <<~RUBY
262
+ class #{name} < ActiveModel::Serializer
263
+ attributes :id, :created_at
264
+
265
+ # TODO: Add attributes
266
+ end
267
+ RUBY
268
+ end
269
+
270
+ def policy_template(name)
271
+ <<~RUBY
272
+ class #{name} < ApplicationPolicy
273
+ def index?
274
+ true
275
+ end
276
+
277
+ def show?
278
+ true
279
+ end
280
+
281
+ def create?
282
+ user.present?
283
+ end
284
+ end
285
+ RUBY
286
+ end
287
+
288
+ def form_template(name)
289
+ <<~RUBY
290
+ class #{name} < ApplicationForm
291
+ attr_accessor :name, :email
292
+
293
+ validates :name, presence: true
294
+ validates :email, presence: true
295
+
296
+ def save
297
+ return false unless valid?
298
+
299
+ # TODO: Implement save logic
300
+ true
301
+ end
302
+ end
303
+ RUBY
304
+ end
305
+ end
306
+ end
307
+ end