What it is
Rails is a full-stack web framework covering routing, ORM, migrations, background jobs, mailers, caching and testing, built on convention over configuration.
Models, views and controllers follow naming conventions that wire themselves together. ActiveRecord derives columns from the schema, so there is no mapping to maintain.
Installation
gem install railsGetting started
The smallest useful thing you can do with it, and what each part means.
class Book < ApplicationRecord
belongs_to :author
has_many :reviews, dependent: :destroy
validates :title, presence: true, length: { maximum: 200 }
validates :isbn, uniqueness: true, allow_nil: true
scope :recent, -> { where(published_at: 1.year.ago..) }
scope :highly_rated, -> {
joins(:reviews).group(:id).having('AVG(reviews.rating) > 4')
}
def average_rating = reviews.average(:rating)&.round(1)
end
# Scopes compose into one lazy query.
Book.recent.highly_rated.includes(:author).limit(10)class BooksController < ApplicationController
before_action :set_book, only: %i[show update destroy]
def index
@books = Book.includes(:author).recent.page(params[:page])
end
def create
@book = Book.new(book_params)
if @book.save
render json: @book, status: :created
else
render json: { errors: @book.errors }, status: :unprocessable_entity
end
end
private
def set_book = @book = Book.find(params[:id]) # raises 404 if missing
def book_params
params.require(:book).permit(:title, :year, :author_id)
end
endAdvanced usage
Where the library earns its place over a simpler alternative.
class ImportBooksJob < ApplicationJob
queue_as :imports
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 5
discard_on ActiveRecord::RecordNotFound
def perform(import_id) # pass the id, never the record
import = Import.find(import_id)
Import.transaction do
import.rows.each { |row| Book.create!(row.attributes) }
import.update!(status: :complete)
end
end
end
# Enqueue only after the surrounding transaction commits.
Book.transaction do
book.save!
NotifySubscribersJob.perform_later(book.id)
endErrors and fixes
The failures you are most likely to hit, and what actually resolves them.
- ActiveRecord::RecordNotFound
- find raises when nothing matches, which Rails renders as 404. Use find_by if nil is an acceptable outcome.
- The page is slow and issues hundreds of queries
- N+1 from a missing includes. Bullet will identify the exact association.
Best practices
- Use includes or preload for any association a view touches; add the Bullet gem to catch N+1 in development.
- Pass record ids to jobs, not the records themselves.
- Keep controllers thin — extract logic into service, form or query objects before models reach a thousand lines.
- Never skip strong parameters; permitting everything is a mass-assignment vulnerability.
Background
Why it exists, and what it was reacting to.
Extracted from Basecamp by David Heinemeier Hansson in 2004, Rails made database-backed web applications dramatically faster to build. Its conventions were copied into nearly every other ecosystem.
