Single Table Inheritance trong Rails 4 (Phần 1)

Hôm nay, chúng ta sẽ thực hiện một vài model kế thừa với Active Record. Mặc dù việc này không nên làm lạm dụng thường xuyên nhưng trong một số hoàn cảnh thích hợp thì nó rất hữu ích.

Bài viết này sẽ giới thiệu về cách tạo mô hình STI (Single Table Inheritance).

I. Điều kiện để có thể thực hiện

Để theo hướng dẫn này, bạn cần môi trường làm việc rails. Tôi sẽ sử dụng Ruby 2.0 với Rails 4 nhưng bạn có thể kiểm tra code sử dụng bất kỳ phiên bản Ruby của nhà cung cấp từ 1.9.2 và tối thiểu là Rails 3.

II. Single Table Inheritance là gì?

Singe Table Inheritance - giống như tên gọi của nó, là một cách thêm kế thừa cho model. STI cho bạn lưu các model kế thừa khác nhau từ một model cha trong cùng một bảng.

Ví dụ, bạn có một model là "employee". Các employees được chia làm 2 loại: "manager" và "developer". Chúng có các thuộc tính và các cột trùng nhau. Tuy nhiên , các phương thức có thể khác nhau. Sẽ thật không hay nếu tạo ra 2 bảng có cùng các trường giống nhau.

Trong trường hợp này bạn hãy sử dụng STI. Với STI, bạn chỉ giữ model "employee" và thêm 2 model kế thừa tương ứng với các loại của employee. Việc duy nhất để làm tại mức độ database là để thêm một cột type vào model "employees", ActiveRecord sẽ tự động sử dụng để phân loại các mô hình con. Nếu bạn muốn biết thêm về STI hãy tiếp tục!

III. Việc sử dụng STI

STI sẽ được sử dụng nếu các mô hình con chia sẻ các thuộc tính giống nhau nhưng các hành vi của chúng khác nhau. Nếu bạn cần thêm 10 cột chỉ để sử dụng cho một model con thì việc tạo thêm bảng mới sẽ là giải pháp tốt hơn.

1. Khởi tạo Rails app

Đầu tiên chúng ta sẽ khởi tạo một ứng dụng rails. Nếu bạn đã có một ứng dụng đang chạy, hãy bỏ qua phần này.

Trong commands chạy dòng lệnh dưới đây để tạo một ứng dụng rails mới không cần test và start server:


rails new sti --no-test-framework

2. Tạo các model và migration

Bây giờ, chúng ta sẽ khởi tạo các model và các migrations liên quan.

Tạo model Tribe


rails g model tribe name:string

Tạo model Animal


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

Cột race sẽ được dùng để Active Record được lưu tên các mô hình con. Nếu không tạo thì Active Record sẽ mặc định sử dụng cột được có tên là type.

Sau đó bạn có thể thêm cột tribe_id vào file Animal migration hoặc khởi tạo một file migration mới:


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

Chạy migrations:


rake db:migrate
  • Thêm một số quan hệ vào trong các model:

# 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 cả. Chúng ta thiết lập quan hệ giữa tribe với animals và khởi tạo 3 mô hình con rỗng. Chú ý rằng self.inheritance_column = :race dùng để thiết lập cột phân loại cho STI và không cần thiết nếu bạn đang dử dụng cột mặc định.

Nếu bạn muốn tắt STI hoặc sử dụng cột type cho việc khác, bạn có thể sử dụng self.inheritance_column = :fake_column.

3. Rails Auto-loading

Trên console và test có thể tạo các model con ở trên. Bạn sẽ gặp lỗi NameError: uninitialized constant nếu gọi một model con (ví dụ Lion) trước khi có gọi model cha (Animal). Điều này là do hệ thống Rails Auto-loading. Để tải một model, Rails tìm kiếm trong một file gọi là model_name.rb bên trong thư mục app/models. Có một vài phương pháp để sửa điều này nhưng chúng ta sẽ sử dụng cách đơn giản nhất là khai báo từng model con.


# 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

Bạn có thể kiểm tra lại và mọi thứ sẽ làm việc tốt! Nếu bạn muốn tìm hiểu thêm về các phương pháp khác hãy xem bài viết này: https://samurails.com/tips/sti-keeping-subclasses-file/

4. STI Tips

Các thiết lập cơ bản đã hoàn thành. Ở đây chúng ta có một vài tip hữu dụng với Single Table Inheritance.

  • Thêm scopes từ model cha cho mỗi model con:

scope :lions, -> { where(race: 'Lion') }
scope :meerkats, -> { where(race: 'Meerkat') }
scope :wild_boars, -> { where(race: 'WildBoar') }
  • Thêm delegates vào model Tribe:

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

Không bắt buộc nhưng việc thêm scopes và delegates giúp điều khiển các model dễ dàng hơn.

5. Các thao tác cơ bản

Trước khi tới phần tiếp theo, chúng ta sẽ tạo một vài object trong database bằng cách sử dụng môi trường console rails c, có thể sử dụng các model Lioon, WildBoarMeekar để tạo. Cột race sẽ tự động được điền bởi Active Record.

Tạo object tribe:


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

_ Tạo các object animal từ 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)

Chú ý rằng bạn có thể sử dụng bất kì các phương thức dưới đây nếu bạn đã thêm các scopes và delegates:


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

Các animals khác nhau dù dùng chung bảng animals nhưng chúng là các model độc lập, chúng ta có thể thêm một vài method cụ thể cho riêng từng model:


#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

Chúng ta vừa thực hiện qua các bước cơ bản với việc tạo và thao tác với các model STI. Như bạn thấy, nó thật hữu ích. Trong phần tiếp theo, chúng ra xem xét về việc làm thế nào để dùng các model kế thừa trong cùng một controller và thiết lập routers cho chúng.

Tài liệu dịch: https://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-1/