简体   繁体   English

Rails —如何使用子对象和强参数的嵌套属性填充父对象ID?

[英]Rails — how to populate parent object id using nested attributes for child object and strong parameters?

I've got a situation much like is presented in Railscast 196-197: Nested Model Form . 我遇到的情况很像Railscast 196-197:嵌套模型表单中所述 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). 我不知道在子对象上填充父记录id字段的好方法,因为我不希望通过表单分配该字段(以防止用户将子记录与他们不拥有的父记录相关联)。 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. 我有一个解决方案(请参见下面的代码),但这似乎是Rails可能为我做的一种聪明,简单的方法。

Here's the code... 这是代码...

There's a parent object (call it Survey) that has_many child objects (call them Questions): 有一个具有has_many个子对象(称为问题)的父对象(称为Survey):

# 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 %>

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. 我想通过强大的参数来保护问题记录上的survey_id字段,但是这样做不会使问题通过验证,因为survey_id是必填字段。

# 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 beta 1, Ruby 2) (Rails 4 beta 1,Ruby 2)

UPDATE 更新

Perhaps the best way to handle this problem is to factor out a "Form object" as suggested in this Code Climate blog post . 解决此问题的最佳方法可能是排除此Code Climate博客文章中建议的“表单对象”。 I'm leaving the question open, though, as I'm curious to other points of view 不过,我还是悬而未决,因为我对其他观点感到好奇

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. 当子对象与父对象同时创建时,子对象不可能知道其父对象的ID以通过验证,这是事实。

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

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 . 此处的区别是传递给has_many关联的:inverse_of ,并且Question现在仅对:survey而不是:survey_id

: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. :inverse_of做到这一点,以便在使用关联创建或构建子对象时,它也收到对创建它的父对象的反向引用。 This seems like something that should happen automagically, but it unfortunately does not unless you specify this option. 这似乎是应该自动发生的事情,但不幸的是,除非您指定此选项,否则不会发生。

Validating on :survey instead of on :survey_id is kind of a compromise. :survey而不是:survey_id:survey验证有点妥协。 The validation is no longer simply checking for the existence of something non-blank in the survey_id field; 验证不再仅仅是检查survey_id字段中是否存在非空白内容; 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. 在这种情况下,由于:inverse_of ,它是:inverse_of ,但是在其他情况下,它实际上必须使用id从数据库加载关联才能进行验证。 This also means that ids not matching anything in the database will not pass validation. 这也意味着ID与数据库中的任何内容都不匹配将无法通过验证。

Hope that helps. 希望能有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM