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ử dụng 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. Khởi tạo các models và các relations

Bây giờ, chúng ta sẽ tạo ra các models và các mối quan hệ (relations).

Tạo Tribe model

rails g model tribe name:string

Tạo Animal model

rails g model animal name:string age:integer race:string

Như phần ở trên đã đề cập, chúng ta chỉ cần thêm cột race sẽ được sử dụng bởi Active Record để lưu lại tên mô hình phụ (submodels). Theo mặc định, nếu không tạo cột mới thì Active Record sẽ sử dụng cột mặc định là type.

Sau đó, bạn có thể thêm cột tribe_id vào bảng animals

rails g migration AddTribeIdToAnimals tribe_id:integer

Chạy lệnh trên ta có file migrate như sau:

class AddTribeIdToAnimal < ActiveRecord::Migration
  def change
    add_column :animals, :tribe_id, :integer
  end
end

Run the migrations: rake db:migrate

Như vậy ta 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ệ hasmany giữa TribeAnimal đồng thời tạo 3 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 trong 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. Kết luận

Bên trên chúng ta đã cài đặt mô hình STI và một số chức năng cơ bản bạn có thể làm. Như bạn có thể thấy, nó là hữu ích khi nếu mô hình cơ sở dữ liệu của bạn có nhiều models có nhiều thuộc tính giống nhau cần thừa kế từ một model khác, STI giúp bạn không cần tạo ra nhiều models khác nhau có các thuộc tính tương tự nhau mà chỉ cần có thêm 1 trường để phân chia. Nguồn: https://devblast.com/b/single-table-inheritance-with-rails-4-part-1