Module: RailsForge::Generator
- Defined in:
- lib/railsforge/generator.rb
Overview
Generator module handles Rails app creation
Defined Under Namespace
Classes: InvalidAppNameError
Constant Summary collapse
- FOLDERS =
Folders to create inside the Rails app
%w[ app/services app/queries app/policies app/forms app/presenters app/jobs app/mailers ].freeze
Class Method Summary collapse
-
.create_app(app_name, options = {}) ⇒ String
Creates a new Rails app with the specified name.
-
.create_folders(app_name) ⇒ void
Creates the additional folder structure inside the Rails app.
-
.install_admin(app_path, admin_type) ⇒ String
Installs admin panel (ActiveAdmin).
-
.install_auth(app_path, auth_type) ⇒ String
Installs authentication (Devise).
-
.install_jobs(app_path, jobs_type) ⇒ String
Installs background jobs (Sidekiq + Redis).
-
.validate_app_name(name) ⇒ void
Validates the app name to ensure it’s valid for a Rails project.
Class Method Details
.create_app(app_name, options = {}) ⇒ String
Creates a new Rails app with the specified name
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/railsforge/generator.rb', line 51 def self.create_app(app_name, = {}) validate_app_name(app_name) # Load profile if specified profile = nil if [:profile] profile = RailsForge::Profile.load([:profile]) puts "Using profile: #{profile['name']} - #{profile['description']}" end # Check if Rails is installed unless system("which rails > /dev/null 2>&1") raise "Rails is not installed. Please install Rails first: gem install rails" end puts "Creating new Rails app: #{app_name}..." # Create the Rails app using rails new command rails_command = "rails new #{app_name} --skip-git --skip-test --skip-system-test" unless system(rails_command) raise "Failed to create Rails app. Please check your Rails installation." end # Create folders from profile or defaults app_path = File.join(Dir.pwd, app_name) if profile RailsForge::Profile.create_folders(app_path, profile) else create_folders(app_path) end # Merge profile defaults with options if profile profile_defaults = RailsForge::Profile.defaults(profile) = profile_defaults.merge() # Auto-enable features from profile profile_features = RailsForge::Profile.default_features(profile) [:auth] = "devise" if profile_features.include?("authentication") && ![:auth] [:admin] = "activeadmin" if profile_features.include?("admin") && ![:admin] [:jobs] = "sidekiq" if profile_features.include?("jobs") && ![:jobs] end # Install optional features based on flags results = [] if [:auth] results << install_auth(app_path, [:auth]) end if [:admin] results << install_admin(app_path, [:admin]) end if [:jobs] results << install_jobs(app_path, [:jobs]) end # Build success message = "Rails app '#{app_name}' created successfully!" += "\n\n" + results.join("\n") if results.any? end |
.create_folders(app_name) ⇒ void
This method returns an undefined value.
Creates the additional folder structure inside the Rails app
293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/railsforge/generator.rb', line 293 def self.create_folders(app_name) app_path = File.join(Dir.pwd, app_name) FOLDERS.each do |folder| folder_path = File.join(app_path, folder) if Dir.exist?(folder_path) puts " Skipping #{folder} (already exists)" else Dir.mkdir(folder_path) puts " Created #{folder}" end end end |
.install_admin(app_path, admin_type) ⇒ String
Installs admin panel (ActiveAdmin)
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/railsforge/generator.rb', line 155 def self.install_admin(app_path, admin_type) puts "\n→ Installing admin panel: #{admin_type}..." if admin_type == "activeadmin" # Add activeadmin and its dependencies to Gemfile gemfile_path = File.join(app_path, "Gemfile") if File.exist?(gemfile_path) content = File.read(gemfile_path) unless content.include?("gem 'activeadmin'") File.open(gemfile_path, "a") do |f| f.puts "\n# Admin panel\ngem 'activeadmin'\ngem 'devise'" end puts " ✓ Added activeadmin to Gemfile" else puts " ✓ activeadmin already in Gemfile" end end # Install activeadmin Dir.chdir(app_path) do system("bundle install 2>/dev/null") system("rails generate active_admin:install 2>/dev/null") system("rails db:migrate 2>/dev/null") end # Create admin user model if needed admin_user_path = File.join(app_path, "app", "models", "admin_user.rb") unless File.exist?(admin_user_path) File.write(admin_user_path, " # AdminUser model for ActiveAdmin\n class AdminUser < ApplicationRecord\n # Include default devise modules\n devise :database_authenticatable, :recoverable, :rememberable, :validatable\n end\n RUBY\n puts \" \u2713 Created AdminUser model\"\n end\n\n \"\u2705 Admin panel (ActiveAdmin) installed successfully!\"\n else\n \"\u26A0\uFE0F Dashboard admin not implemented yet. Use --admin activeadmin\"\n end\nend\n") |
.install_auth(app_path, auth_type) ⇒ String
Installs authentication (Devise)
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/railsforge/generator.rb', line 119 def self.install_auth(app_path, auth_type) puts "\n→ Installing authentication: #{auth_type}..." if auth_type == "devise" # Add devise to Gemfile gemfile_path = File.join(app_path, "Gemfile") if File.exist?(gemfile_path) content = File.read(gemfile_path) unless content.include?("gem 'devise'") File.open(gemfile_path, "a") do |f| f.puts "\n# Authentication\ngem 'devise'" end puts " ✓ Added devise to Gemfile" else puts " ✓ devise already in Gemfile" end end # Install devise Dir.chdir(app_path) do system("bundle install 2>/dev/null") system("rails generate devise:install 2>/dev/null") system("rails generate devise User 2>/dev/null") system("rails db:migrate 2>/dev/null") end "✅ Authentication (Devise) installed successfully!" else "⚠️ JWT authentication not implemented yet. Use --auth devise" end end |
.install_jobs(app_path, jobs_type) ⇒ String
Installs background jobs (Sidekiq + Redis)
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/railsforge/generator.rb', line 203 def self.install_jobs(app_path, jobs_type) puts "\n→ Installing background jobs: #{jobs_type}..." if jobs_type == "sidekiq" # Add sidekiq to Gemfile gemfile_path = File.join(app_path, "Gemfile") if File.exist?(gemfile_path) content = File.read(gemfile_path) unless content.include?("gem 'sidekiq'") File.open(gemfile_path, "a") do |f| f.puts "\n# Background jobs\ngem 'sidekiq'\ngem 'redis', '~> 4.0.1'" end puts " ✓ Added sidekiq to Gemfile" else puts " ✓ sidekiq already in Gemfile" end end # Install dependencies Dir.chdir(app_path) do system("bundle install 2>/dev/null") end # Create config file for sidekiq config_path = File.join(app_path, "config", "sidekiq.yml") unless File.exist?(config_path) FileUtils.mkdir_p(File.dirname(config_path)) File.write(config_path, ":concurrency: 5\n:queues:\n - default\n - mailers\n YAML\n puts \" \u2713 Created config/sidekiq.yml\"\n end\n\n # Create initializers\n init_path = File.join(app_path, \"config\", \"initializers\", \"sidekiq.rb\")\n unless File.exist?(init_path)\n File.write(init_path, <<~RUBY)\n# Sidekiq configuration\nSidekiq.configure_server do |config|\n config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/1') }\nend\n\nSidekiq.configure_client do |config|\n config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/1') }\nend\n RUBY\n puts \" \u2713 Created config/initializers/sidekiq.rb\"\n end\n\n # Update application.rb to use sidekiq for active job\n app_rb_path = File.join(app_path, \"config\", \"application.rb\")\n if File.exist?(app_rb_path)\n content = File.read(app_rb_path)\n unless content.include?(\"config.active_job.queue_adapter = :sidekiq\")\n content.gsub!(/config\\.active_job\\.queue_adapter = :.*/, \"config.active_job.queue_adapter = :sidekiq\")\n File.write(app_rb_path, content)\n puts \" \u2713 Configured ActiveJob to use Sidekiq\"\n end\n end\n\n # Create example job\n jobs_dir = File.join(app_path, \"app\", \"jobs\")\n FileUtils.mkdir_p(jobs_dir)\n example_job_path = File.join(jobs_dir, \"example_job.rb\")\n unless File.exist?(example_job_path)\n File.write(example_job_path, <<~RUBY)\n# Example job using Sidekiq\nclass ExampleJob < ApplicationJob\n queue_as :default\n\n def perform(*args)\n# Do something later\n end\nend\n RUBY\n puts \" \u2713 Created example job\"\n end\n\n \"\u2705 Background jobs (Sidekiq + Redis) installed successfully!\"\n else\n \"\u26A0\uFE0F Unknown jobs type: \#{jobs_type}. Use --jobs sidekiq\"\n end\nend\n") |
.validate_app_name(name) ⇒ void
This method returns an undefined value.
Validates the app name to ensure it’s valid for a Rails project
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/railsforge/generator.rb', line 27 def self.validate_app_name(name) if name.nil? || name.strip.empty? raise InvalidAppNameError, "App name cannot be empty" end # Check for valid Ruby/Rails naming conventions unless name =~ /\A[a-z][a-z0-9_]*\z/ raise InvalidAppNameError, "App name must start with a lowercase letter and contain only letters, numbers, and underscores" end # Check for reserved Ruby words that would cause issues reserved_words = %w[begin end if else elsif unless case when while until do for class module def unless return yield break next redo rescue require include extend raise attr attr_reader attr_writer attr_accessor lambda proc] if reserved_words.include?(name) raise InvalidAppNameError, "'#{name}' is a reserved Ruby word and cannot be used as an app name" end end |