Tìm hiểu về ActiveSupport::Concerns
Mở đầu Trong quá trình build 1 Rails app, nếu để ý chúng ta sẽ thấy xuất hiện 1 folder có tên là concerns, nằm trong đường dẫn app/controllers và app/models. Nếu trước giờ chưa dùng đến nó thì chúng ta thường không quan tâm đến, cũng không rõ nó có tác dụng gì, hoặc là có thể nó để làm 1 thứ gì ...
Mở đầu
Trong quá trình build 1 Rails app, nếu để ý chúng ta sẽ thấy xuất hiện 1 folder có tên là concerns, nằm trong đường dẫn app/controllers và app/models. Nếu trước giờ chưa dùng đến nó thì chúng ta thường không quan tâm đến, cũng không rõ nó có tác dụng gì, hoặc là có thể nó để làm 1 thứ gì đó phức tạp mà mình sẽ không cần đến,.. cho đến khi bước vào làm dự án thực tế lớn, đụng chạm đến nhiều method phức tạp thì concerns đúng là vô cùng hữu dụng. Vậy concerns ở đây là gì? Nói đơn giản thì concerns là 1 thư mục chứa các module bao gồm các method được sử dụng cho nhiều class hay các module include chúng.
Concerns in Controllers
Chúng ta muốn code được gọn gàng, đồng nghĩa với việc phải hạn chế trùng lặp code càng nhiều càng tốt. Giả sử như trong app của chúng ta phân quyền cho cả admin và user đều có thể tạo post với 7 action cơ bản (show, new, index, create, edit, update, destroy) thì để đạt được mong muốn tối ưu số dòng code, tránh trùng lặp ở controller thì đây là lúc concerns phát huy tác dụng của mình. Tạo file post_action.rb trong đường dẫn app/controllers/concerns :
module PostAction included do before_action :load_post, only: [:show, :edit, :destroy, :update] end def index @posts = Post.select(:id, :content) end def new @post = Post.new end def show end def create @post = Post.new(post_params) if @post.save flash[:notice] = "Successfully created post." redirect_to @post else flash[:alert] = "Error creating post." render :new end end def edit end def update if @post.update_attributes(post_params) flash[:notice] = "Successfully updated post." redirect_to posts_path else flash[:alert] = "Error creating post." render :edit end end def destroy if @post.destroy flash[:notice] = "Successfully deleted post." redirect_to posts_path else flash[:alert] = "Error deleting post." end end private def post_params params.require(:post).permit(:content) end def load_post @post = Post.find_by id: params[:id] end end
Ở đây before_action được viết bên trong block included để bảo đảm cho việc before_action sẽ luôn luôn được thực thi ở bất cứ đâu mà module PostAction được include. Và chúng ta chỉ việc include module này vào controller nào cần dùng:
class PostsController < ApplicationController include PostAction end class Admin::PostsController < ApplicationController include PostAction end
Concerns in Models
ActiveSupport::Concern cũng hoạt động ở phía model, tương tự như những gì ta thấy ở bên controller. Giả sử như chúng ta có 3 model Post, Comment, Like có ràng buộc quan hệ như sau:
class Post < ActiveRecord::Base has_many :likes, as: likable has_many :comments def like likes.create end end class Comment < ActiveRecord::Base has_many :likes, as: likable belongs_to :post def like likes.create end end class Like < ActiveRecord::Base belongs_to :likable, polymorphic: true end
Với sự hỗ trợ của concerns chúng ta có thể rút gọn lại như sau: Tạo module Likable trong đường dẫn app/models/concerns/likable.rb:
module Likable extend ActiveSupport::Concern included do has_many :likes, as: :likable end def like likes.create end end
và include vào model:
class Post < ActiveRecord::Base include Likable has_many :comments end class Comment < ActiveRecord::Base include Likable belongs_to :post end
1 số lưu ý
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 đó, tuy nhiên, class này lại không truy cập được đến class method:
module PostAction def put_a puts "A" end class << self def class_method puts "Class Method" end end end class Post < ActiveRecord::Base include PostAction end Post.new.put_a #=> "A" Post.new.class_method #=> NoMethodError
Để khắc phục thì chúng ta có thể sử dụng included:
module A def A.included(mod) puts "#{self} is included in #{mod}" end end module B include A end #=> "A is included in B"
Tổng kết
Trên đây là 1 chút chia sẻ của mình về ActiveSupport::Concerns, bài viết còn nhiều thiếu sót, mong được góp ý, cảm ơn bạn đã dành thời gian đọc bài viết. Nguồn tham khảo: http://api.rubyonrails.org/classes/ActiveSupport/Concern.html https://www.sitepoint.com/dry-off-your-rails-code-with-activesupportconcerns/