Last active
October 14, 2025 19:47
-
-
Save CuddlyBunion341/62543e2eb116d39bf5fbb5f1646f524c to your computer and use it in GitHub Desktop.
SUPER COOL RAILS TEMPLATE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # frozen_string_literal: true | |
| # ----------------------------------------------------------------------------- | |
| # Renuo Rails Application Template | |
| # ----------------------------------------------------------------------------- | |
| # Purpose: | |
| # Automates the setup of a new Rails project according to Renuo AG conventions. | |
| # | |
| # Features: | |
| # * Enforces Ruby version consistency with `.ruby-version` | |
| # * Configures development and code quality tools: | |
| # - Renuocop (Rubocop configuration) | |
| # - MarkdownLint (mdl) | |
| # - Brakeman for security scanning | |
| # * Sets up testing environment: | |
| # - RSpec, SimpleCov, ParallelTests, FactoryBot | |
| # - Enforces full coverage metrics using SimpleCov | |
| # * Defines I18n configuration with default locale and fallbacks | |
| # * Creates `/healthz` endpoint with controller, view, and request specs | |
| # * Generates helper bin scripts: | |
| # - `bin/setup` with 1Password secrets integration | |
| # - `bin/check` and `bin/fastcheck` for validation and linting | |
| # - `bin/bootstrap_github_repo` for Terraform-based GitHub setup | |
| # * Cleans up Rails defaults (CSS skeleton, test directory) | |
| # * Provides consistent project documentation and environment examples | |
| # | |
| # Usage: | |
| # rails new APP_NAME --skip-ci --skip-camal -m path/to/template.rb | |
| # | |
| # Requirements: | |
| # * Ruby, Bundler, Rails | |
| # * Terraform | |
| # * GitHub CLI (`gh`) | |
| # ----------------------------------------------------------------------------- | |
| say_status :info, 'Applying renuo-rails template', :blue | |
| # Ensure the Gemfile points to the Ruby version managed by .ruby-version | |
| if File.read('Gemfile').match?(/^ruby/) | |
| gsub_file 'Gemfile', /^ruby .*$/, "ruby file: '.ruby-version'\n" | |
| else | |
| insert_into_file 'Gemfile', "ruby file: '.ruby-version'\n", after: /^source.*\n/ | |
| end | |
| # -------------------------------------------------------------------- | |
| # bin/setup | |
| # -------------------------------------------------------------------- | |
| insert_into_file 'bin/setup', before: "\n puts \"\\n== Preparing database ==\"" do | |
| <<-RUBY | |
| puts "\\n== Fetching 1password dependencies ==" | |
| system! 'renuo fetch-secrets'#{' '} | |
| RUBY | |
| end | |
| gsub_file 'bin/setup', /\n{0,2}[ \t]*unless ARGV.include\?\("--skip-server"\).*?end/m, '' | |
| # -------------------------------------------------------------------- | |
| # Convenience scripts, checks | |
| # -------------------------------------------------------------------- | |
| gem_group :development do | |
| gem 'mdl', require: false | |
| gem 'renuocop' | |
| end | |
| # Configure markdownlint | |
| create_file '.mdlrc', <<~MDLRC, force: true | |
| all | |
| rule 'MD013', :line_length => 120 | |
| rule 'MD041', :enabled => false | |
| MDLRC | |
| # Ensure Rubocop uses Renuo defaults | |
| create_file '.rubocop.yml', <<~YAML, force: true | |
| inherit_gem: | |
| renuocop: config/base.yml | |
| YAML | |
| create_file 'bin/run', <<~BASH, force: true | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| bundle exec rails server | |
| BASH | |
| create_file 'bin/fastcheck', <<~BASH, force: true | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| bundle exec brakeman -q -z --no-summary --no-pager | |
| RUBOCOP_AUTO_CORRECT="-a" | |
| if [[ "${1:-}" == "--dangerous" ]]; then | |
| RUBOCOP_AUTO_CORRECT="-A" | |
| fi | |
| if ! bundle exec rubocop -D -c .rubocop.yml --fail-fast; then | |
| bundle exec rubocop $RUBOCOP_AUTO_CORRECT -D -c .rubocop.yml | |
| if ! bundle exec rubocop -D -c .rubocop.yml --fail-fast; then | |
| echo 'Tried to auto correct the issues, but must be reviewed manually, commit aborted' | |
| else | |
| echo 'After formatting, everything looks good' | |
| fi | |
| exit 1 | |
| fi | |
| bundle exec mdl README.md | |
| BASH | |
| create_file 'bin/check', <<~BASH, force: true | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| bin/rails zeitwerk:check | |
| adapter=$(bundle exec rails runner 'puts ActiveRecord::Base.connection.adapter_name' 2>/dev/null | tr -d " | |
| ") | |
| if [ "$adapter" != "SQLite" ] && [ "$adapter" != "SQLite3" ]; then | |
| bundle exec rails parallel:drop >/dev/null 2>&1 || true | |
| bundle exec rails parallel:setup | |
| fi | |
| NO_COVERAGE=true bundle exec parallel_rspec | |
| bundle exec rspec | |
| BASH | |
| run 'chmod +x bin/fastcheck' | |
| run 'chmod +x bin/check' | |
| # -------------------------------------------------------------------- | |
| # Configure I18n | |
| # -------------------------------------------------------------------- | |
| application <<~RUBY | |
| config.i18n.available_locales = [:en] | |
| config.i18n.default_locale = :en | |
| config.i18n.fallbacks = true | |
| RUBY | |
| environment <<~RUBY, env: 'development' | |
| config.i18n.raise_on_missing_translations = true | |
| RUBY | |
| environment <<~RUBY, env: 'test' | |
| config.i18n.raise_on_missing_translations = true | |
| RUBY | |
| create_file 'config/locales/.keep' | |
| create_file 'config/locales/en.yml', <<~YAML, force: true | |
| en: | |
| application: | |
| name: "#{app_name.titleize}" | |
| health: | |
| title: "Application health" | |
| ok: "Everything is running" | |
| YAML | |
| # -------------------------------------------------------------------- | |
| # Add rspec related gems | |
| # -------------------------------------------------------------------- | |
| gem_group :development, :test do | |
| gem 'factory_bot_rails' | |
| gem 'parallel_tests' | |
| end | |
| gem_group :test do | |
| gem 'super_diff' | |
| gem 'rspec-rails' | |
| gem 'simplecov', require: false | |
| end | |
| application <<~RUBY | |
| config.generators do |g| | |
| g.test_framework :rspec | |
| g.system_tests = :rspec | |
| end | |
| RUBY | |
| # -------------------------------------------------------------------- | |
| # Other configurations | |
| # -------------------------------------------------------------------- | |
| gem 'simple_form' | |
| schema_version = if defined?(ActiveRecord::VERSION) | |
| "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}" | |
| else | |
| "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}" | |
| end | |
| create_file 'db/schema.rb', <<~RUBY, force: true | |
| # frozen_string_literal: true | |
| ActiveRecord::Schema[#{schema_version}].define(version: 0) do | |
| end | |
| RUBY | |
| # Append coverage folder to gitignore | |
| append_to_file '.gitignore', <<~GITIGNORE unless File.read('.gitignore').include?('coverage') | |
| # Coverage artifacts | |
| coverage/ | |
| GITIGNORE | |
| unless File.read('.gitignore').include?('.env') | |
| append_to_file '.gitignore', " | |
| .env | |
| " | |
| end | |
| # Remove unused CSS skeleton to keep template unstyled | |
| remove_dir 'app/assets/stylesheets' if Dir.exist?('app/assets/stylesheets') | |
| empty_directory 'app/assets/stylesheets' | |
| create_file 'app/assets/stylesheets/.keep' | |
| # Add health check endpoint | |
| route "get '/healthz', to: 'health#show'" | |
| create_file 'app/controllers/health_controller.rb', <<~RUBY, force: true | |
| class HealthController < ApplicationController | |
| def show | |
| checks = { | |
| app: "ok" | |
| } | |
| respond_to do |format| | |
| format.html | |
| format.json { render json: { status: "ok", checks: checks }, status: :ok } | |
| end | |
| end | |
| end | |
| RUBY | |
| create_file 'app/views/health/show.html.erb', <<~ERB, force: true | |
| <main class="health"> | |
| <h1><%= t("health.title") %></h1> | |
| <p><%= t("health.ok") %></p> | |
| </main> | |
| ERB | |
| # Provide environment example for selenium driver | |
| create_file '.env.example', <<~ENV, force: true | |
| # Uncomment to run system specs with a visible browser | |
| # SELENIUM_DRIVER="selenium_chrome" | |
| SELENIUM_DRIVER="selenium_chrome_headless" | |
| ENV | |
| # GitHub Actions workflow for CI | |
| create_file '.github/workflows/ci.yml', <<~YAML, force: true | |
| name: CI | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - develop | |
| pull_request: | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: .ruby-version | |
| bundler-cache: true | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y xvfb libnss3 | |
| - name: Prepare database | |
| run: bin/rails db:prepare | |
| - name: Fast check | |
| run: bin/fastcheck | |
| - name: Run test suite | |
| run: bin/check | |
| YAML | |
| # Documentation for generated app | |
| create_file 'README.md', <<~MARKDOWN, force: true | |
| # #{app_name.titleize} | |
| Short project description | |
| ## Environments | |
| | Branch | Domain | Deployment | | |
| | ------- | ------------------------------------- | ---------------| | |
| | develop | https://#{app_name}-develop.renuoapp.ch | auto | | |
| | main | https://#{app_name}-main.renuoapp.ch | release | | |
| ## Setup | |
| ```sh | |
| git clone git@github.com:renuo/#{app_name}.git | |
| cd '#{app_name}' | |
| bin/setup | |
| ``` | |
| ### Run | |
| ```sh | |
| bin/run | |
| ``` | |
| ### Tests / Checks | |
| ```sh | |
| bin/check | |
| ``` | |
| ## Copyright | |
| Copyright [Renuo AG](https://www.renuo.ch/). | |
| MARKDOWN | |
| after_bundle do | |
| # -------------------------------------------------------------------- | |
| # Configure simple_form | |
| # -------------------------------------------------------------------- | |
| generate 'simple_form:install' | |
| gsub_file 'config/initializers/simple_form.rb', | |
| 'hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b|', | |
| "hint_class: :field_with_hint,\n error_class: :field_with_errors,\n valid_class: :field_without_errors do |b|" | |
| # -------------------------------------------------------------------- | |
| # Configure rspec | |
| # -------------------------------------------------------------------- | |
| remove_dir 'test' | |
| generate 'rspec:install' | |
| run 'bundle binstubs rspec-core' | |
| create_file 'spec/spec_helper.rb', <<~'RUBY', force: true | |
| # frozen_string_literal: true | |
| require "simplecov" unless ENV["NO_COVERAGE"] | |
| unless ENV["NO_COVERAGE"] | |
| SimpleCov.start "rails" do | |
| add_filter "app/channels/application_cable/channel.rb" | |
| add_filter "app/channels/application_cable/connection.rb" | |
| add_filter "app/jobs/application_job.rb" | |
| add_filter "app/mailers/application_mailer.rb" | |
| add_filter "app/models/application_record.rb" | |
| enable_coverage :branch | |
| minimum_coverage line: 100, branch: 100 | |
| end | |
| test_env_number = ENV.fetch("TEST_ENV_NUMBER", "0") | |
| SimpleCov.command_name "RSpec #{test_env_number}" | |
| SimpleCov.use_merging true | |
| end | |
| RSpec.configure do |config| | |
| config.expect_with :rspec do |expectations| | |
| expectations.include_chain_clauses_in_custom_matcher_descriptions = true | |
| end | |
| config.mock_with :rspec do |mocks| | |
| mocks.verify_partial_doubles = true | |
| end | |
| config.shared_context_metadata_behavior = :apply_to_host_groups | |
| config.run_all_when_everything_filtered = true | |
| config.filter_run :focus | |
| config.filter_run_excluding :skip | |
| config.example_status_persistence_file_path = "tmp/rspec_examples.txt" | |
| config.disable_monkey_patching! | |
| config.default_formatter = "doc" if config.files_to_run.one? | |
| config.profile_examples = 5 | |
| config.order = :random | |
| Kernel.srand config.seed | |
| config.define_derived_metadata do |metadata| | |
| metadata[:aggregate_failures] = true | |
| end | |
| end | |
| RUBY | |
| create_file 'spec/rails_helper.rb', <<~'RUBY', force: true | |
| # frozen_string_literal: true | |
| ENV["RAILS_ENV"] ||= "test" | |
| require "spec_helper" | |
| require_relative "../config/environment" | |
| abort("The Rails environment is running in production mode!") if Rails.env.production? | |
| require "rspec/rails" | |
| require "capybara/rspec" | |
| require "capybara/rails" | |
| require "selenium/webdriver" | |
| require "super_diff/rspec-rails" | |
| ActiveRecord::Migration.maintain_test_schema! | |
| Rails.root.glob("spec/support/**/*.rb").each { |f| require f } | |
| RSpec.configure do |config| | |
| config.include FactoryBot::Syntax::Methods | |
| config.include ActiveSupport::Testing::TimeHelpers | |
| config.use_transactional_fixtures = true | |
| config.infer_spec_type_from_file_location! | |
| config.filter_rails_from_backtrace! | |
| config.before do | |
| ActionMailer::Base.deliveries.clear if defined?(ActionMailer::Base) | |
| I18n.locale = I18n.default_locale | |
| end | |
| config.after do | |
| Rails.logger.debug { "--- #{RSpec.current_example&.full_description} FINISHED ---" } | |
| end | |
| config.before(:each, :system) do | |
| driven_by :rack_test | |
| end | |
| config.before(:all, :system) do | |
| Capybara.server = :puma, { Silent: true } | |
| end | |
| config.before(:each, :system, :js) do | |
| driven_by(ENV["SELENIUM_DRIVER"]&.to_sym || :selenium_chrome_headless) | |
| Capybara.page.current_window.resize_to(1280, 800) | |
| end | |
| end | |
| RUBY | |
| create_file 'spec/support/factory_bot.rb', <<~'RUBY', force: true | |
| # frozen_string_literal: true | |
| RSpec.configure do |config| | |
| config.include FactoryBot::Syntax::Methods | |
| end | |
| RUBY | |
| create_file 'spec/support/javascript_error_reporter.rb', <<~'RUBY', force: true | |
| # frozen_string_literal: true | |
| module JavaScriptErrorReporter | |
| RSpec.configure do |config| | |
| config.after(:each, :system, :js) do | |
| errors = page.driver.browser.logs.get(:browser) | |
| aggregate_failures "javascript errors" do | |
| errors.each do |error| | |
| expect(error.level).not_to eq("SEVERE"), error.message | |
| next unless error.level == "WARNING" | |
| warn "\e[33m\nJAVASCRIPT WARNING\n#{error.message}\e[0m" | |
| end | |
| end | |
| end | |
| end | |
| end | |
| RUBY | |
| create_file 'spec/support/capybara.rb', <<~'RUBY', force: true | |
| # frozen_string_literal: true | |
| require_relative "javascript_error_reporter" | |
| require "fileutils" | |
| require "tmpdir" | |
| Capybara.register_driver :selenium_chrome do |app| | |
| options = Selenium::WebDriver::Chrome::Options.new | |
| options.add_argument("--window-size=1280,800") | |
| profile_path = File.join(Dir.tmpdir, "selenium_profiles", ENV.fetch("TEST_ENV_NUMBER", "0")) | |
| FileUtils.mkdir_p(profile_path) | |
| options.add_argument("--user-data-dir=#{profile_path}") | |
| Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) | |
| end | |
| Capybara.register_driver :selenium_chrome_headless do |app| | |
| options = Selenium::WebDriver::Chrome::Options.new | |
| options.add_argument("--headless=new") | |
| options.add_argument("--disable-gpu") | |
| options.add_argument("--no-sandbox") | |
| options.add_argument("--disable-dev-shm-usage") | |
| options.add_argument("--window-size=1280,800") | |
| profile_path = File.join(Dir.tmpdir, "selenium_profiles", ENV.fetch("TEST_ENV_NUMBER", "0")) | |
| FileUtils.mkdir_p(profile_path) | |
| options.add_argument("--user-data-dir=#{profile_path}") | |
| Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) | |
| end | |
| RSpec.configure do |config| | |
| config.include JavaScriptErrorReporter | |
| end | |
| Capybara.default_max_wait_time = 5 | |
| RUBY | |
| create_file 'spec/requests/health_controller_spec.rb', <<~'RUBY', force: true | |
| # frozen_string_literal: true | |
| require "rails_helper" | |
| RSpec.describe HealthController, type: :request do | |
| it "returns a successful json response" do | |
| get "/healthz", as: :json | |
| expect(response).to have_http_status(:ok) | |
| expect(response.parsed_body).to include("status" => "ok") | |
| end | |
| end | |
| RUBY | |
| create_file 'spec/system/health_spec.rb', <<~'RUBY', force: true | |
| # frozen_string_literal: true | |
| require "rails_helper" | |
| RSpec.describe "Health check" do | |
| it "shows the health page" do | |
| visit "/healthz" | |
| expect(page).to have_text(I18n.t("health.title")) | |
| expect(page).to have_text(I18n.t("health.ok")) | |
| end | |
| it "supports the JSON endpoint", :js do | |
| visit "/healthz.json" | |
| expect(page).to have_text("status") | |
| expect(page).to have_text("ok") | |
| end | |
| end | |
| RUBY | |
| # -------------------------------------------------------------------- | |
| # GitHub repository configuration | |
| # -------------------------------------------------------------------- | |
| say_status :info, 'Setting up GitHub bootstrap scripts', :blue | |
| bootstrap_dir = 'tmp/bootstrap_github_repo' | |
| empty_directory bootstrap_dir | |
| OWNER = 'CuddlyBunion341' | |
| create_file "#{bootstrap_dir}/main.tf", <<~HCL | |
| terraform { | |
| required_providers { | |
| github = { | |
| source = "integrations/github" | |
| version = "~> 6.5" | |
| } | |
| } | |
| } | |
| provider "github" { | |
| owner = "#{OWNER}" | |
| } | |
| resource "github_repository" "repo" { | |
| name = "#{app_name}" | |
| description = "#{app_name.titleize} Rails Application" | |
| visibility = "private" | |
| has_issues = false | |
| has_projects = false | |
| has_wiki = false | |
| } | |
| resource "github_branch_protection" "develop" { | |
| repository_id = github_repository.repo.node_id | |
| pattern = "develop" | |
| enforce_admins = true | |
| required_status_checks { | |
| strict = true | |
| contexts = ["ci/semaphore/push"] | |
| } | |
| required_pull_request_reviews { | |
| dismiss_stale_reviews = true | |
| require_code_owner_reviews = false | |
| } | |
| required_linear_history = true | |
| allows_deletions = false | |
| } | |
| resource "github_branch_protection" "main" { | |
| repository_id = github_repository.repo.node_id | |
| pattern = "main" | |
| enforce_admins = true | |
| required_pull_request_reviews { | |
| dismiss_stale_reviews = false | |
| require_code_owner_reviews = false | |
| } | |
| required_linear_history = true | |
| allows_deletions = false | |
| } | |
| resource "github_repository_autolink_reference" "redmine" { | |
| repository = github_repository.repo.name | |
| key_prefix = "TICKET-" | |
| target_url_template = "https://redmine.renuo.ch/issues/<num>" | |
| } | |
| HCL | |
| create_file "#{bootstrap_dir}/run", <<~BASH | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| RED='\\033[0;31m' | |
| GREEN='\\033[0;32m' | |
| YELLOW='\\033[1;33m' | |
| BLUE='\\033[0;34m' | |
| NC='\\033[0m' | |
| echo_info() { echo -e "${BLUE}==>${NC} $1"; } | |
| echo_success() { echo -e "${GREEN}✓${NC} $1"; } | |
| echo_error() { echo -e "${RED}✗${NC} $1" >&2; } | |
| echo_warning() { echo -e "${YELLOW}!${NC} $1"; } | |
| echo_info "Checking prerequisites..." | |
| if ! command -v gh >/dev/null 2>&1; then | |
| echo_error "GitHub CLI (gh) not found. Install from https://cli.github.com/" | |
| exit 1 | |
| fi | |
| echo_success "GitHub CLI found" | |
| if ! gh auth status >/dev/null 2>&1; then | |
| echo_error "Not authenticated with GitHub CLI. Run: gh auth login" | |
| exit 1 | |
| fi | |
| echo_success "GitHub CLI authenticated" | |
| if ! command -v terraform >/dev/null 2>&1; then | |
| echo_error "Terraform not found." | |
| echo_warning "Install with Homebrew: brew install terraform" | |
| exit 1 | |
| fi | |
| echo_success "Terraform found" | |
| export GITHUB_TOKEN=$(gh auth token) | |
| echo_success "GitHub token exported" | |
| cd "$(dirname "$0")" | |
| echo_info "Initializing Terraform..." | |
| if ! terraform init -input=false; then | |
| echo_error "Terraform initialization failed" | |
| exit 1 | |
| fi | |
| echo_success "Terraform initialized" | |
| echo_info "Validating Terraform configuration..." | |
| if ! terraform validate; then | |
| echo_error "Terraform validation failed" | |
| exit 1 | |
| fi | |
| echo_success "Terraform configuration valid" | |
| echo_info "Applying Terraform configuration..." | |
| echo_warning "This will create/update GitHub repository settings" | |
| if terraform apply -auto-approve -input=false; then | |
| echo "" | |
| echo_success "Bootstrap complete!" | |
| echo_info "GitHub repository settings have been applied" | |
| else | |
| echo_error "Terraform apply failed" | |
| exit 1 | |
| fi | |
| echo_info "Feel free to see your changes here: https://github.com/#{OWNER}/#{app_name}" | |
| BASH | |
| run "chmod +x #{bootstrap_dir}/run" | |
| create_file 'bin/bootstrap_github_repo', <<~BASH, force: true | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| echo "Applying GitHub configuration for repository '#{app_name}'..." | |
| tmp/bootstrap_github_repo/run | |
| BASH | |
| run 'chmod +x bin/bootstrap_github_repo' | |
| run 'bin/fastcheck --dangerous', abort_on_failure: true | |
| say_status :info, 'Local Rails Project is setup, feel free to create and prepare github repsositiory using:', :blue | |
| say_status :info, "cd '#{app_name}' && bin/bootstrap_github_repo", :blue | |
| say "\nNext steps for App setup:", :blue | |
| say <<~MSG, :green | |
| ------------------------------------------------------------ | |
| 1. Change into project | |
| cd '#{app_name}' | |
| 2. Stage and commit your files: | |
| git add . | |
| git commit -m \"Initial commit\" | |
| 3. Create and configure the GitHub repository: | |
| bin/bootstrap_github_repo | |
| 4. Link your local repository to GitHub: | |
| git remote add origin git@github.com:#{OWNER}/#{app_name}.git | |
| git push -u origin develop | |
| ------------------------------------------------------------ | |
| MSG | |
| end |
Author
Author
Will merge once ready!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Building on top of Renuo ASG:
https://raw.githubusercontent.com/renuo/applications-setup-guide/main/ruby_on_rails/template.rb