Rails - 如何使用子对象的嵌套属性和强参数填充父对象 id? [英] Rails -- how to populate parent object id using nested attributes for child object and strong parameters?
问题描述
我遇到的情况很像 Railscast 196-197:嵌套模型表单.但是,我遇到了这种方法和强参数之间的冲突.我想不出在子对象上填充父记录 id 字段的好方法,因为我不希望它可以通过表单分配(以防止用户将子记录关联到他们不拥有的父记录)).我有一个解决方案(请参阅下面的代码),但这似乎是 Rails 为我提供的一种聪明、简单的方法.
I've got a situation much like is presented in Railscast 196-197: Nested Model Form. However, I've encountered a conflict between this approach and strong parameters. I can't figure out a good way to populate the parent record id field on the child object, since I don't want that to be assignable through the form (to prevent users from associating child records to parent records they don't own). I have a solution (see code below) but this seems like the kind of thing Rails might have a clever, easy way to do for me.
这是代码...
有一个父对象(称为调查),它有_许多子对象(称为问题):
There's a parent object (call it Survey) that has_many child objects (call them Questions):
# app/models/survey.rb
class Survey
belongs_to :user
has_many :questions
accepts_nested_attributes_for :questions
end
# app/models/question.rb
class Question
validates :survey_id, :presence => true
belongs_to :survey
end
有一个表单允许用户同时创建调查和调查中的问题(为简单起见,下面的代码将调查视为只有问题):
There's a form that allows users to create a survey and the questions on that survey at the same time (for simplicity, the code below treats surveys as though they have only question):
# app/views/surveys/edit.html.erb
<%= form_for @survey do |f| %>
<%= f.label :name %>
<%= f.text_field :name %><br />
<%= f.fields_for :questions do |builder| %>
<%= builder.label :content, "Question" %>
<%= builder.text_area :content, :rows => 3 %><br />
<% end %>
<%= f.submit "Submit" %>
<% end %>
问题是控制器.我想通过强参数保护问题记录中的survey_id 字段,但这样做时问题不会通过验证,因为survey_id 是必填字段.
The problem is the controller. I want to protect the survey_id field on the question record via strong parameters, but in doing so the questions don't pass validation, since the survey_id is a required field.
# app/controllers/surveys_controller.rb
class SurveysController
def edit
@survey = Survey.new
Survey.questions.build
end
def create
@survey = current_user.surveys.build(survey_params)
if @survey.save
redirect_to @survey
else
render :new
end
end
private
def survey_params
params.require(:survey).permit(:name, :questions_attributes => [:content])
end
end
我认为解决这个问题的唯一方法是像这样将问题与调查分开构建:
The only way I can think to solve this problem is to build the questions separately from the survey like this:
def create
@survey = current_user.surveys.build(survey_params)
if @survey.save
if params[:survey][:questions_attributes]
params[:survey][:questions_attributes].each_value do |q|
question_params = ActionController::Parameters.new(q)
@survey.questions.build(question_params.permit(:content))
end
end
redirect_to @survey
else
render :new
end
end
private
def survey_params
params.require(:survey).permit(:name)
end
(Rails 4 测试版 1,Ruby 2)
(Rails 4 beta 1, Ruby 2)
更新
也许处理这个问题的最好方法是按照 这篇代码气候博客文章.不过,我将问题悬而未决,因为我对其他观点感到好奇
Perhaps the best way to handle this problem is to factor out a "Form object" as suggested in this Code Climate blog post. I'm leaving the question open, though, as I'm curious to other points of view
推荐答案
所以你遇到的问题是子对象没有通过验证,对吧?当子对象与父对象同时创建时,子对象不可能知道其父对象的 id 以通过验证,这是真的.
So the problem you are running into is that the child objects don't pass validation, right? When the child objects are created at the same time as the parent, the child objects could not possibly know the id of their parent in order to pass validation, it's true.
以下是解决该问题的方法.按如下方式更改您的模型:
Here is how you can solve that problem. Change your models as follows:
# app/models/survey.rb
class Survey
belongs_to :user
has_many :questions, :inverse_of => :survey
accepts_nested_attributes_for :questions
end
# app/models/question.rb
class Question
validates :survey, :presence => true
belongs_to :survey
end
这里的区别在于传递给 has_many
关联的 :inverse_of
,并且问题现在仅验证 :survey
而不是 <代码>:survey_id.
The differences here are the :inverse_of
passed to the has_many
association, and that the Question now validates on just :survey
instead of :survey_id
.
:inverse_of
使得当使用关联创建或构建子对象时,它还接收对创建它的父对象的反向引用.这似乎应该自动发生,但不幸的是,除非您指定此选项,否则不会发生.
:inverse_of
makes it so that when a child object is created or built using the association, it also receives a back-reference to the parent who created it. This seems like something that should happen automagically, but it unfortunately does not unless you specify this option.
在 :survey
上验证而不是在 :survey_id
上进行验证是一种妥协.验证不再简单地检查survey_id 字段中是否存在非空白内容;它现在实际上检查关联是否存在父对象.在这种情况下,由于 :inverse_of
而知道它是有帮助的,但在其他情况下,它实际上必须使用 id 从数据库加载关联才能进行验证.这也意味着不匹配数据库中任何内容的 id 将不会通过验证.
Validating on :survey
instead of on :survey_id
is kind of a compromise. The validation is no longer simply checking for the existence of something non-blank in the survey_id field; it now actually checks the association for the existence of a parent object. In this case it is helpfully known due to :inverse_of
, but in other cases it will actually have to load the association from the database using the id in order to validate. This also means that ids not matching anything in the database will not pass validation.
希望有所帮助.
这篇关于Rails - 如何使用子对象的嵌套属性和强参数填充父对象 id?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!