Module Concern
Bài đăng này đã không được cập nhật trong 8 năm
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
All rights reserved