Rails Polymorphic Associations
Chắc chắn đầu tiên chúng ta muốn biết liên kết polymorphic là gì? Nó là nơi mà một mô hình ActiveRecord có thể belong to nhiều hơn một model khác. Như trường hợp chiếc oto của bạn phụ thuộc bạn hay một người nào đó, trong khi các xe khác có thể phụ thuộc nhiều xe hay doanh nghiệp. 1. Tại sao ...
Chắc chắn đầu tiên chúng ta muốn biết liên kết polymorphic là gì? Nó là nơi mà một mô hình ActiveRecord có thể belong to nhiều hơn một model khác. Như trường hợp chiếc oto của bạn phụ thuộc bạn hay một người nào đó, trong khi các xe khác có thể phụ thuộc nhiều xe hay doanh nghiệp.
1. Tại sao chúng ta cần nó?
Trong rails, ActiveRecord mặc định cách quan hệ giữa các bản ghi trong DB của bạn. Một bản ghi đơn giản truy cập đến một cái khác sẽ đơn giản là chèn tên trước _id (vd: model_name_id). Một User có thể sở hữu một Profile. Nên chúng ta sẽ có cả model User và Profile. Profile sẽ belongs_to phụ thuộc vào User và User chỉ có một Profile.
# /app/models/user.rb class User < ActiveRecord::Base has_one :profile, dependent: :destroy end # /app/models/profile.rb class Profile < ActiveRecord::Base belongs_to :user end
Model profile trong database cần có cột user_id, từ đó ta sẽ biết được profile đó của user nào. Sử dụng lệnh sau để tạo model Profile một cách tự động:
rails generate model Profile user_id:integer:index name:string:index rake db:migrate
Bây giờ bạn có thể tạo một profile. Nếu ta đã có User với id là 1 ta có thể tạo profile cho user đó.
Profile.new( user_id: 1, name: "Master of Lorem Ipsum" ).save
chúng ta đã định nghĩa quan hệ has_one và belongs_to trong models, ta có thể truy cập thông tin profile thông qua biến user.
user = User.first user.profile.name # => "Master of Lorem Ipsum"
Và nếu bạn muốn tìm user từ profile bạn có thể dùng lệnh đơn giản như sau:
profile = Profile.where(name: "Master of Lorem Ipsum").first profile.user.id # => 1
Bây giờ nếu bạn muốn có các profile phụ thuộc nhiều hơn một loại model thì phải làm sao? Chúng ta sẽ muốn business có profile của riêng họ. Bây giờ chúng ta đang có cột user_id để biết profile đó của user nào, vậy bây giờ chúng ta cũng cần có cột business_id trong Profileđể biết đó là của business nào.
rails generate migration AddBusinessIdToProfile business_id:integer:index rake db:migrate
Bây giờ, khi chúng ta cập nhật các model, chúng ta có một vấn đề. Bây giờ, nếu ta muốn xem những người sở hữu những profile, ta không còn có thể tra cứu một trường để chắc chắn về nó. Điều gì nếu cả user_id và profile_id có một số trong đó? Điều gì nếu chúng đều rỗng?
Một giải pháp đơn giản là di chuyển tham chiếu từ Profile vào User và Business. Vì thế thay vì profile cho biết nó thuộc về ai thì user và business nói về hồ sơ mà họ sở hữu.
Một giải pháp tốt nhất để giải quyết việc này là sử dụng polymorphic.
2. How does it work?
Polymophic thực hiện thay vì sử dụng user_id ta sẽ tách ra name vào một trường và một trường khác cho id. Nên thay vì một phương thức với từ khoá user trong nó. Chúng ta lấy "User" như một giá trị cuả loại sở hữu và chúng ta vẫn giữ tham chiếu ID của "User" đó. Nếu chúng ta muốn thay đổi người sở hữu chúng ta sẽ thay đổi giá trị từ "User" thành "Business" và sau đó cập nhật lại id phù hợp.
Chúng ta sẽ tạo một quan hệ polymophic:
rails g model Profile name:string:index profileable:references{polymorphic} rake db:migrate
khi chọn một tên tham chiếu nó là thực tế để sử dụng tên đối tượng và thêm vào từ "able" vào cuối. Nên Profile sẽ trở thành profileable. Tham chiếu truy cập đến nguời sở hữu trong model Profile bây giờ sẽ là profileable_id và profileable_type.
Các model của bạn sẽ giống như sau:
# /app/models/user.rb class User < ActiveRecord::Base has_one :profile, as: :profileable, dependent: :destroy end # /app/models/business.rb class Business < ActiveRecord::Base has_one :profile, as: :profileable, dependent: :destroy end # /app/models/profile.rb class Profile < ActiveRecord::Base belongs_to :profileable, polymorphic: true end
Bây giờ chúng ta có thể tạo ra các profile như sau:
user = User.first Profile.new( profileable_id: user.id, profileable_type: user.class.name, name: "Master of Lorem Ipsum" ).save business = Business.first Profile.new( profileable_id: business.id, profileable_type: business.class.name, name: "Taco Shell" ).save
Sẽ đơn giản hơn nếu bạn sử dụng liên kết polymorphic. Chúng ta thêm vào một số phương thức để tạo các record. Từ khi chúng ta sử dụng quan hệ has_one, phương thức có thể sử dụng là build_ với class polymorphic của chúng ta. Nên nó là build_profile.
user = User.first user.build_profile(name: "Master of Lorem Ipsum").save user.profile.name # => "Master of Lorem Ipsum" business = Business.first business.build_profile(name: "Taco Shell").save business.profile.name # => "Taco Shell"
Bây giờ nếu bạn muốn permit nhiều hơn một Profile cho 1 người chúng ta phải làm thế nào? Đó là những người sống hai nơi và những người thuộc các nhóm hoặc tổ chức khác ... Do óó chúng ta cần đến quan hệ has_many.
3. has_many
Các model được sinh ra bằng cách dùng câu lệnh, chúng ta chỉ cần cập nhật quan hệ giữa các model trong thư mục /app/models.
# /app/models/user.rb class User < ActiveRecord::Base has_many :profiles, as: :profileable, dependent: :destroy end
Bây giờ mọi thứ đã thay đổi. Chúng ta không thể truy cập một profile (user.profile) bởi vì user bây giờ có nhiều profile nên phải sử dụng user.profiles.
user = User.first user.profiles # => #<ActiveRecord::Associations::CollectionProxy []>
Ở trên tương ứng với một mảng rỗng. Bây giờ chúng ta sẽ tạo thêm các profile mới. Thay vì lúc trước sử dụng build_profile thì giờ ta chỉ cần gọi build.
user = User.first user.profiles.build(name: "Master of Lorem Ipsum").save user.profiles.first.name # => "Master of Lorem Ipsum"
Bây giờ chúng ta có thể tạo nhiều profile cho mỗi user.
4. Form Nested Attributes
Form để update cho user có thể giống như sau:
<%= form_for(@user) do |f| %> email: <%= f.text_field :email %> <%= f.submit "Save changes" %> <% end %>
Biến @user được thiết lập trong controller app/controllers/users_controller.rb
Thông thường, chúng ta muốn có các bản ghi polymorphic mà phụ thuộc User được đưa vào form. Chúng ta có thể tạo một phần profile của user trong form với fields_for. Cho quan hệ has_one với profile, nó sẽ có dạng như sau. Nhưng trước đó ta cần thêm một trường vào model User vì vậy chúng ta có thể chấp nhận các thuộc tính lồng nhau.
# /app/models/user.rb class User < ActiveRecord::Base has_one :profile, as: :profileable, dependent: :destroy accepts_nested_attributes_for :profile end
Form của bạn sẽ có dạng:
<%= form_for(@user) do |f| %> email: <%= f.text_field :email %> <% f.fields_for :profile do |prf| %> <%= prf.text_field :name %> <% end %> <%= f.submit "Save changes" %> <% end %>
Bạn có thể tìm hiểu thêm về nested attributes tại đây
Khi tạo hồ sơ mới, bạn sẽ cần phải sử dụng một trong các điều sau đây trong controller của bạn:
# For a single profile @user = User.new # New User @user.build_profile # For the first of multiple profiles @user = User.new # New User @user.profiles.build
Có cái này trong controller của bạn sẽ cho phép bạn sử dụng fields_for cho việc tạo các record mới trong form của bạn.
Kết Luận:
Trên đây là một số kiến thức về polimorphic. Cảm ơn mọi người đã theo dõi bài viết. Kiến thức còn hạn hẹp mong mọi người góp ý để bài viết hoàn thiện hơn.
Tham khảo:
http://6ftdan.com/allyourdev/2015/02/10/rails-polymorphic-models/