NYC

install

SKILL.md

Rails 8 Application Setup

Overview

Bootstrap a new Rails 8 application with production-ready defaults:

  • Rails 8 with Solid Queue, Solid Cache, Solid Cable
  • Built-in authentication (has_secure_password)
  • Multi-tenancy via Account scoping
  • Hotwire (Turbo + Stimulus)
  • Minitest + fixtures for testing
  • Tailwind CSS for styling

Quick Start

rails new myapp --css tailwind --database sqlite3
cd myapp

Post-Generation Setup

1. Generate Authentication

bin/rails generate authentication
bin/rails db:migrate

This creates:

  • User model with email_address and password_digest
  • Session model with token-based sessions
  • Current model (CurrentAttributes)
  • Authentication concern
  • SessionsController

2. Add Account Model (Multi-Tenancy)

bin/rails generate model Account name:string
bin/rails generate migration AddAccountToUsers account:references
bin/rails db:migrate
# app/models/account.rb
class Account < ApplicationRecord
  has_many :users, dependent: :destroy

  validates :name, presence: true
end
# app/models/user.rb
class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy
  belongs_to :account

  normalizes :email_address, with: ->(e) { e.strip.downcase }

  validates :email_address, presence: true,
    uniqueness: true,
    format: { with: URI::MailTo::EMAIL_REGEXP }
end

3. Add Current Account

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :session
  attribute :user_agent, :ip_address

  delegate :user, to: :session, allow_nil: true

  def account
    user&.account
  end
end

4. Configure Test Helper

# test/test_helper.rb
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"

module ActiveSupport
  class TestCase
    parallelize(workers: :number_of_processors)
    fixtures :all

    private

    def sign_in(user)
      session = user.sessions.create!
      cookies.signed.permanent[:session_token] = {
        value: session.token,
        httponly: true
      }
      Current.session = session
    end

    def sign_out
      Current.session&.destroy
      cookies.delete(:session_token)
    end
  end
end

5. Create Fixtures

# test/fixtures/accounts.yml
one:
  name: Acme Corp

other:
  name: Other Corp
# test/fixtures/users.yml
one:
  email_address: user@example.com
  password_digest: <%= BCrypt::Password.create("password123") %>
  account: one

admin:
  email_address: admin@example.com
  password_digest: <%= BCrypt::Password.create("password123") %>
  account: one

other_account:
  email_address: other@example.com
  password_digest: <%= BCrypt::Password.create("password123") %>
  account: other
# test/fixtures/sessions.yml
one:
  user: one
  token: <%= SecureRandom.urlsafe_base64(32) %>

6. Configure Solid Queue

# config/queue.yml (already generated by Rails 8)
default: &default
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: "*"
      threads: 3
      processes: 1
      polling_interval: 0.1
# config/environments/production.rb
config.active_job.queue_adapter = :solid_queue
config.solid_queue.connects_to = { database: { writing: :queue } }

7. Configure Solid Cache

# config/environments/production.rb
config.cache_store = :solid_cache_store
config.solid_cache.connects_to = { database: { writing: :cache } }

8. Add Development Gems

# Gemfile
group :development, :test do
  gem "debug", platforms: %i[mri windows], require: "debug/prelude"
  gem "brakeman", require: false
  gem "rubocop-rails-omakase", require: false
  gem "bullet"
end
bundle install

9. Configure Bullet (N+1 Detection)

# config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
end

# config/environments/test.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.raise = true
end

10. Add Application Layout Defaults

<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= content_for(:title) || "MyApp" %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>
  <body class="min-h-screen bg-white">
    <div id="flash">
      <% flash.each do |type, message| %>
        <div class="flash flash-<%= type %>"><%= message %></div>
      <% end %>
    </div>

    <main class="container mx-auto px-4 py-8">
      <%= yield %>
    </main>
  </body>
</html>

Verification

Run the test suite to confirm everything is wired up:

bin/rails test

Smoke Test

# test/models/user_test.rb
require "test_helper"

class UserTest < ActiveSupport::TestCase
  test "fixture user is valid" do
    user = users(:one)
    assert user.valid?
  end

  test "requires email_address" do
    user = User.new(password: "password123", account: accounts(:one))
    assert_not user.valid?
    assert user.errors[:email_address].any?
  end

  test "requires unique email_address" do
    existing = users(:one)
    user = User.new(
      email_address: existing.email_address,
      password: "password123",
      account: accounts(:one)
    )
    assert_not user.valid?
    assert user.errors[:email_address].any?
  end
end
# test/models/account_test.rb
require "test_helper"

class AccountTest < ActiveSupport::TestCase
  test "fixture account is valid" do
    account = accounts(:one)
    assert account.valid?
  end

  test "requires name" do
    account = Account.new
    assert_not account.valid?
    assert account.errors[:name].any?
  end
end

Directory Structure After Setup

app/
├── controllers/
│   ├── application_controller.rb
│   ├── concerns/
│   │   └── authentication.rb
│   └── sessions_controller.rb
├── models/
│   ├── account.rb
│   ├── current.rb
│   ├── session.rb
│   └── user.rb
├── views/
│   └── layouts/
│       └── application.html.erb
config/
├── database.yml
├── queue.yml
├── cable.yml
└── environments/
    ├── development.rb
    ├── production.rb
    └── test.rb
test/
├── test_helper.rb
├── fixtures/
│   ├── accounts.yml
│   ├── users.yml
│   └── sessions.yml
└── models/
    ├── user_test.rb
    └── account_test.rb

Rails 8 Defaults

Rails 8 ships with these defaults already configured:

  • Solid Queue - Database-backed Active Job backend (replaces Redis/Sidekiq)
  • Solid Cache - Database-backed cache store (replaces Redis/Memcached)
  • Solid Cable - Database-backed Action Cable adapter
  • Propshaft - Asset pipeline (replaces Sprockets)
  • Import Maps - JavaScript without bundling
  • Kamal - Deployment
  • Thruster - HTTP/2 proxy with asset caching

Checklist

  • Rails app generated
  • Authentication generated and migrated
  • Account model created (multi-tenancy)
  • Current model configured with account
  • Test helper with sign_in/sign_out
  • Fixtures created (accounts, users, sessions)
  • Solid Queue configured
  • Solid Cache configured
  • Bullet configured for N+1 detection
  • Smoke tests pass
  • All tests GREEN
Weekly Installs
2
First Seen
7 days ago
Installed on
opencode2
gemini-cli2
antigravity2
claude-code2
windsurf2
codex2