Tìm hiểu Single Table Inheritance (STI) trong Rails

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 ManagerDeveloper 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ả ManagerDeveloper 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, MeerkatWildBoar

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 TribeAnimal đồ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