Single Table Inheritance trong Rails 4 (Phần 1)
Bài đăng này đã không được cập nhật trong 8 năm
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
, WildBoar
và Meekar
để 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/
All rights reserved