Ruby on Rails
Rails is a full-stack MVC framework built on “convention over configuration.” Most of what follows applies to Rails 7/8, which leans back toward a simpler, no-build-step default stack (Hotwire, importmaps, SQLite-friendly).
Directory structure
app/
models/ # ActiveRecord models, business logic
controllers/ # thin, orchestrate models + render responses
views/ # ERB templates
jobs/ # ActiveJob background jobs
mailers/ # ActionMailer
channels/ # ActionCable
javascript/ # Stimulus controllers, importmap entry points
config/
routes.rb
database.yml
application.rb
db/
migrate/
schema.rb
Routing
# config/routes.rb
resources :posts do
resources :comments, only: [:create, :destroy]
member { post :publish } # /posts/:id/publish
collection { get :search } # /posts/search
end
resource :profile, only: [:show, :edit, :update] # singular, no :id
resources gives you the 7 RESTful actions: index show new create edit update destroy.
Models (ActiveRecord)
class Post < ApplicationRecord
belongs_to :author, class_name: "User"
has_many :comments, dependent: :destroy
has_one :featured_image
validates :title, presence: true, length: { maximum: 200 }
scope :published, -> { where.not(published_at: nil) }
before_validation :generate_slug, on: :create
end
- Associations:
belongs_to,has_many,has_one,has_and_belongs_to_many,has_many :through. - Validations run before save;
errorscollects failures. - Callbacks (
before_save,after_commit, etc.) — useful but easy to overuse; prefer plain methods called explicitly when the logic isn’t truly “always happens on save.” - Concerns (
app/models/concerns) share behavior across models via modules — good for genuinely shared behavior, not just a place to dump code to avoid a fat file. - Enums:
enum :status, %i[draft published archived].
Migrations
class AddSlugToPosts < ActiveRecord::Migration[7.1]
def change
add_column :posts, :slug, :string
add_index :posts, :slug, unique: true
end
end
rails g migration AddSlugToPosts slug:string:indexrails db:migrate,rails db:rollback,rails db:migrate:status- Add indexes concurrently on Postgres for large tables:
add_index :posts, :slug, algorithm: :concurrently(requiresdisable_ddl_transaction!).
Controllers
class PostsController < ApplicationController
before_action :set_post, only: %i[show edit update destroy]
def index
@posts = Post.published.order(created_at: :desc)
end
def create
@post = current_user.posts.new(post_params)
if @post.save
redirect_to @post, notice: "Post created."
else
render :new, status: :unprocessable_entity
end
end
private
def set_post = @post = Post.find(params[:id])
def post_params = params.require(:post).permit(:title, :body)
end
Keep controllers thin: fetch, authorize, delegate to the model, respond. Push real logic down into models (or a plain Ruby object under app/models/ if it doesn’t belong to one record) rather than into controllers or service-object layers.
Views
- ERB by default:
<%= %>outputs,<% %>doesn’t. - Layouts in
app/views/layouts/application.html.erb,yieldmarks content insertion. - Partials:
_form.html.erb, rendered withrender "form", post: @postorrender @post(usesposts/_post). - Helpers live in
app/helpers/.
Hotwire (default frontend stack since Rails 7)
- Turbo Drive: intercepts link/form navigation, swaps
<body>without a full reload. - Turbo Frames:
<turbo-frame id="...">scopes navigation/updates to a page fragment. - Turbo Streams: server-sent
<turbo-stream action="replace" target="...">fragments for real-time or post-action updates. - Stimulus: lightweight JS controllers (
app/javascript/controllers/) that attach behavior viadata-controllerattributes — no build step needed with importmaps.
Background jobs & async
class WelcomeEmailJob < ApplicationJob
queue_as :default
def perform(user) = UserMailer.welcome(user).deliver_now
end
WelcomeEmailJob.perform_later(user)
Rails 8 defaults to Solid Queue (DB-backed jobs), Solid Cache, and Solid Cable — no Redis required for a standard app.
Testing
- Default: Minitest, in
test/.rails test,rails test:models, etc. - Fixtures in
test/fixtures/*.yml, or use factories (FactoryBot) if preferred. - System tests (
test/system/) drive a real browser via Capybara.
Useful commands
| Command | Purpose |
|---|---|
rails new app_name |
Scaffold a new app |
rails server / rails s |
Start dev server |
rails console / rails c |
REPL with app loaded |
rails generate / rails g |
Generators (model, controller, migration…) |
rails db:prepare |
Create + migrate + seed if needed |
rails routes |
List all routes |
bin/rails credentials:edit |
Edit encrypted credentials |
Rails 8 notable defaults
- SQLite is production-viable (with
solid_*gems handling queue/cache/cable). - Kamal for zero-downtime Docker deploys to your own servers.
- Thruster in front of Puma for HTTP/2, compression, and asset caching.
- Built-in
rails generate authenticationscaffolds a real (bcrypt-based) auth system — no Devise needed for the common case.