Kết hợp Primary keys cho ActiveRecords
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 ...
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