Rails - 如何使用子对象的嵌套属性和强参数填充父对象 id? [英] Rails -- how to populate parent object id using nested attributes for child object and strong parameters?

查看:33
本文介绍了Rails - 如何使用子对象的嵌套属性和强参数填充父对象 id?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到的情况很像 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆