Overview
Ruby on Rails (often just “Rails”) is a full-stack web application framework written in Ruby that follows the Model-View-Controller (MVC) architectural pattern. Created by David Heinemeier Hansson in 2004, Rails emphasizes convention over configuration (CoC) and the DRY (Don’t Repeat Yourself) principle, enabling rapid application development with sensible defaults. It includes everything needed to build database-backed web applications: an ORM (Active Record), routing, templating, asset pipeline, and testing framework.
Rails powers some of the world’s largest websites and applications including Shopify, GitHub, Basecamp, and Airbnb. The framework has continuously evolved through major versions, with Rails 7+ introducing Hotwire (Turbo + Stimulus) for building modern interactive applications without heavy JavaScript frameworks. Rails also includes Action Cable for WebSocket support, Active Job for background processing, and Active Storage for file uploads.
Installation
Setup
# Install Ruby (3.1+ recommended)
rbenv install 3.3.0
rbenv global 3.3.0
# Install Rails
gem install rails
# Create new application
rails new myapp
rails new myapp --database=postgresql
rails new myapp --api # API-only mode
rails new myapp --css=tailwind # With Tailwind CSS
rails new myapp --skip-test --skip-jbuilder
# Setup and run
cd myapp
bin/setup
bin/rails server
Project Structure
| Path | Description |
|---|
app/models/ | Active Record models |
app/controllers/ | Action controllers |
app/views/ | ERB/HTML templates |
app/helpers/ | View helper methods |
app/jobs/ | Background jobs |
app/mailers/ | Email handlers |
app/channels/ | WebSocket channels |
config/routes.rb | URL routing |
config/database.yml | Database configuration |
db/migrate/ | Database migrations |
db/schema.rb | Current schema snapshot |
test/ or spec/ | Test files |
Gemfile | Ruby dependencies |
Generators
| Command | Description |
|---|
rails g model User name:string email:string | Generate model + migration |
rails g controller Users index show | Generate controller + views |
rails g scaffold Post title:string body:text | Full CRUD resource |
rails g migration AddAgeToUsers age:integer | Generate migration |
rails g mailer UserMailer welcome | Generate mailer |
rails g job ProcessPayment | Generate background job |
rails g channel Chat | Generate WebSocket channel |
rails destroy model User | Remove generated files |
Routing
# config/routes.rb
Rails.application.routes.draw do
root "pages#home"
resources :users do
resources :posts, only: [:index, :create]
member do
post :activate
end
collection do
get :search
end
end
namespace :admin do
resources :dashboard, only: [:index]
end
scope "/api/v1", module: "api/v1" do
resources :articles, only: [:index, :show]
end
get "about", to: "pages#about"
get "health", to: proc { [200, {}, ["OK"]] }
end
# View all routes
bin/rails routes
bin/rails routes -g users # Grep routes
Active Record
Models
class User < ApplicationRecord
# Associations
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
has_one :profile
belongs_to :organization, optional: true
# Validations
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
validates :email, presence: true, uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }
validates :age, numericality: { greater_than: 0 }, allow_nil: true
# Callbacks
before_save :normalize_email
after_create :send_welcome_email
# Scopes
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
scope :adults, -> { where("age >= ?", 18) }
# Enums
enum :role, { user: 0, moderator: 1, admin: 2 }
private
def normalize_email
self.email = email.downcase.strip
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
Queries
# Finding records
User.find(1) # By ID (raises if not found)
User.find_by(email: "a@b.com") # First match (returns nil)
User.where(active: true) # Collection
User.where("age > ?", 18) # SQL condition
User.where(role: [:admin, :moderator])
# Chaining
User.active.recent.where(role: :admin).limit(5)
# Aggregations
User.count
User.average(:age)
User.group(:role).count
# Eager loading (avoid N+1)
User.includes(:posts).where(posts: { published: true })
User.preload(:comments)
# Raw SQL
User.find_by_sql("SELECT * FROM users WHERE age > 18")
# Batching
User.find_each(batch_size: 1000) { |user| process(user) }
Migrations
bin/rails db:migrate
bin/rails db:rollback
bin/rails db:migrate:status
bin/rails db:seed
bin/rails db:reset # Drop + create + migrate + seed
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false
t.integer :age
t.references :organization, foreign_key: true
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, [:name, :organization_id]
end
end
Controllers
class UsersController < ApplicationController
before_action :authenticate_user!
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
@users = User.active.page(params[:page])
end
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: "User created."
else
render :new, status: :unprocessable_entity
end
end
def update
if @user.update(user_params)
redirect_to @user, notice: "User updated."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@user.destroy
redirect_to users_path, notice: "User deleted.", status: :see_other
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:name, :email, :age, :role)
end
end
Configuration
Database Configuration
# config/database.yml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: myapp_development
test:
<<: *default
database: myapp_test
production:
<<: *default
url: <%= ENV["DATABASE_URL"] %>
Environment Configuration
# config/environments/production.rb
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
config.active_job.queue_adapter = :sidekiq
config.force_ssl = true
config.log_level = :info
end
Advanced Usage
Background Jobs
class ProcessPaymentJob < ApplicationJob
queue_as :critical
retry_on Stripe::RateLimitError, wait: :polynomially_longer, attempts: 5
discard_on ActiveRecord::RecordNotFound
def perform(order_id)
order = Order.find(order_id)
PaymentService.charge(order)
end
end
# Enqueue
ProcessPaymentJob.perform_later(order.id)
ProcessPaymentJob.set(wait: 1.hour).perform_later(order.id)
Action Mailer
class UserMailer < ApplicationMailer
def welcome(user)
@user = user
mail(to: @user.email, subject: "Welcome!")
end
end
# Send
UserMailer.welcome(user).deliver_later
Hotwire (Turbo + Stimulus)
<%# Turbo Frame %>
<%= turbo_frame_tag "user_#{@user.id}" do %>
<%= render @user %>
<% end %>
<%# Turbo Stream %>
<%= turbo_stream.append "messages", partial: "messages/message", locals: { message: @message } %>
Testing
# test/models/user_test.rb
class UserTest < ActiveSupport::TestCase
test "should not save user without name" do
user = User.new(email: "test@example.com")
assert_not user.save
end
end
# test/controllers/users_controller_test.rb
class UsersControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get users_url
assert_response :success
end
end
Troubleshooting
| Problem | Solution |
|---|
PendingMigrationError | Run bin/rails db:migrate |
| N+1 query warnings | Use includes(:association) for eager loading |
ActionController::ParameterMissing | Check params.require and permit in controller |
| Asset pipeline errors | Run bin/rails assets:precompile |
Couldn't find User with id= | Check record exists; use find_by for nil return |
| Slow queries | Add database indexes; check with EXPLAIN ANALYZE |
| Memory issues in production | Tune WEB_CONCURRENCY and RAILS_MAX_THREADS |
| CSRF token errors | Ensure <%= csrf_meta_tags %> in layout |