无法使用 Devise 批量分配受保护的属性以创建 has_many 嵌套模型
Posted
技术标签:
【中文标题】无法使用 Devise 批量分配受保护的属性以创建 has_many 嵌套模型【英文标题】:Can't mass-assign protected attributes for creating a has_many nested model with Devise 【发布时间】:2013-05-20 18:21:38 【问题描述】:我看过 RailsCast,另一个嵌套属性视频,很多 SO 帖子,并为此奋斗了一段时间,但我仍然无法弄清楚。我希望它是微小的。
我有两个模型,User
(由 Devise 创建)和 Locker
(又名产品愿望清单),当他们注册时,我正在尝试为 User
创建一个 Locker
。我的登录表单有一个字段是他们新的Locker
(恰当地称为:name
)的名称,我试图将其分配给在新用户注册时创建的储物柜。我所受到的只是:
WARNING: Can't mass-assign protected attributes: locker
我已经在我的两个模型中尝试了accepts_nested_attributes
和attr_accesible
的所有组合,但仍然没有任何效果。我可以从日志中看到 Devise#create 方法正在处理它,而且我知道 Devise 不够聪明,无法按照我的意愿创建模型:)
这是我的两个模型的相关部分:
# user.rb
class User < ActiveRecord::Base
attr_accessible :username, :email, :password, :password_confirmation, :remember_me, :locker_attributes
# Associations
has_many :lockers
has_many :lockups, :through => :lockers
# Model nesting access
accepts_nested_attributes_for :lockers
end
和
# locker.rb
class Locker < ActiveRecord::Base
belongs_to :user
has_many :lockups
has_many :products, :through => :lockups
attr_accessible :name, :description
end
# lockers_controller.rb (create)
@locker = current_user.lockers.build(params[:locker])
@locker.save
我假设我需要重写 Devise 的 create
方法才能以某种方式使其工作,但我对 Rails 很陌生,并且已经习惯了它的黑匣子“魔法”性质。
如果有人可以帮助我,我将非常感激。已经花太多时间在这上面了:)
编辑:我意识到我在我的问题中遗漏了一些东西。我的 Locker
模型具有三个属性 - name
、description
(非强制性)和 user_id
以将其链接回 User
。我的注册表单只需要name
,所以我没有遍历嵌套表单中的所有属性。这也与我的问题有关吗?
编辑 2: 我还想出了如何覆盖 Devise 的 RegistrationsController#create
方法,我只是不知道该放什么。 Devise 的整个 resource
对我来说毫无意义,浏览他们的 RegistrationsController
源代码也对我没有多大帮助。
对于奖励积分:当用户提交带有无效数据的登录表单时,Locker
字段总是空白,而常规的设计字段、用户名和电子邮件被填写。这也可以轻松解决吗?如果有,怎么做?
【问题讨论】:
你是如何创建储物柜的?你能发布控制器代码吗? 我发布了我的 Lockers#create 方法,但我认为我的代码甚至没有达到那个目的。我认为 Devise 正在尝试创建用户并绕过我的控制器代码。不过我可能是错的。 这应该很容易检查,只需在控制器中添加一个debugger
行,看看它是否停在那里。
@mihai 如果我所要做的就是安装调试器 gem 并将 debugger
添加到我的控制器中构建新储物柜的上方的行中,它不会触发。正如我所怀疑的那样,看来我必须重写 Devise 的 User#create 方法。
是的,关于调试器这就是你所要做的一切
【参考方案1】:
首先,你有一个错字:
attr_accessible :locker_attributes
应该是复数:
attr_accessible :lockers_attributes
那么,使用nested_attributes
的标准方法是:
<%= form_for @user do |f| %>
<%# fields_for will iterate over all user.lockers and
build fields for each one of them using the block below,
with html name attributes like user[lockers_attributes][0][name].
it will also generate a hidden field user[lockers_attributes][0][id]
if the locker is already persisted, which allows nested_attributes
to know if the locker already exists of if it must create a new one
%>
<% f.fields_for :lockers do |locker_fields| %>
<%= locker_fields.label :name %>
<%= locker_fields.text_field :name %>
<% end %>
<% end %>
在你的控制器中:
def new
@user = User.new
@user.lockers.build
end
def create
# no need to use build here : params[:user] contains a
# :lockers_attributes key, which has an array of lockers attributes as value ;
# it gets assigned to the user using user.lockers_attributes=,
# a method created by nested_attributes
@user = User.new( params[:user] )
end
作为旁注,您可以通过不同的方式避免在控制器中为新用户构建新的储物柜:
在User
上创建工厂方法,或覆盖new
,或使用after_initialize
回调来确保实例化的每个新用户都能自动构建一个储物柜
将特定对象传递给fields_for
:
<% f.fields_for :lockers, f.object.lockers.new do |new_locker_fields| %>
【讨论】:
我非常感谢您的回答,并且我认为您走在正确的轨道上(因为我了解您的目标),但是在 Devise#create 中发生了质量分配错误,我不知道那在哪里。我已经可以创建Users
、Lockers
等,这只是注册时自动为用户创建储物柜的新功能不起作用。似乎我需要深入研究 Devise,但我不知道如何。
哦,不明白问题出在设计上。你知道你可以覆盖设计控制器吗?参见devise wiki,“配置控制器”部分
哦,你需要覆盖的方法是new_with_session
on User
。有一个关于它的railscast,但不幸的是它不是免费的......另一个选择是在你的控制器上覆盖build_resource
。
嗯,我检查了 Devise 中的两个 new_with_session
实例:一个处理 facebook 登录,另一个只有这一行 new(params)
。不知道我将如何使用其中任何一个。还尝试修改提到的所有三个地方build_resource
,但我只是把事情搞砸了。一旦用户点击提交按钮,似乎设计只是扔掉与Locker
相关的任何参数。【参考方案2】:
有人用strong attributes
和如何覆盖Devise 的sign_up_params
以更“Rails 4'y”的方式帮助我找出解决方案(以捕获来自我的注册表单的所有数据)。
def sign_up_params
params.require(:user).permit(:username, :email, :password, :lockers_attributes)
end
Gemfile 添加:gem 'strong_parameters'
注释掉我的user.rb
文件中的attr_accessible
语句,因为显然强参数消除了对attr_accessible
声明的需要。
# attr_accessible :username, :email, :password, :password_confirmation, :lockers
以及在提交表单之前构建Locker的正确方法:在嵌套表单的开头:
<%= l.input :name, :required => true, label: "Locker name", :placeholder => "Name your first locker" %>
再次感谢您的帮助。我知道如果不查看整个代码库就很难回答这样的问题。
【讨论】:
以上是关于无法使用 Devise 批量分配受保护的属性以创建 has_many 嵌套模型的主要内容,如果未能解决你的问题,请参考以下文章