Quan hệ Đa hình trong Ruby on Rails: Part 2 - Đa hình ngược - Reverse Polymorphic Associations

Đây là phần thứ hai trong loạt bài gồm ba phần về các quan hệ đa hình trong Ruby on Rails, sử dụng Active Model. Phần này sẽ giải thích các quan hệ đa hình ngược - chúng là gì và làm thế nào để tạo ra chúng. Ba phần là:

  1. Giới thiệu và cách tạo ra một Polymorphic Associations cơ bản

  2. Quan hệ đa hình nghịch đảo(Reverse Polymorphic Associations)

  3. Quan hệ đa hình nhiều-nhiều (Many-to-Many Polymorphic Associations)

Trong bài viết trước, mình đã trình bày loại quan hệ đa hình "tiêu chuẩn" trong Rails, trong đó các model con (comments) trong association có thể thuộc về các model cha khác nhau (post, imagepage). Polymorphic đã giải quyết việc sinh ra nhiều bảng trung gian tuơng đồng và rút ngắn chỉ trong 1 bảng trung gian ảo (trong ví dụ của bài viết trước, là commentable_idcommentable_type trong bảng comment).

I. Quan hệ đa hình đảo ngược

Trong một quan hệ đa hình đảo ngược, đó là các model cha có thể có nhiều model con thuộc nhiều loại. Điều này phức tạp hơn một chút vì không có nơi rõ ràng cho trường "xxxx_type" - ứng với trường "xxxx_id", nhưng các model con vẫn cần cặp "xxxx_type" - "xxxx_id" này để liên kết . Vì vậy, những gì chúng ta thực sự cần là một bảng khác, một bảng đại diện cho mối quan hệ này. Một điều khác cần lưu ý, "association table" sẽ sử dụng một liên kết đa hình thông thường. Nói sơ qua như trên sẽ rất khó hiểu, vậy ta đi vào ví dụ để có cái hiểu đơn giản nhất nhé ...

II. Ví dụ demo

Trong Rails, thuật ngữ "reverse polymorphic association" dùng để chỉ phương pháp thiết lập quan hệ đa hình qua nhiều model, trên thực tế không có từ khóa "Reverse_polymorphic".

1. Vấn đề

Giả sử các bài viết trong blog tin tức được tạo thành từ nhiều loại phương tiện - khối văn bản, ảnh và video(text blocks, photos and videos). Mỗi bài viết sẽ là một tập hợp các loại phương tiện được tổ chức theo một trình tự cụ thể. Gọi tắt là "article elements" - các yếu tố trong bài viết. Sơ đồ mô tả các elements của 1 or nhiều article cần có:

2. Quan hệ đa hình đảo ngược

Như trong model trên, 1 article đều có ảnh, video, text blocks; Ở đây rõ ràng không sử dụng polymorphic thông thường được, vì article và các video, photos, text blocks đều có thể thuộc nhiều article khác; lúc này thay vì cơ chế tiêu chuẩn; ta xây dựng 1 bảng trung gian giữa chúng và đặt polymorphic trong bảng này; đại diện các types của article. Tạm gọi model này là ArticleElement

Dưới đây là sơ đồ mô hình (và trường cơ sở dữ liệu) mà chúng ta phải thực hiện: Generate và migration các models:

rails g model Article title:string published_on:date
rails g model TextBlock body:string
rails g model Photo caption:string filename:string
rails g model Video title:string filename:string

Và model trung gian:

rails g model ArticleElement sequence:int references:article references:element{polymorphic}

Hãy nhìn vào các model. Đầu tiên, mô hình Article :

class Article < ApplicationRecord
  has_many :article_elements, dependent: :destroy
end

Sau đó, các loại phương tiện mà một article được tạo thành:

class TextBlock < ApplicationRecord
  has_one :article_element, as: :element, dependent: :destroy
  has_one :article, through: :article_element
end

class Photo < ApplicationRecord
  has_one :article_element, as: :element, dependent: :destroy
  has_one :article, through: :article_element
end

class Video < ApplicationRecord
  has_one :article_element, as: :element, dependent: :destroy
  has_one :article, through: :article_element
end

Và cuối cùng, model trung gian:

class ArticleElement
  belongs_to :article
  belongs_to :element, polymorphic: true
  default_scope { order(:sequence).includes(:element) }
end

Mô hình ArticleElement là model đại diện cho quan hệ giữa các Article và các Element(ba loại phương tiện truyền thông). Các bản ghi thuộc loại này đều thuộc về n Article và thuộc về một Element- và đó là nơi polymorphic: true xuất hiện - các bản ghi ArticleElement sẽ có cả trường element_idelement_type.

Nếu bạn nhớ từ bài viết trước, các association polymorphic có tên "commentable". Trong trường hợp này, tôi vừa đặt tên cho model thể hiện asociation ở đây là "Element", vì đó là bản ghi của mỗi loại phương tiện, là một element của một article.

Ba model Video, Photo, TextBlock đều khai báo quan hệ: has_one :article, through: :article_element

Tức chúng đều thuộc 1 article thông qua article_element tuơng ứng. Điều này cho phép nhanh chóng tìm thấy bài viết có một yếu tố nào đó như:

photo = Photo.find(:photo_id)
puts photo.article

Cũng lưu ý các tuyên bố dependent: :destroy trên các quan hệ. Vì đang sử dụng bảng trung gian, nên cần đảm bảo rằng các bản ghi trong bảng đó sẽ bị hủy khi một article hoặc một element bị xóa. Ngoài ra, nếu chúng ta xóa một article , nó cũng sẽ xóa các element của article đó. Nếu chúng ta không làm điều này, chúng ta sẽ có những bản record mồ côi làm xáo trộn cơ sở dữ liệu.

Cuối cùng, lưu ý rằng có một default scope trên Model ArticleElement. Điều này đảm bảo rằng các elementcủa article được truy xuất theo đúng trình tự và cũng có thể, nếu đang truy xuất bản ghi của loại model này, chúng ta thực sự đang tìm bản ghi element được liên kết với (text blocks, photos hoặc videos ).

Truy xuất dữ liệu

Chắc hẳn các bạn đang thắc mắc sử dụng loại Quan hệ đa hình đảo ngược như này thì việc truy xuất dữ liệu hay tạo 1 record mới có gì khác biệt với polymorphic thông thường không thì đây là 1 vaì ví dụ cho thấy việc truy xuất hay tạo mới record không có nhiều sự khác biệt:

article = Article.find(:article_id)
elements = article.article_elements

# First, check the element_type
if elements[0].element_type == 'Photo'
  # To get the photo's caption, we have to do this:
  elements[0].element.caption

  # This won't work
  elements[0].caption
end

# To add a new element, we have to create the ArticleElement
# record that links a article to the element:
article = Article.find(:article_id)
photo = Photo.create({ caption:"Alien Abduction Infographic", filename:"aa_info.jpg" })
Article.article_elements.create({ element: photo })

III. Lời kết

Trên đây mình đã giới thiệu 1 loại polymorphic khá đặc biệt mà hữu dụng cho các mối liên kết phức tạp. Rất mong chúng hữu ích và giúp được các bạn. Thank you! 😃

Tài liệu: https://guides.rubyonrails.org/association_basics.html#polymorphic-associations


All Rights Reserved