Polymorphic association

Polymorphic association Tuy không phải là một feauture mới của Rails, tuy nhiên vẫn có nhiều developer RoR hiểu nhầm, mập mờ về polymorphic association, vì polymorphic vẫn có vẻ hơi “lạ” , hoặc vẫn chưa hiểu polymorphic là gì nên hay bị nhầm lẫn, khó khăn khi sử dụng.

Bài chia sẻ lần này, tôi sẽ làm rõ một số vấn đề hay bị hiểu nhầm về polymorphic, mong muốn giúp các bạn không còn lúng túng khi “phải đụng đến” polymorphic association.

Hai câu hỏi thường gặp nhất khi đối mặt với vấn đề ( ở đây là polymorphic association) đó là: nó là gì? Nó hoạt động như thế nào?

Vậy polymorphic association trong Rails là gì? Nó được định nghĩa ở Polymorphic. Điều rút ra là, polymorphic là một phần xử lý có phần nâng cao hơn giữa các association models, một model có thể belongs_to với một hoặc nhiều hơn models khác, mà chỉ cần định nghĩa một association.

Sau đây, chúng ta sẽ định nghĩa polymorphic:

Tạo ra bảng email_questions với mục đích lưu email người hỏi, và nội dung hỏi.

Bảng lawyers, engineers là người có thể hỏi. Theo cách thông thường ta sẽ làm như sau:

Class CreateEmailQuestions < ActiveRecord::Migration[5.0]
  def change
	create_table :email_questions do |t|
  	  t.string : questioner _email
	  t.text :content
	  t.references :lawyer
	  t.references :engineer

	  t.timestamps
     end
  end
end

Tạo ra bảng lawyers

class CreateLawyer < ActiveRecord::Migration[5.0]
  def change
    create_table :lawyers do |t|
        t.string :name
        t.string :email
        t.integer :year_old
    end
  end
end

Tạo ra bảng engineers

class CreateEngineers  < ActiveRecord::Migration[5.0]
  def change
    create_table :engineers do |t|
        t.string :name
        t.string :email
        t.string :city
    end
  end
end

Xây dựng association giữa email_questions và hai bảng engineerslawyers:

class EmailQuestion < ApplicationRecord
  belongs_to :lawyers
  belongs_to :engineer
end
class Lawyer < ApplicationRecord
  has_many :email_questions
end
class Engineer < ApplicationRecord
  has_many :email_questions
end

Bây giờ, ta có một biến instance @email_question của class EmailQuestion, muốn tìm người đã được hỏi của @email_question này thì ta có thể thử

@email_question.lawyer || @email_question.engineer

Nhìn qua có vẻ khá rắc rối và dài dòng.

Đây là lúc, chúng ta nên áp dụng polymorphic để giảm thiểu khai báo thừa: Ví dụ: email_questions references đến lawyersengineers

Polymorphic được định nghĩa như sau

Với bảng email_questions:

Class CreateEmailQuestions < ActiveRecord::Migration[5.0]
  def change
	create_table :email_questions do |t|
  	  t.string : questioner _email
	  t.text :content
	  t.string :questionable_type
	  t.integer :questionable_id

	  t.timestamps
     end
  end
end

Ở đây, questionable_type là tên class questionable(“Lawyer” or “Engineer”) questionable_id là id của questionable (yaoming).

Từ questionable_type và questionable_id của object email_question thì ta có thể tìm được questionable(người được hỏi của email_question), đồng thời, hoàn thành các khai báo polymorphic trong model.

Với các model:

Xây dựng association giữa email_questions và hai bảng engineers và lawyers:

class EmailQuestion < ApplicationRecord
  belongs_to :questionable, polymorphic: true
end
class Lawyer < ApplicationRecord
  has_many :email_questions, as: questionable
end
class Engineer < ApplicationRecord
  has_many :email_questions, as: questionable
end

Thậm chí, ở hai model lawyer và engineer chúng ta còn có thể đưa vào module dùng chung (trong concern model)

module QuestionableRelation
  extend ActiveSupport::Concern

  included do
   has_many :email_questions, as: questionable
  end
end

Và ta chỉ cần khai báo

class Lawyer < ApplicationRecord
  include  QuestionableRelation
end
class Engineer < ApplicationRecord
  include  QuestionableRelation
end

=> DRY

Đừng quên rake db:migrate khi tạo/sửa table. Chúng ta có thể kiểm tra các association của các model bằng cách dùng reflect_on_all_associations (xem thêm)

Bây giờ, ta có thể dễ dàng hơn trong việc tìm questionable:

@ questionable = @email_question.questionable

Nếu muốn test với Rspec (ở đây sử dụng shoulda-matchers )

Rspec model/email_question_spec:
  it {should belong_to(:questionable)}
Rspec model/lawyer_spec:
  it {should have_many(:email_questions)}

Hoặc có thể dùng behaves_like để test cho module.

Trên đây là những ý kiến chia sẻ của tôi về cách dùng và hoạt động của polymorphic association trong Rails

tài liệu tham khảo:

Railscast

Rspec

guides RoR

All Rights Reserved