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

Class Method Details

.create_app(app_name, options = {}) ⇒ String

Creates a new Rails app with the specified name

Parameters:

  • app_name (String)

    The name of the Rails app to create

  • options (Hash) (defaults to: {})

    Optional flags: auth, admin, jobs

Returns:

  • (String)

    Success message



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, options = {})
  validate_app_name(app_name)

  # Load profile if specified
  profile = nil
  if options[:profile]
    profile = RailsForge::Profile.load(options[: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)
    options = profile_defaults.merge(options)
    
    # Auto-enable features from profile
    profile_features = RailsForge::Profile.default_features(profile)
    options[:auth] = "devise" if profile_features.include?("authentication") && !options[:auth]
    options[:admin] = "activeadmin" if profile_features.include?("admin") && !options[:admin]
    options[:jobs] = "sidekiq" if profile_features.include?("jobs") && !options[:jobs]
  end

  # Install optional features based on flags
  results = []

  if options[:auth]
    results << install_auth(app_path, options[:auth])
  end

  if options[:admin]
    results << install_admin(app_path, options[:admin])
  end

  if options[:jobs]
    results << install_jobs(app_path, options[:jobs])
  end

  # Build success message
  message = "Rails app '#{app_name}' created successfully!"
  message += "\n\n" + results.join("\n") if results.any?
  message
end

.create_folders(app_name) ⇒ void

This method returns an undefined value.

Creates the additional folder structure inside the Rails app

Parameters:

  • app_name (String)

    The name of 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)

Parameters:

  • app_path (String)

    Path to the Rails app

  • admin_type (String)

    Type of admin (activeadmin or dashboard)

Returns:

  • (String)

    Success message



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)

Parameters:

  • app_path (String)

    Path to the Rails app

  • auth_type (String)

    Type of auth (devise or jwt)

Returns:

  • (String)

    Success message



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)

Parameters:

  • app_path (String)

    Path to the Rails app

  • jobs_type (String)

    Type of jobs (sidekiq)

Returns:

  • (String)

    Success message



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

Parameters:

  • name (String)

    The app name to validate



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