Callback order in controller of Rails

2017/06/17 Rails

在Rails controller中可以定义回调方法。回调方法是一个队列,满足先进先出的执行顺序。但我们知道,回调有before_action, after_action, around_action 这三种简单的类型。并且还有append_xxx_action、 prepend_xxx_action、和skip_xxx_action。 在RubyChina上有一篇精华帖讲了controller callback 源码的内容 详解 Rails Controller 中的 Callback 这里我们研究除了skip_xxx_action的其他类型的执行顺序。

对于回调的执行顺序,个人觉得在一定场景下,是很有意义的。然而,往往我们写Ruby写的爽了,就忽略了。

举个简单的栗子(不一定符合最佳实践):

class User < ApplicationController
  before_action :check_name
  before_action :check_require_params

  def create
  end

  private
  def check_name
    user = User.find_by(name: params[:name])
    if user.blank?
      render json: {code: 1404, message: "User is not found"} and return
    end
  end

  def check_require_params
    if params[:name].blank?
      render json: {code: 1500, message: "params name is blank"} and return
    end
  end
end

上面的栗子,如果我们传的params[:name] 为nil。根据队列顺序,从上往下执行回调,通过check_name 后返回 “User is not found”。但是,如果我们将顺序更改为:

before_action :check_require_params
before_action :check_name

我们则可以通过check_require_params 回调检查出问题,而不需要进行一次数据库的查询。哪种顺序更佳,我想你已经知道了。

如果你遇到了在项目代码中controller有一些回调,调整这些回调,找出合适的执行顺序,我觉得是有意义的。

上面的栗子讲解的是相同回调类型下的执行顺序,不同回调类型的执行顺序会是怎样的呢?

class User < ApplicationController
  before_action :log_before_action
  around_action :log_around_action
  after_action  :log_after_action

  def index
    return plain: 'Hello World'
  end

  private
  def log_before_action
    puts "="*10 + "before_action"
  end

  def log_around_action
    puts "-"*10 + "before around_action"
    yield
    puts "-"*10 + "after around_action"
  end

  def log_after_action
    puts "+"*10 + "after_action"
  end
end

访问<index action> 打印结果:

==========before_action
----------before around_action
  Rendering text template
  Rendered text template (0.0ms)
++++++++++after_action
----------after around_action

显然,before_action 是最先执行的,之后是around_action中的 进入<yield>之前的逻辑,然后是after_action, 最后才是around_action中的 执行完<yield>后的逻辑。可以看出,这里比较特别的是around_action。

我们调整回调的顺序为:

around_action :log_around_action
before_action :log_before_action
after_action  :log_after_action

打印结果:

----------before around_action
==========before_action
  Rendering text template
  Rendered text template (0.0ms)
++++++++++after_action
----------after around_action

最先执行的是 around_action中的 进入<yield>之前的逻辑,然后before_action, after_action, 最后是around_action中的 执行完<yield>后的逻辑。也就是说,最先触发的回调是around_action,因为这时候, around_action 在before_action的上面了。

另一种顺序:

after_action  :log_after_action
around_action :log_around_action
before_action :log_before_action

打印结果:

----------before around_action
==========before_action
  Rendering text template
  Rendered text template (0.0ms)
----------after around_action
++++++++++after_action

最先执行的是 around_action中的 进入<yield>之前的逻辑,然后before_action, around_action中的 执行完<yield>后的逻辑,最后是after_action。

把after_action 放到最上面,和around_action 执行完<yield>后的逻辑相比较反而最后执行了。

简单的总结: before_action 和 around_action中的 进入<yield>之前的逻辑部分是同一类别,并且满足队列的执行顺序。 after_action 和 around_action中的 执行完<yield>后的逻辑部分是统一类别,并且满足栈的执行顺序。

Search

    Table of Contents