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.