Giới thiệu module Rails Concern
Kể từ bản Rails 4, một thư mục mặc định được tạo ra mỗi khi tạo project mới, đó là thư mục concerns. Ta sẽ tìm hiểu về module concern trong bài viết này. But first, let’s return to Ruby’s realm Module và included callback Ruby cung cấp một hàm callback có tên included cho ...
Kể từ bản Rails 4, một thư mục mặc định được tạo ra mỗi khi tạo project mới, đó là thư mục concerns. Ta sẽ tìm hiểu về module concern trong bài viết này.
But first, let’s return to Ruby’s realm
Module và included callback
Ruby cung cấp một hàm callback có tên included cho module. Hàm callback này sẽ được gọi mỗi khi module được included vào một module hoặc class khác. Cách dùng included rất đơn giản, ví dụ sau được trích từ document của Ruby
module A def A.included(mod) puts "#{self} included in #{mod}" end end module Enumerable include A end
Khi chạy file test.rb trên sẽ được kết quả:
$ ruby test.rb A included in Enumerable
included có thể được dùng để tách các phần logic giống nhau vào một module dùng chung. Chằng hạn trong ví dụ sau ta có 2 class Entry và Comment, cả 2 class đều định nghĩa các hàm như posted_at và cùng gọi các helper như validates_presence_of
class Entry validates_presence_of :user_id
def posted_at created_at.strftime("%Y/%m/%d") end end class Comment validates_presence_of :user_id
def posted_at created_at.strftime("%Y/%m/%d") end end
Dùng included ta dễ dàng move các logic này vào một module riêng, cụ thể như sau:
module Postable def self.included(base) base.class_eval do validates_presence_of :user_id end end def posted_at created_at.strftime("%Y/%m/%d") end end class Entry include Postable end class Comment include Postable end
Class methods
Khi một module được include vào một class, mặc định class đó sẽ access được các instance method được định nghĩa trong module đó nhưng lại không gọi được các class method. Chẳng hạn như hàm find_by_user_id trong ví dụ sau:
module Postable def posted_at created_at.strftime("%Y/%m/%d") end def self.find_by_user_id
end end class Entry include Postable end class Comment include Postable end
Một cách để workaround chính là sử dụng callback included
module Postable def self.included(base) base.extend(ClassMethods) end module ClassMethods def find_by_user_id
end end end
Trong ví dụ trên ta dùng function base của class/module để extend các class method. Sử dụng cách này, các class/module include Postable sẽ access được hàm find_by_user_id đã được định nghĩa.
Module Concern
Các ví dụ ở trên chính là cách hoạt động của module Concern. Với việc sử dụng Concern ta viết lại module Postable đơn giản như sau:
require 'active_support/concern' module Postable extend ActiveSupport::Concern included do validates_presence_of :user_id end module ClassMethods def find_by_user_id
end end end class Entry include Postable end class Comment include Postable end
Ngoài ra, module concern còn giúp giải quyết vấn đề dependency. Như trong ví dụ sau, module B phụ thuộc vào module A
module A def self.included(base) base.class_eval do def self.method_of_module_a
end end end end module B def self.included(base) base.method_of_module_a end end class C include A include B end
Ta thấy class C chỉ muốn sử dụng module B, nhưng lại phải include thêm module A(do B phụ thuộc vào hàm method_of_module_a của A). Nhưng với việc sử dụng Concern vấn đề dependency đã được giải quyết. Ta viết lại đoạn code trên như sau:
module A extend ActiveSupport::Concern included do def self.method_of_module_a
end end end module B extend ActiveSupport::Concern include A included do self.method_of_module_a end end module C include B end
Chi tiết về module Concern có thể tham khảo tại:
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb