Tìm hiểu Single Table Inheritance (STI) trong Rails
This post hasn't been updated for 8 years
1. Single Table Inheritance là gì ?
STI
về cơ bản là ý tưởng sử dụng một bảng duy nhất để phản ánh cho nhiều model được kế thừa từ một model cha. Nó là một thành phần của ActiveRecord::Base
. Trong cơ sở dữ liệu, model con dược xác định bởi trường type
. Trong Rails bạn chỉ việc thêm trường type
vào bảng hệ thống sẽ hiểu bạn đang thiết lập STI. Ví dụ bạn có model Employee
. Các employee có thể chia làm hai loại: Manager
hoặc Developer
. Chúng có chung các thuộc tính nhưng hành vi của chúng khác nhau. Việc tạo hai bảng Manager
và Developer
là không cần thiết. Thay vào đó bạn chỉ cần sử dụng một bảng Employee
để lưu dữ liệu của cả Manager
và Developer
và chúng được phân biệt nhau bởi trường type
.
2. Sử dung STI khi nào ?
STI
sử dụng khi các model có các trường và các function giống nhau. Thay vì bạn viết một chức năng nhiều lần cho nhiều modle khác nhau hoặc linh hoạt trong việc thêm các chức năng riêng biết cho từng model khác nhau, STI
cho phép bạn lưu trữ dữ liệu của các model đó trong một bảng duy nhất trong khi vẫn có thể viết các chức năng riêng cho từng model. STI cung cấp đầy đủ các model method trong Rails như create, new, update_attributes … cho cả class cha và các class con được lưu trên một bảng duy nhất.
Chú ý rằng không sử dụng STI
chỉ vì các model có vẻ giống nhau. Hãy chắc chắn rằng có một mối quan hệ hướng đối tượng giữa chúng. Một ví dụ thực thế là chúng ta không thể sử dụng bảng Vehicles
để lưu data cho các model Car
, Bicycle
, Tank
. Trong trường hợp trên sử dụng STI
là không phù hợp vì một chiếc ô tô có đặc điểm và chức năng khác với một chiếc xe tăng và một chiếc xe đạp. Sẽ phù hợp hơn khi sử dụng STI
cho bảng Car
để lưu dữ liệu cho các phân loại xe như suv
, sedan
, hybrid
. Các class con này có chức năng và phong cách riêng nhưng chia sẻ những được giống nhau của một chiếc xe ô tô. Sử dụng STI
là một cách hiệu quả để làm tối giản và tránh bị lặp trong sơ đồ cơ sở dữ liệu
3. Cài đặt
Trước hết bạn tạo model Tribe
:
rails g model tribe name:string
Tạo model Animal
:
rails g model animal name:string age:integer race:string
Mặc định Active Record
sẽ lưu tên của class con trong trường type
nhưng bạn có thể đổi tên trường này thành bất cứ tên nào. Ở đây tôi sử dụng trường race
.
Sau đó bạn thêm trường tribe_id
vào Animal bằng cách thêm vào file migration hoặc tạo một migration mới như sau:
class AddTribeIdToAnimal < ActiveRecord::Migration
def change
add_column :animals, :tribe_id, :integer
end
end
chạy rake db:migrate
Như vậy tao có 2 model Tribe và Animal như bên dưới.
app/models/tribe.rb
class Tribe < ActiveRecord::Base
has_many :animals
end
app/models/animal.rb
class Animal < ActiveRecord::Base
belongs_to :tribe
self.inheritance_column = :race
# We will need a way to know which animals
# will subclass the Animal model
def self.races
%w(Lion WildBoar Meerkat)
end
end
Tạo thêm các class Lion
, Meerkat
và WildBoar
app/models/lion.rb
class Lion < Animal
end
app/models/meerkat.rb
class Meerkat < Animal
end
app/models/wild_boar.rb
class WildBoar < Animal
end
Ở trên chúng ta khai báo quan hệ has_many giữa Tribe
và Animal
đồng thời tạo ba model con của model Animal. self.inheritance_column = :race
được dùng để khai báo trường STI lưu tên của các model con trong cơ sở dữ liệu (nếu bạn sử dụng tên trường là type
thì không cần phải khai báo dòng này).
Chú ý: Nếu bạn muốn bỏ STI
cho model hoặc sử dụng trường type
với mục đích khác bạn có thể sử dụng self.inheritance_column = :fake_column
.
Như vậy coi như bạn đã khai báo thành công STI cho model Animal.
Thêm scope trong model cha cho mỗi model con và delegates
in Tribe
model giúp việc gọi object của các class con dễ dàng hơn
app/models/animals
scope :lions, -> {where(race: “Lion”)}
scope :meerkats, -> {where(race: “Meerkat”)}
scope :wild_boars, -> {where(race: “WildBoar”)}
app/models/tribes
delegate :lions, :meerkats, :wild_boars, to: :animals
Tạo dữ liệu cho SLI:
Nhờ STI
giờ chúng tao có thể sử dụng các model Lion
, WildBoar
, Meerkar
để tạo ra các object tương ứng. Trường race
sẽ tự động được lưu bởi Active Record
.
Tạo một Tribe
tribe = Tribe.create name: "LionTribe"
Tạo một vài object Animal
thêm vào Tribe
tribe.animals << Lion.new name: "Simba", age: 10
tribe.animals << WildBoar.new name: "Pumba", age: 30
tribe.animals << Meerkat.new name: "Timon", age: 30
Nếu bạn khai báo delegate
như phía trên bạn có thể sử dụng các method sau:
tribe.wild_boars, tribe.lions, tribe.meerkats, tribe.animals
Animal.lions, Animal.meerkats, Animal.wild_boars
Animal.all, Lion.all, Meerkat.all, WildBoar.all
4. Tổng kết
Qua bài viết trên tôi đã hướng dẫn cho các bạn những điều cơ bản nhất và những lợi ích nó mang lại. Chúc các bạn ứng dụng thành công trong dự án.
All Rights Reserved