Rails控制器过滤器 #

一、过滤器概述 #

1.1 什么是过滤器 #

过滤器是控制器中在动作执行前后自动运行的方法,用于处理跨切面关注点。

1.2 过滤器类型 #

类型 执行时机 用途
before_action 动作执行前 认证、授权、数据准备
after_action 动作执行后 日志记录、响应处理
around_action 动作执行前后 性能监控、事务管理

二、before_action #

2.1 基本用法 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :require_login
  
  private
  
  def require_login
    redirect_to login_path unless current_user
  end
end

2.2 限制动作 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  # 只对指定动作执行
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  
  # 排除指定动作
  before_action :require_admin, except: [:index, :show]
  
  def show
    # @article已由过滤器设置
  end
  
  private
  
  def set_article
    @article = Article.find(params[:id])
  end
  
  def require_admin
    redirect_to root_path unless current_user&.admin?
  end
end

2.3 过滤器顺序 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  # 按定义顺序执行
  before_action :require_login
  before_action :set_article
  before_action :check_permission
  
  private
  
  def require_login
    # 第一个执行
  end
  
  def set_article
    # 第二个执行
  end
  
  def check_permission
    # 第三个执行
  end
end

2.4 条件执行 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, if: :article_id_present?
  before_action :check_permission, unless: :public_action?
  
  private
  
  def article_id_present?
    params[:id].present?
  end
  
  def public_action?
    action_name.in?(%w[index show])
  end
end

三、after_action #

3.1 基本用法 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  after_action :log_access
  
  private
  
  def log_access
    AccessLog.create(
      user: current_user,
      action: action_name,
      path: request.path,
      ip: request.remote_ip
    )
  end
end

3.2 响应处理 #

ruby
# app/controllers/api/v1/base_controller.rb
module Api
  module V1
    class BaseController < ApplicationController
      after_action :set_response_headers
      
      private
      
      def set_response_headers
        response.set_header('X-Request-Id', request.uuid)
        response.set_header('X-Page', params[:page] || 1)
      end
    end
  end
end

3.3 记录性能 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  after_action :record_performance
  
  private
  
  def record_performance
    duration = (Time.now - request_start_time) * 1000
    Rails.logger.info "Action #{action_name} took #{duration.round(2)}ms"
  end
  
  def request_start_time
    request.env['action_dispatch.request_start_time'] || Time.now
  end
end

四、around_action #

4.1 基本用法 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_action :track_time
  
  private
  
  def track_time
    start = Time.now
    yield  # 执行动作
    duration = Time.now - start
    Rails.logger.info "Request took #{duration}s"
  end
end

4.2 事务管理 #

ruby
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  around_action :wrap_in_transaction, only: [:create, :update]
  
  def create
    @order = Order.new(order_params)
    # 如果保存失败,事务会自动回滚
    @order.save!
    redirect_to @order
  end
  
  private
  
  def wrap_in_transaction
    ActiveRecord::Base.transaction do
      yield
    end
  end
end

4.3 异常处理 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_action :handle_exceptions
  
  private
  
  def handle_exceptions
    yield
  rescue StandardError => e
    Rails.logger.error "Error: #{e.message}"
    Rails.logger.error e.backtrace.join("\n")
    
    respond_to do |format|
      format.html { render file: 'public/500.html', status: :internal_server_error }
      format.json { render json: { error: 'Internal Server Error' }, status: :internal_server_error }
    end
  end
end

五、过滤器继承 #

5.1 继承链 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :require_login
end

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  # 继承require_login过滤器
  before_action :set_article, only: [:show, :edit]
end

# app/controllers/admin/articles_controller.rb
module Admin
  class ArticlesController < ApplicationController
    # 继承require_login过滤器
    before_action :require_admin
  end
end

5.2 跳过过滤器 #

ruby
# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  # 跳过继承的过滤器
  skip_before_action :require_login, only: [:index, :show]
end

# app/controllers/api/base_controller.rb
module Api
  class BaseController < ApplicationController
    # 跳过CSRF保护
    skip_before_action :verify_authenticity_token
  end
end

六、过滤器最佳实践 #

6.1 认证过滤器 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :authenticate_user!
  
  private
  
  def authenticate_user!
    redirect_to login_path unless current_user
  end
  
  def current_user
    @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
  end
  
  helper_method :current_user
end

6.2 授权过滤器 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :authorize_user!
  
  private
  
  def authorize_user!
    return if current_user&.admin?
    
    redirect_to root_path, alert: '您没有权限访问此页面'
  end
end

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :check_article_owner, only: [:edit, :update, :destroy]
  
  private
  
  def check_article_owner
    @article = Article.find(params[:id])
    redirect_to @article, alert: '您只能编辑自己的文章' unless @article.author == current_user
  end
end

6.3 数据准备过滤器 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  before_action :set_categories, only: [:new, :edit]
  
  private
  
  def set_article
    @article = Article.find(params[:id])
  end
  
  def set_categories
    @categories = Category.all
  end
end

七、高级用法 #

7.1 过滤器链控制 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # 在特定过滤器之前执行
  prepend_before_action :set_locale
  
  # 在特定过滤器之后执行
  append_after_action :log_request
  
  private
  
  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end
  
  def log_request
    Rails.logger.info "Request: #{request.method} #{request.path}"
  end
end

7.2 过滤器条件组合 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  before_action :check_permission, only: [:edit, :update, :destroy]
  before_action :require_admin, if: -> { action_name == 'destroy' }
  
  private
  
  def set_article
    @article = Article.find(params[:id])
  end
  
  def check_permission
    redirect_to @article unless @article.author == current_user
  end
  
  def require_admin
    redirect_to root_path unless current_user.admin?
  end
end

7.3 过滤器类 #

ruby
# lib/filters/authentication_filter.rb
class AuthenticationFilter
  def self.before(controller)
    unless controller.current_user
      controller.redirect_to controller.login_path
      false
    end
  end
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action AuthenticationFilter
end

八、过滤器调试 #

8.1 查看过滤器链 #

ruby
# Rails控制台
rails console

# 查看控制器的过滤器链
ArticlesController._process_action_callbacks.map { |c| c.filter if c.kind == :before }.compact

8.2 日志记录 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :log_filter_execution
  
  private
  
  def log_filter_execution
    Rails.logger.debug "Executing filter for #{action_name}"
  end
end

九、总结 #

9.1 核心要点 #

要点 说明
before_action 动作执行前运行
after_action 动作执行后运行
around_action 动作执行前后运行
only/except 限制过滤器作用范围
if/unless 条件执行过滤器
skip_before_action 跳过继承的过滤器

9.2 下一步 #

现在你已经掌握了过滤器,接下来让我们学习 响应格式,深入了解Rails的多种响应格式处理!

最后更新:2026-03-28