Kết hợp Primary keys cho ActiveRecords
Bài đăng này đã không được cập nhật trong 7 năm
1. Giới thiệu
Trong một vài trường hợp, khi cần thao tác với bảng trung gian chứa khóa ngoại đến các bảng khác, chúng ta có thể không để primary_key id. Nguyên nhân là do số lượng record trong bảng này tăng rất nhanh nên giá trị của id sẽ sớm vượt giới hạn lưu trữ, nên thông thường, bảng trung gian như này sẽ bỏ cột id và các record được xác định thông qua các khóa ngoại.
Ví dụ: ta tạo các bảng sau:
# tạo bảng users
def up
create_table :users do |t|
t.string :name
end
end
# tạo bảng products
def up
create_table :products do |t|
t.string :name
end
end
giả sử ta có bảng trung gian orders
def up
create_table :orders, id: false do |t|
t.references :product
t.references :user
t.integer :status
t.index [:product_id, :user_id], unique: true
end
end
Nếu muốn tìm kiếm trong bảng orders, ta có thể sử dụng lệnh where
Order.where(product_id: 1, user_id: 1).first
khi muốn update 1 record thì phải sử dụng update_all
Order.where(product_id: 1, user_id: 1).update_all status: 1
Tuy nhiên, có một vài project sẽ hạn chế không cho dùng update_all, ví dụ chạy gem rubocop chẳng hạn, bởi vì như ta đã biết update_all
sẽ không chạy callback.
Nếu sử dụng update_attributes
thì sẽ gây ra lỗi
Order.where(product_id: 1, user_id: 1).first.update_attributes status: 1
=> TypeError: nil is not a symbol nor a string
Sử dụng hàm destroy
cũng gây ra lỗi tương tự. Nguyên nhân là do update_attributes
và destroy
yêu cầu phải có cột primary_key để xác định record , nhưng ActiveRecord hiện không hỗ trợ kết hợp primary keys
Ngoài ra, việc tạo quan hệ với các bảng khác sẽ khó khăn, vì bảng orders được định nghĩa thông qua 2 khóa ngoại.
Gem composite_primary_keys hỗ trợ khả năng kết hợp 2 khóa để tạo thành primary_key cho table.
2. Cài đặt
Chúng ta cài gem bằng lệnh
gem install composite_primary_keys
hay thêm vào trong file Gemfile
gem 'composite_primary_keys'
và chạy bundle install
Ta thêm cài đặt primary_key trong model order
class Order < ApplicationRecord
self.primary_key = :user_id, :product_id
belongs_to :user
belongs_to :product
end
primary_key sẽ được định nghĩa theo 2 khóa ngoại self.primary_key = :user_id, :product_id
Ta chạy lệnh
Order.primary_key # => ["user_id", "product_id"]
Order.primary_key.to_s # => "user_id,product_id"
Khi đó, để tìm 1 bản ghi order ta truyền mảng khóa ngoại vào hàm find
Order.find([1,1])
# lệnh trên sẽ tạo ra query
SELECT `orders`.* FROM `orders` WHERE (`orders`.`user_id` = 1 AND `orders`.`product_id` = 1) LIMIT 1
Thực hiện update và destroy
Order.find([1,1]).update_attributes status: 1
UPDATE `orders` SET `status` = 1 WHERE (`orders`.`user_id` = 1 AND `orders`.`product_id` = 1)
Order.find([1,1]).destroy
DELETE FROM `orders` WHERE `orders`.`user_id` = 1 AND `orders`.`product_id` = 1
Tạo quan hệ với bảng khác:
Ta thêm quan hệ với bảng notifications
def change
create_table :notifications do |t|
t.references :user
t.references :product
t.datetime :nofified_date
end
end
để định nghĩa quan hệ has_many của order với notifications, ta dùng:
# order.rb
has_many :notifications, foreign_key: [:user_id, :product_id]
# notification.rb
class Notification < ApplicationRecord
belongs_to :order, foreign_key: [:user_id, :product_id]
end
Join giữa các bảng như table bình thường
Order.joins(:notifications)
SELECT `orders`.* FROM `orders` INNER JOIN `notifications` ON `notifications`.`user_id` = `orders`.`user_id` AND `notifications`.`product_id` = `orders`.`product_id`
Lưu ý
Chỉ nên dùng gem composite_primary_keys
với các bảng có khóa ngoại unique.
Tài liệu hướng dẫn: composite_primary_keys
All rights reserved