Ruby: Rails Fundamentals
Ruby on Rails is a full-stack web framework built on the MVC pattern. Convention over configuration: Rails generates predictable paths, names, and structures so you can focus on business logic.
Routes
# config/routes.rb
Rails.application.routes.draw do
# RESTful resources — generates 7 standard routes (index, show, new, create, edit, update, destroy)
resources :users
resources :posts do
resources :comments, only: [:create, :destroy] # nested
member do
post :publish # POST /posts/:id/publish
end
collection do
get :archived # GET /posts/archived
end
end
# Custom routes
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
# Namespace (admin panel)
namespace :admin do
resources :users # /admin/users → Admin::UsersController
end
root 'home#index' # GET / → HomeController#index
end
# Inspect routes
# rails routes
# rails routes --grep usersControllers
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authorize_owner!, only: [:edit, :update, :destroy]
def index
@posts = Post.published.order(created_at: :desc).page(params[:page])
render json: @posts # or just let it render index.html.erb
end
def show
# @post set by before_action
end
def create
@post = current_user.posts.build(post_params)
if @post.save
redirect_to @post, notice: 'Post created!'
else
render :new, status: :unprocessable_entity
end
end
def update
if @post.update(post_params)
redirect_to @post
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@post.destroy
redirect_to posts_path, status: :see_other
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :published, tag_ids: [])
end
def authorize_owner!
redirect_to root_path unless @post.user == current_user
end
endActiveRecord: Models & Associations
# app/models/user.rb
class User < ApplicationRecord
# Associations
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
has_one :profile, dependent: :destroy
belongs_to :organization, optional: true
has_and_belongs_to_many :roles
# Validations
validates :email, presence: true, uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
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 :admins, -> { where(role: 'admin') }
scope :recent, -> { order(created_at: :desc) }
# Enum
enum status: { pending: 0, active: 1, suspended: 2 }
private
def normalize_email
self.email = email.downcase.strip
end
end
# Queries
User.all
User.find(1)
User.find_by(email: 'alice@example.com') # nil if not found
User.find_by!(email: '...') # raises if not found
User.where(active: true).order(:name).limit(10)
User.where('age > ?', 18)
User.where(age: 18..65)
User.joins(:posts).where(posts: { published: true })
User.includes(:posts, :profile) # eager load — avoid N+1
User.count
User.pluck(:email) # ["alice@...", "bob@..."] — no model objectsMigrations & Database
# Generate migration
# rails generate migration AddPublishedToPosts published:boolean:index
class AddPublishedToPosts < ActiveRecord::Migration[7.2]
def change
add_column :posts, :published, :boolean, default: false, null: false
add_index :posts, :published
add_column :posts, :published_at, :datetime
# Other common helpers:
# add_reference :posts, :category, null: false, foreign_key: true
# rename_column :users, :username, :login
# remove_column :users, :legacy_field
# change_column_null :users, :email, false
end
end
# Run migrations
# rails db:migrate
# rails db:migrate:status
# rails db:rollback STEP=2
# rails db:schema:load # recreate from schema.rb (faster for fresh setup)
# Seeds (db/seeds.rb)
User.create!(name: 'Admin', email: 'admin@example.com', role: 'admin')
10.times { User.create!(Faker::User.safe_message) }Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free