Setting up a Rails project: patterns, pitfalls and fixes
Getting a new Rails project off the ground should be straightforward, but the choices we make in those first few minutes—from Ruby version management to database selection—compound quickly. We've all inherited projects where the development environment was fragile, dependencies were pinned inconsistently, or the build process required undocumented steps. This guide walks through the proven patterns we've used in production e-commerce and SaaS teams to set up Rails projects that remain maintainable from day one.
Install Ruby — use a version manager like mise or rbenv
The single biggest source of "works on my machine" friction is inconsistent Ruby versions across the team. System Ruby, which ships with macOS or Linux, is often outdated and managed by the OS package manager—never the right source of truth for application development. Instead, we use version managers to pin Ruby to a specific release that everyone on the team runs.
Two solid options dominate production workflows: rbenv has been the standard for years, with a simple design and broad adoption. mise (formerly rtx) is newer, faster, and handles multiple language runtimes in a single tool—useful when your projects also need Node, Python, or Postgres CLI tools at specific versions.
Here's how to set up rbenv on macOS or Linux:
# Install rbenv (macOS with Homebrew)
brew install rbenv ruby-build
# Add rbenv to your shell
echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc
source ~/.bashrc
# List available Ruby versions
rbenv versions
# Install Ruby 3.3.0 (current stable)
rbenv install 3.3.0
# Set it as default
rbenv global 3.3.0
# Verify
ruby --version
# ruby 3.3.0 (2023-12-25 revision 5aac304e19)With mise, the setup is even simpler if you're already using it for other tools:
# Install and set Ruby 3.3.0 globally
mise use --global ruby@3.3.0
# Or pin it for a specific project
cd ~/projects/shop
mise use ruby@3.3.0
# Creates .mise.toml with the versionOnce your team shares a .ruby-version file (rbenv) or .mise.toml (mise), developers who have the tool installed will automatically switch to the correct version when they enter the project directory. This eliminates entire categories of version-mismatch bugs before they start.
Create an app — one command with sensible defaults
Rails has matured to the point where rails new handles most decisions for us. The command accepts options that set up the right foundation: database choice, CSS framework, JavaScript bundler, and testing libraries.
Start a new project with PostgreSQL (the database of choice for most teams we know) and skip the default SQLite:
rails new shop --database=postgresql
cd shop
bin/rails serverThis creates a directory structure with models, controllers, views, migrations, tests, and configuration files all in conventional locations. The Rails team has spent years optimizing these defaults for real applications.
In practice, we usually add a few more flags based on what we know about the project upfront:
# Skip test unit (we'll use RSpec instead, added later)
rails new shop --database=postgresql --skip-test
# Include CSS bundling with importmap (no Node build step needed)
rails new shop --database=postgresql --css=tailwind
# Skip Spring (can cause stale code issues in development)
rails new shop --database=postgresql --skip-springFor an e-commerce app, we might combine several options:
rails new shop \
--database=postgresql \
--css=tailwind \
--javascript=esbuild \
--skip-spring \
--skip-testThe key insight: Rails' generators are opinionated and battle-tested. Deviating from them requires a real reason. When we've tried building our own asset pipeline or test setup from scratch, we've always regretted it. Use what Rails gives you, then layer in specific choices (RSpec, authentication libraries, job queues) once the foundation is solid.
Generators — bin/rails generate scaffolds models and controllers
Rails generators save time, but they're also a gateway to messy code if used carelessly. We use them strategically: for scaffolding early-stage features and for creating standard resource structures, but we delete and rewrite the views immediately.
A scaffold generates a complete model, migration, controller, and views for a resource:
bin/rails generate scaffold Product \
name:string \
description:text \
price:decimal \
stock:integerThis creates:
- A migration file (create the products table)
- A Product model with validations
- A ProductsController with CRUD actions
- Views for index, show, new, edit
- Routes
Run the migration to create the table:
bin/rails db:create
bin/rails db:migrateThe scaffold is useful for getting something working fast, but the generated code is rarely production-ready. The controller actions are too simple for real business logic, the views lack proper CSS structure (even with Tailwind), and error handling is minimal. We treat scaffolds as a starting template: inspect the generated code, keep what's useful (the model and migration usually are), and replace the controller and views with our own implementation.
For plain model and controller generation (without views), which is often what we actually want:
# Just the model and migration
bin/rails generate model Product name:string price:decimal
# Just the controller
bin/rails generate controller Products index show createUsing generators thoughtfully keeps us in sync with Rails conventions without shipping scaffolded code to production.
Dev setup — bin/dev runs the app with CSS and JS watchers
Rails 7+ includes a Procfile that coordinates the web server, CSS watcher, and JavaScript bundler. This single command starts everything:
bin/devBehind the scenes, it runs:
bin/rails server(the Rails app on port 3000)- CSS build watcher (if using Tailwind, PostCSS, or similar)
- JavaScript bundler watcher (if using esbuild, rollup, or similar)
This approach sidesteps the complexity of running multiple processes manually or fighting with asset precompilation. The Procfile is environment-aware, so it picks the right commands based on what you selected during rails new.
On a team with multiple developers, consistency matters. Make sure everyone's environment runs the same command:
# In the project root, typically already present after rails new
cat Procfile
# Output (with Tailwind CSS):
# web: bin/rails server -p 3000
# css: bin/rails tailwindcss:watch
# js: node esbuild.config.mjsIf a developer tries to run just bin/rails server, they'll miss CSS changes and won't see updated JavaScript. Training the team to use bin/dev from day one prevents confusion.
Common mistakes
Mixing Ruby versions without documenting them. We've seen projects where senior developers all had Ruby 3.2, a newer hire had 2.7, and gems with native extensions failed to build for days. Solution: commit .ruby-version or .mise.toml to version control and document it in the README.
Over-customizing the initial setup. Choosing an obscure CSS framework, integrating a custom JavaScript build, or swapping out the default test framework before shipping a single feature adds friction for no gain. Stick with Rails defaults for 80% of projects; customize only when you have specific production constraints.
Running bin/rails server instead of bin/dev. This skips the asset watchers, leading to developer confusion when CSS changes don't appear or JavaScript doesn't update. Make it a habit from the start.
Forgetting to initialize the database. New team members clone the repo, run the app, and hit a missing table error. Always include migration steps in the onboarding documentation or in a setup script.
Scaffolding everything, shipping scaffolds to production. Scaffold views are bare-bones HTML with minimal styling. They're useful for testing the model and controller layer quickly, but replacing them with real views should be immediate, not postponed.
Key takeaways
- Use a version manager (rbenv or mise) and commit the Ruby version to your repository so the entire team uses the same release.
- Let
rails newhandle the initial structure with sensible options like--database=postgresqland--css=tailwind; deviating from Rails conventions should be intentional and rare. - Generators are shortcuts for boilerplate, not templates for shipping code. Use them to scaffold quickly, then replace the views and refine the controller logic.
- Run
bin/dev, notbin/rails server, to keep CSS and JavaScript watchers active during development. Make this the team standard. - Document your database setup and any custom commands in the README so new developers and CI/CD pipelines can reproduce the environment without guessing.






Comments
Loading comments…