12/08/2018, 13:03

Single Table Inheritance with Rails 4 (Part 1)

Ở bài viết này, chúng ta sẽ làm một mô hình thừa kế với Active Record. Phương pháp này được sử dụng cho một số trường hợp và nó đem lại hiệu qủa tuyệt vời. Trong bài viết này sẽ đi tới thiết lập một mô hình STI (Single Table Inheritance), ở những bài viết tiếp theo sẽ đi sâu hơn về cài đặt từng ...

Ở bài viết này, chúng ta sẽ làm một mô hình thừa kế với Active Record. Phương pháp này được sử dụng cho một số trường hợp và nó đem lại hiệu qủa tuyệt vời. Trong bài viết này sẽ đi tới thiết lập một mô hình STI (Single Table Inheritance), ở những bài viết tiếp theo sẽ đi sâu hơn về cài đặt từng phần cụ thể.

Yêu cầu môi trường

Chúng ta cần phải có một môi trường rails để thực hiện. Tôi sử dụng Ruby 2.0 và Rails 4. Tuy nhiên bạn có thể sử dụng Ruby từ phiên bản 1.9.2 và Rails từ phiên bản Rails 3.

Single Table Inheritance - (STI) là gì ?

Single Table Inheritance là gì?, hiểu một cách đơn giản đó là một cách để thêm thừa kế cho model của bạn. STI cho phép bạn lưu các model khác nhau kế thừa từ model cha bên trong một bảng duy nhất.

Ví dụ, giả sử bạn có một model employee. Các employee có thể được của hai loại: manage hoặc developer. Họ khá nhiều chia sẻ các thuộc tính và các cột tương tự. Tuy nhiên, hành vi của họ là khác nhau. Việc tạo ra hai bảng có các cột tương tự nhau là không tốt.

Nhưng ở đây có STI! Với STI, bạn có thể giữ model employee của bạn và chỉ đơn giản là phân lớp nó với hai dạng của nhân viên đó là manage và developer. Ở mức cơ sở dữ liệu là thêm một cột để phân loại bảng nhân viên và ActiveRecord sẽ tự động sử dụng để xác định các mô hình phụ. Cài đặt các mối quan hệ bảng dữ liệu ở phía dưới.

Vậy khi nào thì nên sử dụng mô hình STI và khi nào thì không?

STI nên được sử dụng nếu có các mô hình con (submodels) của bạn sẽ chia sẻ các thuộc tính tương tự nhưng cần có các hành vi(behaviors) của mỗi loại là khác nhau.

Nếu bạn có kế hoạch bổ sung thêm 10 cột chỉ được sử dụng bởi một mô hình phụ, thì sử dụng các bảng khác nhau có thể là một giải pháp tốt hơn.

Tạo mới ứng dụng rails

Chúng ta sẽ tạo mới một rails app. Nếu bạn đã có một ứng dụng đang chạy thì không cần tạo mới nữa mà hãy chuyển sang phần tiếp theo.

Tạo mới một rails app

rails new sti

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

Cài đặt các mối quan hệ cho mô hình của bạn:

# 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

class Lion < Animal; end
class Meerkat < Animal; end
class WildBoar < Animal; end

Không có gì phức tạp ở đây. Chúng ta thiết lập các mối quan hệ giữa các tribe và animals và tạo ra ba models con trống.

Lưu ý rằng: self.inheritance_column =:race được sử dụng để chỉ định các cột cho STI và không cần thiết nếu bạn đang sử dụng cột mặc định type.

Nếu bạn muốn tắt Single Table Inheritance hoặc sử dụng các loại cột cho cái gì khác, bạn có thể sử dụng self.inheritance_column =: fake_column.

Rails Auto-loading

Trong môi trường console và test bạn có thể tạo ra các models như ở trên. Bạn sẽ thấy lỗi như sau: NameError: uninitialized constant nếu bạn gọi một mô hình phụ (ví dụ như Lion) trước khi thực hiện gọi đến cha (Animal). Điều này là do hệ thống Rails Auto-load. Để auto-load các models đã được cài đặt, Rails tìm kiếm một tập tin gọi là model_name.rb trong app/models. Có một số giải pháp để khắc phục điều này, nhưng chúng ta sẽ sử dụng một cách đơn giản nhất là tạo ra các tập tin với tên là tên của models.

# 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

Một số truy vấn

Các thiết lập cơ bản bây giờ là hoàn tất. Dưới đây là một số cách dùng có thể hữu ích khi bạn cài đặt mô hình với Single Table Inheritance.

Viết thêm scope cho mỗi model bạn muốn

scope :lions, -> { where(race: 'Lion') }
scope :meerkats, -> { where(race: 'Meerkat') }
scope :wild_boars, -> { where(race: 'WildBoar') }

Thêm delegates trong Tribe model

delegate :lions, :meerkats, :wild_boars, to: :animals

Tạo cơ sở dữ liệu mẫu

Trước khi chuyển sang phần tiếp theo, chúng ta sẽ tạo ra một số đối tượng trong cơ sở dữ liệu.

Bạn có thể sử dụng gem 'faker' để tạo ngẫu nhiên các bản ghi cho cơ sở dữ liệu, tuy nhiên ở đây tôi sẽ tạo một vài bản ghi để test. Chúng ta sẽ vào môi trường console để tạo. Nhờ STI, bây giờ chúng ta có thể sử dụng các model Lion, Wild Boar và Meerkat để tạo ra các đối tượng tương ứng. Cột race sẽ được tự động điền bởi Active Record.

Vào môi trường console

rails c

Tạo đối tượng tribe

tribe = Tribe.create(name: 'LionTribe')

Tạo thêm một số đối tượng animals và đưa chúng vào trong 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)

Các truy vấn có thể sử dụng

tribe.wild_boars, tribe.lions, tribe.meerkats, tribe.animals
Animal.lions, Animal.meerkats, Animal.wild_boars
Animal.all, Lion.all, Meerkat.all, WildBoar.all

Chúng ta có thể tạo thêm các hành vi (behaviors) khác nhau cho các con vật (animals) khác nhau. Ví dụ như sau:

#app/models/animal.rb
def talk
    raise 'Abstract Method'
end

#app/models/meerkat.rb
def talk
    "Hakuna Matata, what a wonderful phrase !"
end

#app/models/wild_boar.rb
def talk
    "Hakuna Matata! Ain't no passing craze"
end

#app/models/lion.rb
def talk
    "It means no worries for the rest of your days"
end

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 giong nhau cần thừa kế từ một model khác, STI giups 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.

Trong các phần tiếp theo, chúng ta sẽ xem làm thế nào chúng ta có thể trình bày mô hình thừa kế của chúng ta với controller duy nhất và cấu hình routes cho chúng.

0