Module Concern
Giới thiệu Trong models, bạn thấy 1 thư mục là concerns mà có thể chưa từng sử dụng đến nó. Concerns là nơi đưa vào các method được gộp lại vào trong các module và có thể sử dụng cho nhiều module/class thông qua include module chứa chúng Ví dụ 1: # ../model/concerns/study.rb module ...
Giới thiệu
Trong models, bạn thấy 1 thư mục là concerns mà có thể chưa từng sử dụng đến nó.
Concerns là nơi đưa vào các method được gộp lại vào trong các module và có thể sử dụng cho nhiều module/class thông qua include module chứa chúng
Ví dụ 1:
# ../model/concerns/study.rb module Study def push_ups "I have many Answer!" end end # ../model/question.rb class Question < ActiveRecord::Base include Study belongs_to :subject has_many :answers validates :content, presence: true end # ../model/suggest_question.rb class Suggest_Question < ActiveRecord::Base include Study belongs_to :subject has_many :answers validates :content, presence: true end
INCLUDED CALLBACK
Khi một module được include vào một class, class đó sẽ được sử dụng các instance method được khai báo trong module đó:
Ví dụ 2:
# ../model/concerns/study.rb module Study def push_ups "I have many Answer!" end class << self def run_3_times #.. end end end # ../model/question.rb class Question < ActiveRecord::Base include Study end
$ rails c $ puts Question.new.push_ups #I have many Answer!
Tuy nhiên class này lại không truy cập được đến class method
$ puts Question.run_3_times #NoMethodError
Để giải quyết vấn đề này, trước hết ta hãy tìm hiểu về Included Callback:
Một hàm callback là 'included' được Ruby cung cấp cho module. Hàm này được sử dụng mỗi khi module được included vào một module/class khác.
Ví dụ 3:
#includedcallback.rb module A def A.incuded(mod) puts "#{self} included in #{mod}" end end module foo include A end
$ ruby includedcallback.rb A included in foo
Included hữu ích trong trường hợp có các method giống nhau.
Chẳng hạn trong ví dụ dưới đây ta có 2 class subject và question đều được định nghĩa hàm created_at và cùng gọi các helper như validates :content, presence: true
Ví dụ 4
class Subject validates :content, presence: true def created_at created_at.strftime("%Y/%m/%d") end end class Question validates :content, presence: true def created_at created_at.strftime("%Y/%m/%d") end end
Dùng included để đưa các logic này vào 1 module, sẽ giúp cho code sạch sẽ, việc sử dụng dễ dàng hơn:
Ví dụ 5
module Postable def self.included(base) base.class_eval do validates :content, presence: true end end def created_at created_at.strftime("%Y/%m/%d") end end class Subject include Postable end class Question include Postable end
*Quay trở lại vấn đề ban đầu:
Hạn chế của việc một class include một module đó là class đó chỉ có thể truy cập các instance methods của module mà không thể truy cập tới các class methods.
Một cách giải quyết của vấn đề này là nhóm các class method vào 1 module, và extend nó trong included callback:
Ví dụ 6
# ../model/concerns/study.rb module Study def self.included klass klass.extend ModuleMethods klass.class_eval do belongs_to :subject has_many :answers validates :content, presence: true end end module ModuleMethods def run_3_times #.. end end def push_ups #.. end end # ../model/question.rb class Question < ActiveRecord::Base include Study end
$ rails c $ puts Question.new.push_ups #OK $ puts Question.run_3_times #OK
MODULE CONCERN
Phần trình bày ở trên là cơ chế hoạt động của module Concern. Tuy nhiên ta có thể cấu trúc lại phần code trên trở nên đơn giản, dễ nhìn hơn:
Ví dụ 7
# ../model/concerns/study.rb module Study extend ActiveSupport::Concern included do belongs_to :subject has_many :answers validates :content, presence: true def push_ups "I have many Answer!" end class << self def run_3_times "Running 3 times" end end end end # ../model/question.rb class Question < ActiveRecord::Base include Study end # ../model/suggest_question.rb class Suggest_Question < ActiveRecord::Base include Study end
Ngoài ra, module Concern còn giải quyết vấn đề dependency. Ví dụ sau đây, module Answer phụ thuộc vào Question (Answer sử dụng hàm mothod_of_Question). Nhưng class foobar chỉ cần sử dụng Answer, trong khi lại phải include thêm Question. (Ruby không cho phép một class đa kế thừa từ nhiều module/class khác, tuy nhiên Ruby dùng cơ chế Mix-in để mix-in các module lại, điều này cho phép multi- inheritance).
Điều này thực sự không hay chút nào, khi mà cần sử dụng module này lại phải quan tâm đến cả sự phụ thuộc của nó đến module khác
Ví dụ 8
# test_dependency.rb module Question def self.included base base.class_eval do def self.method_of_question puts "inside method of Question" end end end end module Answer def self.included base base.method_of_question end end class Test include Question # Cần include module này do module Answer phụ thuộc vào module Question include Answer # Answer mới là module thực sự cần dùng end
Thế thì thử include Question trong module Answer luôn, kết quả sẽ lỗi ngay lập tức, vì khi này, "base" không phải là Test nữa mà lại là "Bar", nên chương trình sẽ không thực hiện ngay từ module Foo:
Ví dụ 9
# test_dependency.rb module Question def self.included base base.class_eval do def self.method_of_question puts "inside method of Question" end end end end module Answer include Question def self.included base base.method_of_question end end class Test include Answer end
=> Với việc dùng Concern, vấn đề dependency này được giải quyết
Ví dụ 10
# test_dependency_ok.rb require "active_support/concern" module Question extend ActiveSupport::Concern included do class << self def method_of_question puts "inside method of Question" end end end end module Answer extend ActiveSupport::Concern include Question included do self.method_of_question end end class Test include Answer # Class Test không quan tâm đến các module mà Answer phụ thuộc nữa end
TỔNG KẾT:
Bài viết của mình đã trình bày về Module Concern, với những kiến thức mình đọc và tìm hiểu được, mong sẽ giúp ích phần nào cho các bạn, đặc biệt với những bạn mới làm quen với Ruby on Rails.
Tham khảo:
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb