Rails参数处理 #

一、参数概述 #

1.1 参数来源 #

Rails参数来自多个来源:

来源 说明 示例
路由参数 URL中的动态片段 /articles/:id
查询参数 URL中的查询字符串 ?page=2
表单参数 POST/PUT请求体 表单提交数据
JSON参数 JSON格式请求体 API请求

1.2 params对象 #

ruby
# params是一个Hash-like对象
params[:id]           # 获取参数
params[:article]      # 获取嵌套参数
params.permit(:id)    # Strong Parameters

二、Strong Parameters #

2.1 为什么需要Strong Parameters #

Strong Parameters用于防止批量赋值漏洞,只允许指定的参数通过。

2.2 基本用法 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def create
    # 不安全:允许所有参数
    # @article = Article.new(params[:article])
    
    # 安全:只允许指定参数
    @article = Article.new(article_params)
  end
  
  private
  
  def article_params
    # require确保article参数存在
    # permit只允许title和body参数
    params.require(:article).permit(:title, :body)
  end
end

2.3 permit详解 #

ruby
# 允许标量参数
params.permit(:id, :title, :body)

# 允许嵌套参数
params.require(:article).permit(:title, :body, comments: [:id, :body])

# 允许数组参数
params.require(:article).permit(:title, tags: [])

# 允许所有参数(不推荐)
params.require(:article).permit!

2.4 require详解 #

ruby
# require确保参数存在,否则抛出异常
params.require(:article)

# 条件性require
params.fetch(:article, {}).permit(:title, :body)

# 多个require
params.require(:article).require(:metadata)

三、嵌套参数 #

3.1 一层嵌套 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  private
  
  def article_params
    params.require(:article).permit(
      :title,
      :body,
      author: [:name, :email]
    )
  end
end

3.2 多层嵌套 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  private
  
  def article_params
    params.require(:article).permit(
      :title,
      :body,
      author: [
        :name,
        :email,
        profile: [:bio, :location]
      ],
      comments: [
        :id,
        :body,
        :author_name
      ]
    )
  end
end

3.3 动态嵌套 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  private
  
  def article_params
    params.require(:article).permit(
      :title,
      :body,
      metadata: permitted_metadata_keys
    )
  end
  
  def permitted_metadata_keys
    [:keywords, :description, :author]
  end
end

四、数组参数 #

4.1 基本数组 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  private
  
  def article_params
    params.require(:article).permit(
      :title,
      tags: []  # 允许任意数量的标签
    )
  end
end

# 请求示例
# { article: { title: 'Rails Guide', tags: ['rails', 'ruby', 'web'] } }

4.2 对象数组 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  private
  
  def article_params
    params.require(:article).permit(
      :title,
      comments_attributes: [:id, :body, :author_name, :_destroy]
    )
  end
end

# 配合accepts_nested_attributes_for使用
# app/models/article.rb
class Article < ApplicationRecord
  has_many :comments
  accepts_nested_attributes_for :comments, allow_destroy: true
end

五、参数类型转换 #

5.1 自动转换 #

ruby
# Rails自动转换某些参数类型
params[:id]              # 字符串 "1"
params[:id].to_i         # 整数 1
params[:enabled]         # 字符串 "true"
params[:enabled] == 'true'  # 布尔比较

5.2 布尔转换 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    # 使用ActiveModel类型转换
    @published = ActiveModel::Type::Boolean.new.cast(params[:published])
    
    # 或使用自定义方法
    @featured = to_boolean(params[:featured])
  end
  
  private
  
  def to_boolean(value)
    ActiveRecord::Type::Boolean.new.cast(value)
  end
end

5.3 数组转换 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    # 字符串转数组
    @tags = params[:tags].to_s.split(',')
    
    # 确保是数组
    @ids = Array(params[:ids])
  end
end

六、参数验证 #

6.1 存在性验证 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :validate_params, only: [:create, :update]
  
  def create
    @article = Article.new(article_params)
    
    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end
  
  private
  
  def validate_params
    unless params[:article].present?
      render json: { error: '缺少article参数' }, status: :bad_request
    end
    
    unless params[:article][:title].present?
      render json: { error: '缺少title参数' }, status: :bad_request
    end
  end
end

6.2 类型验证 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  private
  
  def article_params
    params.require(:article).permit(:title, :body, :category_id).tap do |p|
      # 验证category_id是数字
      if p[:category_id].present? && !p[:category_id].match?(/\A\d+\z/)
        raise ActionController::ParameterMissing, 'category_id必须是数字'
      end
    end
  end
end

七、参数过滤 #

7.1 全局过滤 #

ruby
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [
  :password,
  :password_confirmation,
  :token,
  :secret,
  :api_key,
  :credit_card
]

# 过滤后的日志
# Processing by UsersController#create as HTML
#   Parameters: {"user"=>{"name"=>"John", "password"=>"[FILTERED]"}}

7.2 控制器级别过滤 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :filter_params
  
  private
  
  def filter_params
    params.except!(:password, :token)
  end
end

八、参数默认值 #

8.1 设置默认值 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.page(page_param).per(per_page_param)
  end
  
  private
  
  def page_param
    params[:page].presence || 1
  end
  
  def per_page_param
    params[:per_page].presence || 20
  end
end

8.2 使用fetch #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @page = params.fetch(:page, 1)
    @per_page = params.fetch(:per_page, 20)
  end
end

九、API参数处理 #

9.1 JSON参数 #

ruby
# app/controllers/api/v1/articles_controller.rb
module Api
  module V1
    class ArticlesController < ApplicationController
      def create
        # JSON请求自动解析
        @article = Article.new(article_params)
        
        if @article.save
          render json: @article, status: :created
        else
          render json: { errors: @article.errors }, status: :unprocessable_entity
        end
      end
      
      private
      
      def article_params
        params.require(:article).permit(:title, :body, :status)
      end
    end
  end
end

9.2 包装参数 #

ruby
# app/controllers/api/v1/articles_controller.rb
module Api
  module V1
    class ArticlesController < ApplicationController
      # 自动将参数包装在article键下
      wrap_parameters format: [:json]
      
      def create
        @article = Article.new(article_params)
        # ...
      end
      
      private
      
      def article_params
        params.permit(:title, :body, :status)
      end
    end
  end
end

9.3 命名参数 #

ruby
# app/controllers/api/v1/articles_controller.rb
module Api
  module V1
    class ArticlesController < ApplicationController
      def index
        # 使用命名参数
        articles = Article.where(filter_params)
        render json: articles
      end
      
      private
      
      def filter_params
        params.slice(:status, :category_id, :author_id).permit!
      end
    end
  end
end

十、高级用法 #

10.1 参数对象模式 #

ruby
# app/parameters/article_parameters.rb
class ArticleParameters
  attr_reader :params
  
  def initialize(params)
    @params = params
  end
  
  def to_h
    @to_h ||= params.require(:article).permit(permitted_attributes)
  end
  
  private
  
  def permitted_attributes
    [
      :title,
      :body,
      :status,
      :category_id,
      tag_ids: [],
      author_attributes: [:name, :email]
    ]
  end
end

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def create
    @article = Article.new(article_params.to_h)
    # ...
  end
  
  private
  
  def article_params
    ArticleParameters.new(params)
  end
end

10.2 条件参数 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  private
  
  def article_params
    permitted = [:title, :body]
    
    # 管理员可以设置更多字段
    if current_user.admin?
      permitted += [:status, :featured, :author_id]
    end
    
    params.require(:article).permit(permitted)
  end
end

10.3 参数合并 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def create
    @article = Article.new(article_params.merge(
      author: current_user,
      status: 'draft'
    ))
    # ...
  end
end

十一、调试技巧 #

11.1 查看参数 #

ruby
# 控制器中
def create
  Rails.logger.debug "Params: #{params.inspect}"
  Rails.logger.debug "Article params: #{article_params.inspect}"
  # ...
end

11.2 视图中调试 #

erb
<!-- 显示所有参数 -->
<%= debug(params) %>

<!-- 显示特定参数 -->
<%= params[:id] %>
<%= params[:article][:title] rescue 'N/A' %>

十二、最佳实践 #

12.1 参数处理原则 #

原则 说明
使用Strong Parameters 永远不要信任用户输入
集中定义 在私有方法中定义参数
保持简洁 只允许必要的参数
验证输入 在模型中验证数据
过滤敏感信息 日志中过滤密码等

12.2 代码组织 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  
  def index
    @articles = Article.filter(filter_params).page(page_param)
  end
  
  def create
    @article = Article.new(article_params)
    
    if @article.save
      redirect_to @article, notice: '创建成功'
    else
      render :new, status: :unprocessable_entity
    end
  end
  
  private
  
  def set_article
    @article = Article.find(params[:id])
  end
  
  def article_params
    params.require(:article).permit(:title, :body, :status, :category_id)
  end
  
  def filter_params
    params.slice(:status, :category_id).permit!
  end
  
  def page_param
    params.fetch(:page, 1)
  end
end

十三、总结 #

13.1 核心要点 #

要点 说明
Strong Parameters 只允许指定参数
require 确保参数存在
permit 允许特定参数
嵌套参数 处理复杂结构
参数过滤 保护敏感信息

13.2 下一步 #

现在你已经掌握了参数处理,接下来让我们学习 过滤器,深入了解控制器的生命周期回调!

最后更新:2026-03-28