12/08/2018, 16:16

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/

0