80 phút
ActiveRecord và Migrations
Migrations
Tạo migration
rails g migration CreateProducts name:string price:decimal stock:integer
Migration file
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products do |t|
t.string :name, null: false
t.text :description
t.decimal :price, precision: 12, scale: 2, null: false
t.integer :stock, default: 0, null: false
t.references :category, null: false, foreign_key: true
t.timestamps
end
add_index :products, :name
add_index :products, [:category_id, :name], unique: true
end
end
Migration commands
rails db:migrate
rails db:rollback
rails db:migrate:status
rails db:reset
rails db:seed
Modify table
class AddPublishedToPosts < ActiveRecord::Migration[7.1]
def change
add_column :posts, :published_at, :datetime
add_index :posts, :published_at
add_reference :posts, :user, foreign_key: true
change_column_null :posts, :title, false
change_column_default :posts, :views, from: nil, to: 0
end
end
Models
Basic Model
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_secure_password
validates :name, presence: true, length: { in: 2..100 }
validates :email, presence: true, uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }
before_save :downcase_email
after_create :send_welcome_email
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc) }
private
def downcase_email
self.email = email.downcase
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
Associations
has_many / belongs_to
class Author < ApplicationRecord
has_many :books, dependent: :destroy
has_many :reviews, through: :books
end
class Book < ApplicationRecord
belongs_to :author
has_many :reviews, dependent: :destroy
end
class Review < ApplicationRecord
belongs_to :book
belongs_to :user
end
has_one
class User < ApplicationRecord
has_one :profile, dependent: :destroy
end
class Profile < ApplicationRecord
belongs_to :user
end
has_many :through
class Doctor < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Patient < ApplicationRecord
has_many :appointments
has_many :doctors, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :doctor
belongs_to :patient
end
Polymorphic
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Photo < ApplicationRecord
has_many :comments, as: :commentable
end
Querying
Basic queries
# Find
User.all
User.first
User.last
User.find(1)
User.find([1, 2, 3])
User.find_by(email: 'user@example.com')
User.find_by!(email: 'user@example.com')
# Where
User.where(active: true)
User.where(age: 18..65)
User.where.not(role: 'admin')
User.where(created_at: 1.week.ago..)
# Order
User.order(:name)
User.order(created_at: :desc)
User.order(name: :asc, created_at: :desc)
# Limit và Offset
User.limit(10).offset(20)
# Select
User.select(:id, :name)
# Group và Having
Order.group(:user_id).having('count(*) > 5').count
Order.group(:status).sum(:total)
# Joins
User.joins(:posts).where(posts: { published: true })
User.left_joins(:posts).where(posts: { id: nil })
# Includes (N+1 prevention)
Post.includes(:comments, :author).all
Find or create
User.find_or_create_by(email: 'user@example.com') do |u|
u.name = 'John'
end
User.find_or_initialize_by(email: 'user@example.com')
User.create_or_find_by(email: 'user@example.com')
Batch processing
User.find_each(batch_size: 100) do |user|
# process user
end
User.find_in_batches(batch_size: 100) do |users|
# process batch
end
Scopes
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
scope :by_author, ->(author_id) { where(author_id: author_id) }
scope :popular, -> { where('views > ?', 1000) }
# Default scope (dùng cẩn thận)
# default_scope { where(deleted_at: nil) }
end
Post.published.recent.by_author(1)
Post.published.where('views > ?', 100)
Callbacks
class Post < ApplicationRecord
before_validation :normalize_title
before_save :calculate_word_count
after_save :update_search_index
before_destroy :check_can_delete
after_commit :send_notification, on: :create
private
def normalize_title
self.title = title.strip if title.present?
end
def calculate_word_count
self.word_count = body.split.size
end
def update_search_index
SearchIndexJob.perform_later(self)
end
def check_can_delete
throw :abort if locked?
end
def send_notification
NotificationJob.perform_later(id)
end
end
Seeds
# db/seeds.rb
10.times do |i|
User.create!(
name: "User #{i}",
email: "user#{i}@example.com",
password: "password123"
)
end
User.find_each do |user|
5.times do |i|
user.posts.create!(
title: "Post #{i} by #{user.name}",
body: Faker::Lorem.paragraphs(number: 3).join("\n\n"),
published: [true, false].sample
)
end
end
rails db:seed
rails db:reset # drop + create + migrate + seed
Bài tập thực hành
Hãy tạo model với associations và queries!