【问题标题】:Create method for nested resource with protected ID attribute为具有受保护 ID 属性的嵌套资源创建方法
【发布时间】:2013-01-31 05:35:09
【问题描述】:

我正在关注来自 PeepCode 的(现在稍微过时的)Meet Rails 3 教程,但在获取教程中的一些建议以与 Rails 3.2 一起使用时遇到了问题。

本教程让您创建一个属于ProjectRole 模型:

class Role < ActiveRecord::Base
  belongs_to :project
  validates :project_id, :presence => true
  attr_protected :project_id
end

routes.rb 文件嵌套了Role 资源,因此您必须在Project 的上下文中使用Role

resources :projects do
  resources :roles
end

注意在上面的模型代码中,本教程建议您使用attr_protected 来保护:project_id 字段,因为可以通过在项目上下文中创建每个Role 来“更安全”地设置它,例如这在 roles_controller.rb 中:

class RolesController < ApplicationController
  ⋮

  def create
    @role = project.roles.new(params[:role])
    ⋮

问题是,用于创建Role 的HTML 表单是用Formtastic 创建的,其中包含用于选择项目的project_id 字段。因此,当project.roles.new(params[:role]) 尝试使用表单中的参数来填充新的Role 对象时,它会尝试使用批量赋值来设置project_id,但失败并显示:

ActiveModel::MassAssignmentSecurity::RolesController#create 中的错误
无法批量分配受保护的属性:project_id

公认的实现方式是什么?保护project_id 属性是个坏主意吗?或者有什么方法可以用表单数据填充新的Role,而不包括project_id

【问题讨论】:

    标签: ruby-on-rails-3.2 formtastic


    【解决方案1】:

    如果您通过params[:project_id] 而不是params[:role][:project_id] 获得project,那么您实际上可能会设置冲突值。

    Mass Assignment 想要保护这一点的原因是为了防止用户为project_id 输入任意值,这可能允许不受此用户控制的project。你有几个选择。

    如果您将权威的useraccount 附加到对象,您可以添加before_save 回调,例如self.project_id = nil unless user.projects.find(project_id)

    既然你不这样做,我会使用哈希中的 project_id 来查找项目,然后回退到路由 ID(我不确定它是 project_id 还是只是 id从我的头顶)。

    def create
      user.
        projects.
        find(params[:role].delete(:project_id) || params[:project_id] || params[:id]).
        create(params[:role])
    

    最简单的方法是从表单中删除选择框,因为他们在选择创建新角色时选择了一个项目——它是一个嵌套资源。

    【讨论】:

    • 确实,本教程的下一步将重新编写formtastic 表单,使其不再包含项目下拉菜单。该错误虽然从未在教程中提及,但已通过该更改修复。