Skip to content
ExponaBLOG
RUBY ON RAILS · DAY 091 / 100

Rails generators and scaffolds: best practices explained

Start features quickly.

Expona Admin6 min read

We spend a lot of time boilerplate code when building Rails features, but the framework gives us powerful tools to reduce that friction. Rails generators and scaffolds: best practices explained covers how to use these tools effectively while avoiding the trap of accepting auto-generated code that doesn't fit your application's architecture.

Understanding scaffold: full CRUD in one command

The scaffold generator is Rails' most aggressive tool. It creates a complete resource with model, migration, controller, views, tests, and routes in a single command. For a blog application, running rails generate scaffold Post title:string content:text published:boolean produces 23 files covering the entire create-read-update-delete cycle.

Scaffold is valuable for prototyping and learning Rails patterns, but its real strength emerges when you use it as a starting template rather than a destination. In production systems, we've found that accepting scaffold's opinions wholesale leads to problems: the generated controller logic rarely matches your business rules, the views assume a specific UI pattern, and the relationships between models often need adjustment.

A practical approach: use scaffold for isolated features with straightforward business logic, then customize aggressively. For a basic admin dashboard feature managing tags or categories, scaffold might need only minor tweaks. For your core domain model—the Post, Order, or User resource—you're better served by building incrementally with targeted generators.

ruby
$ rails generate scaffold Article title:string body:text author:references published_at:datetime

# This creates:
# app/models/article.rb
# app/controllers/articles_controller.rb
# app/views/articles/ (index, show, new, edit, _form)
# config/routes.rb (resources :articles added)
# db/migrate/[timestamp]_create_articles.rb
# test/models/article_test.rb
# test/controllers/articles_controller_test.rb

The key decision point: if you need to modify more than 40% of the generated code, you should have built those pieces manually. Scaffold shines when it handles 70-90% of what you need.

Model and migration generators for precision

While scaffold creates entire features, the model and migration generators give you surgical control. rails generate model creates just the model class and migration, letting you define your domain layer without view assumptions.

In a real SaaS application managing subscriptions, we often generate the core model separately:

ruby
$ rails generate model Subscription customer:references plan:string status:string expires_at:datetime

# Creates only:
# app/models/subscription.rb
# db/migrate/[timestamp]_create_subscriptions.rb
# test/models/subscription_test.rb

This approach lets you define associations, validations, and business logic before touching controllers or views. You can test your model thoroughly, define scopes for common queries, and establish the data contract your application depends on.

The migration generator respects Rails conventions while giving you full flexibility:

ruby
$ rails generate migration AddStatusToSubscriptions status:string:index cancelled_at:datetime

# Generates a migration that you review and adjust as needed
# You control column types, indexes, and constraints

We've seen teams run into performance issues because they accepted default migration configurations. An unindexed foreign key or missing unique constraint on scaffold-generated code became a production bottleneck weeks later. Always audit migrations before deploying, regardless of how they were generated.

Customize generator templates for consistency

Rails generators use ERB templates stored in your application's lib/templates directory. By customizing these templates, you can enforce consistent patterns across all generated code—your team's testing style, controller organization, view structure, or API response format.

Most teams discover this after generating their tenth controller and realizing they've been fixing the same issues each time. Building custom templates costs time upfront but pays dividends at scale.

To customize scaffold views, create the template structure:

ruby
# lib/templates/erb/scaffold/index.html.erb
# lib/templates/erb/scaffold/show.html.erb
# lib/templates/erb/scaffold/new.html.erb
# lib/templates/erb/scaffold/edit.html.erb
# lib/templates/erb/scaffold/_form.html.erb

# For controllers:
# lib/templates/rails/scaffold_controller/controller.rb.tt

# For tests:
# lib/templates/rails/scaffold_controller/controller_test.rb.tt

A practical example: if your application uses ViewComponent, you can generate a component scaffold instead of a views scaffold. If you standardize on JSON:API format, your controller template can emit proper JSON:API responses by default. If your team enforces specific view helpers or ARIA attributes, the template ensures consistency without manual checking.

Here's a minimal custom controller template that includes your standard authorization pattern:

ruby
# lib/templates/rails/scaffold_controller/controller.rb.tt
class <%= controller_class_name %>Controller < ApplicationController
  before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy]
  before_action :authorize_<%= singular_table_name %>!, except: [:index, :new, :create]

  def index
    @<%= plural_table_name %> = current_user.<%= plural_table_name %>
  end

  # ... rest of actions
end

This template automatically includes your authorization check, scoped to the current user. New scaffolds respect your security model without requiring per-controller customization.

Delete what you do not need

Scaffold generates defensively—it includes features many applications never use. Cleaning up aggressively keeps your codebase lean and your team's cognitive load lower.

After running a scaffold, immediately assess each generated file:

  • Views: Does your application need both new and create routes? Can you combine them into a single form? Are all seven view files actually needed? Many APIs don't need HTML views at all.
  • Tests: Scaffold generates controller tests with a standard pattern. Most teams need to rewrite these to match their testing strategy. Don't keep placeholder tests—either write real tests or delete the file and start fresh.
  • Routes: The default resources :posts creates seven routes. If you only need index and show, use resources :posts, only: [:index, :show].
  • Controller actions: Not every resource needs edit and delete. Define your actual requirements first, then remove unused actions.

A frequently overlooked cleanup: the _form.html.erb` partial. Scaffold generates a form that works generically but rarely handles validation errors, complex relationships, or conditional fields elegantly. Replace it early with a form that reflects your actual user experience.

In an e-commerce platform, we scaffolded the Product resource, then removed: the generic edit view (products used a modal workflow), the delete action (products were soft-deleted), and most of the generated tests (we had domain-specific test suites). Keeping the scaffold's structure would have confused new team members about what was actually in use.

The principle: every line of code is a maintenance liability. If scaffold generates it but you don't need it, remove it immediately. Deletion is faster than later refactoring.

Common mistakes

Mistake 1: Using scaffold for your core domain. Your Order, User, or Post model carries business logic that scaffold doesn't anticipate. You'll spend more time removing scaffold's assumptions than you saved generating code. Build these models manually.

Mistake 2: Skipping test generation review. Scaffold generates tests that pass but don't reflect your actual coverage strategy. A passing test that doesn't test your business rules is worse than no test. Review generated tests and rewrite them to match your standards.

Mistake 3: Committing unnecessary dependencies. If your feature needs Pundit for authorization but scaffold doesn't know about Pundit, you're already out of sync. Customize your generator templates first, or accept that you'll manually add this to every scaffold.

Mistake 4: Not indexing foreign keys. Scaffold creates migrations with foreign keys but often forgets indexes. A lookup query that scans the entire users table becomes a production incident. Always review migrations before running them.

Mistake 5: Over-customizing generator templates. If your template handles 30 different application-specific cases, it's too complex. Generator templates should enforce consistent structure, not encode all business logic. Complex templates confuse new team members and become maintenance burdens.

Key takeaways

  • Scaffold is a template, not a destination. Use it to accelerate simple features, then customize aggressively or build from scratch for complex domain models.
  • Model and migration generators offer surgical precision for building incrementally. Prefer these over scaffold when you need control over your data layer.
  • Customize generator templates once to enforce your team's patterns consistently across all generated code. The upfront investment pays back quickly at scale.
  • Delete generated code you don't need. Every file is a maintenance cost. If scaffold generated it but your application doesn't use it, remove it immediately.
  • Review all migrations before deployment, regardless of how they were generated. Foreign key indexes and constraints are easy to forget and expensive to add later.
  • Generate tests are rarely sufficient. Rewrite them to match your coverage strategy and business logic, or delete them and build proper tests separately.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles