Class: Maquina::Generators::AppGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Defined in:
lib/generators/maquina/app/app_generator.rb

Instance Method Summary collapse

Instance Method Details

#add_gemsObject

  1. Add gems



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/generators/maquina/app/app_generator.rb', line 16

def add_gems
  gemfile_path = File.join(destination_root, "Gemfile")
  return unless File.exist?(gemfile_path)

  content = File.read(gemfile_path)

  remove_gems = ["rubocop-rails-omakase"]
  # standard >= 1.54: avoids the inoperative placeholder releases
  # (1.34.0.1 / 1.35.0.1) whose too-loose `rubocop ~> 1.62` requirement
  # makes bundler backtrack onto them and pair them with an incompatible
  # rubocop, so the `standard` binary refuses to run.
  #
  # standard.rb runs through the generated .rubocop.yml (see
  # create_config_files) — that file is standard.rb's runner bridge, not
  # custom RuboCop rules, so it ships even though guidance says "no
  # .rubocop.yml".
  dev_gems = {"brakeman" => nil, "bundle-audit" => nil, "letter_opener" => nil, "standard" => ">= 1.54"}
  runtime_gems = {"rails-i18n" => nil, "maquina-components" => nil}
  production_gems = {"aws-sdk-s3" => nil}

  remove_gems.each do |gem_name|
    gsub_file "Gemfile", /^\s*gem\s+["']#{gem_name}["'].*\n/, ""
  end

  dev_gems.each do |gem_name, version|
    next if content.include?("gem \"#{gem_name}\"")
    append_to_file "Gemfile", "\n#{gem_line(gem_name, version, group: :development)}\n"
  end

  runtime_gems.each do |gem_name, version|
    next if content.include?("gem \"#{gem_name}\"")
    append_to_file "Gemfile", "\n#{gem_line(gem_name, version)}\n"
  end

  production_gems.each do |gem_name, version|
    next if content.include?("gem \"#{gem_name}\"")
    append_to_file "Gemfile", "\n#{gem_line(gem_name, version, group: :production)}\n"
  end

  return unless rails_app?

  Bundler.with_unbundled_env do
    system("bundle install", chdir: destination_root)
  end
end

#configure_applicationObject

  1. Configure application



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/generators/maquina/app/app_generator.rb', line 94

def configure_application
  application_file = File.join(destination_root, "config/application.rb")
  return unless File.exist?(application_file)

  content = File.read(application_file)

  unless content.include?("field_error_proc")
    inject_into_file "config/application.rb",
      after: /class Application < Rails::Application\n/ do
      <<~RUBY.indent(4)

        # Don't wrap form fields with errors in an extra div
        config.action_view.field_error_proc = proc { |html_tag, _instance| html_tag }
      RUBY
    end
  end

  unless content.include?("solid_queue")
    inject_into_file "config/application.rb",
      after: /class Application < Rails::Application\n/ do
      <<~RUBY.indent(4)

        # Use Solid Queue as the Active Job backend in all environments except test
        config.active_job.queue_adapter = :solid_queue unless Rails.env.test?
        config.solid_queue.connects_to = {database: {writing: :queue}}
      RUBY
    end
  end
end

#configure_bin_setupObject

  1. Restore database.yml from its example in bin/setup. config/database.yml is gitignored (it differs per environment), so a fresh clone has only the committed example — bin/setup must copy it before db:prepare.



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/generators/maquina/app/app_generator.rb', line 267

def configure_bin_setup
  setup_file = File.join(destination_root, "bin/setup")
  return unless File.exist?(setup_file)
  return if File.read(setup_file).include?("config/database.yml.example")

  inject_into_file "bin/setup",
    after: %r{system\("bundle check"\) \|\| system!\("bundle install"\)\n} do
    <<~RUBY.indent(2)

      # config/database.yml is gitignored; restore it from the committed example
      unless File.exist?("config/database.yml")
        puts "\\n== Copying config/database.yml =="
        FileUtils.cp "config/database.yml.example", "config/database.yml"
      end
    RUBY
  end
end

#configure_ciObject

  1. Restore database.yml from its example in CI. Without it, the test job boots with no database.yml and every Rails task fails. Only touched when the host app already has a GitHub Actions workflow.



288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/generators/maquina/app/app_generator.rb', line 288

def configure_ci
  ci_file = File.join(destination_root, ".github/workflows/ci.yml")
  return unless File.exist?(ci_file)
  return if File.read(ci_file).include?("config/database.yml.example")

  inject_into_file ".github/workflows/ci.yml",
    before: /^      - name: Run tests$/ do
    <<~YAML.indent(6)
      - name: Prepare database config
        run: cp config/database.yml.example config/database.yml
    YAML
  end
end

#configure_databasesObject

  1. Configure multiple databases (after installers so we overwrite their database.yml)



231
232
233
# File 'lib/generators/maquina/app/app_generator.rb', line 231

def configure_databases
  template "config/database.yml.tt", "config/database.yml", force: true
end

#configure_environmentsObject

  1. Configure environments



88
89
90
91
# File 'lib/generators/maquina/app/app_generator.rb', line 88

def configure_environments
  configure_development
  configure_production
end

#create_config_filesObject

  1. Config files



68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/generators/maquina/app/app_generator.rb', line 68

def create_config_files
  copy_file ".rubocop.yml", ".rubocop.yml"
  copy_file ".standard.yml", ".standard.yml"

  gitignore_path = File.join(destination_root, ".gitignore")
  if File.exist?(gitignore_path)
    content = File.read(gitignore_path)
    unless content.include?("config/database.yml")
      append_to_file ".gitignore", "\n# Ignore database configuration\nconfig/database.yml\n"
    end
  end
end

#create_database_sampleObject

  1. Create database.yml.example



257
258
259
260
261
262
# File 'lib/generators/maquina/app/app_generator.rb', line 257

def create_database_sample
  db_config = File.join(destination_root, "config/database.yml")
  if File.exist?(db_config)
    copy_file db_config, "config/database.yml.example"
  end
end

#create_home_pageObject

  1. Create home page



236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/generators/maquina/app/app_generator.rb', line 236

def create_home_page
  template "app/controllers/home_controller.rb",
    "app/controllers/home_controller.rb"
  template "app/views/home/index.html.erb",
    "app/views/home/index.html.erb"

  route_file = File.join(destination_root, "config/routes.rb")
  if File.exist?(route_file)
    content = File.read(route_file)
    unless content.match?(/^\s*root\s/)
      route 'root "home#index"'
    end
  end
end

#create_initializersObject

  1. Initializers



82
83
84
85
# File 'lib/generators/maquina/app/app_generator.rb', line 82

def create_initializers
  copy_file "config/initializers/generators.rb",
    "config/initializers/generators.rb"
end

#create_procfileObject

  1. Create Procfile.dev



63
64
65
# File 'lib/generators/maquina/app/app_generator.rb', line 63

def create_procfile
  template "Procfile.dev.tt", "Procfile.dev"
end

#create_readmeObject

  1. Create README



252
253
254
# File 'lib/generators/maquina/app/app_generator.rb', line 252

def create_readme
  template "README.md.erb", "README.md"
end

#install_authenticationObject

  1. Install authentication



187
188
189
190
191
192
193
194
195
196
# File 'lib/generators/maquina/app/app_generator.rb', line 187

def install_authentication
  return unless rails_app?

  case options[:auth]
  when "clave"
    generate "maquina:clave", "--quiet"
  when "registration"
    generate "maquina:registration", "--quiet"
  end
end

#install_maquina_generatorsObject

  1. Install maquina generators



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/generators/maquina/app/app_generator.rb', line 199

def install_maquina_generators
  return unless rails_app?

  generate "maquina:rack_attack", "--quiet"
  generate "maquina:mission_control_jobs", "--prefix", options[:prefix], "--quiet"
  generate "maquina:solid_errors", "--prefix", options[:prefix], "--quiet"

  Bundler.with_unbundled_env do
    system("bin/rails generate solid_queue:install", chdir: destination_root)
    system("bin/rails generate solid_errors:install", chdir: destination_root)
    system("bin/rails generate solid_cache:install", chdir: destination_root)
    system("bin/rails generate solid_cable:install", chdir: destination_root)
    system("bin/rails generate maquina_components:install", chdir: destination_root)
  end
end

#install_rails_featuresObject

  1. Install Rails features



125
126
127
128
129
130
131
132
# File 'lib/generators/maquina/app/app_generator.rb', line 125

def install_rails_features
  return unless rails_app?

  Bundler.with_unbundled_env do
    system("bin/rails action_text:install", chdir: destination_root)
    system("bin/rails active_storage:install", chdir: destination_root)
  end
end

#restore_custom_layoutsObject

  1. Restore custom layouts overwritten by gem installers



216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/generators/maquina/app/app_generator.rb', line 216

def restore_custom_layouts
  return unless rails_app?

  solid_errors_layout = File.expand_path(
    "../solid_errors/templates/app/views/layouts/solid_errors/application.html.erb",
    __dir__
  )

  if File.exist?(solid_errors_layout)
    create_file "app/views/layouts/solid_errors/application.html.erb",
      File.read(solid_errors_layout), force: true
  end
end

#run_migrationsObject

  1. Run all migrations



303
304
305
306
307
308
309
# File 'lib/generators/maquina/app/app_generator.rb', line 303

def run_migrations
  return unless rails_app?

  Bundler.with_unbundled_env do
    system("bin/rails db:prepare", chdir: destination_root)
  end
end

#setup_javascriptObject

  1. Setup JavaScript



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/generators/maquina/app/app_generator.rb', line 135

def setup_javascript
  return unless rails_app?

  importmap_path = File.join(destination_root, "config/importmap.rb")
  if File.exist?(importmap_path)
    content = File.read(importmap_path)
    unless content.include?("activestorage")
      append_to_file "config/importmap.rb",
        "\npin \"@rails/activestorage\", to: \"activestorage.esm.js\"\n"
    end
  end

  js_path = File.join(destination_root, "app/javascript/application.js")
  if File.exist?(js_path)
    content = File.read(js_path)
    unless content.include?("ActiveStorage")
      append_to_file "app/javascript/application.js", <<~JS

        import * as ActiveStorage from "@rails/activestorage"
        ActiveStorage.start()
      JS
    end
  end
end

#setup_layoutObject

  1. Setup layout



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/generators/maquina/app/app_generator.rb', line 161

def setup_layout
  layout_path = File.join(destination_root, "app/views/layouts/application.html.erb")
  return unless File.exist?(layout_path)

  content = File.read(layout_path)

  unless content.include?("yield :head")
    gsub_file "app/views/layouts/application.html.erb",
      "</head>",
      "    <%= yield :head %>\n  </head>"
  end

  unless content.include?("data-turbo-refresh-method")
    gsub_file "app/views/layouts/application.html.erb",
      "<body>",
      '<body data-turbo-refresh-method="morph" data-turbo-refresh-scroll="preserve">'
  end

  if content.include?('class="container mx-auto mt-28 px-5 flex"')
    gsub_file "app/views/layouts/application.html.erb",
      '<main class="container mx-auto mt-28 px-5 flex">',
      "<main>"
  end
end

#show_post_installObject

  1. Post-install message



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/generators/maquina/app/app_generator.rb', line 312

def show_post_install
  say ""
  say "=" * 60, :green
  say "Your Rails app is ready!", :green
  say "=" * 60, :green
  say ""
  say "Next steps:", :yellow
  say "  1. Set credentials: bin/rails credentials:edit"
  say "     backstage:"
  say "       username: your_user"
  say "       password: your_password"
  say "  2. Start the app: bin/dev"
  say ""
  say "Installed components:", :yellow
  say "  - Rack::Attack: rate limiting and blocklists (config/initializers/rack_attack.rb)"
  say "  - Solid Queue: background jobs (config/solid_queue.yml)"
  say "  - Mission Control Jobs: job dashboard at #{options[:prefix]}/mission_control_jobs"
  say "  - Solid Errors: error tracking at #{options[:prefix]}/solid_errors"
  say "  - Maquina Components: UI components"
  if options[:auth] != "none"
    say ""
    say "Authentication (#{options[:auth]}):", :yellow
    say "  - Visit /session/new to sign in"
    say "  - Visit /registrations/new to sign up" if options[:auth] == "registration"
    say "  - Visit /registration/new to sign up" if options[:auth] == "clave"
    say "  - Customize: app/controllers/concerns/authentication.rb"
    say "  - Locales: config/locales/"
  end
  say ""
end