12/08/2018, 13:59

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

0