【问题标题】:Rails -- how to populate parent object id using nested attributes for child object and strong parameters?Rails - 如何使用子对象的嵌套属性和强参数填充父对象ID?
【发布时间】:2013-05-28 02:31:04
【问题描述】:

我遇到的情况很像Railscast 196-197: Nested Model Form 中介绍的情况。但是,我遇到了这种方法和强参数之间的冲突。我想不出一种在子对象上填充父记录 id 字段的好方法,因为我不希望它可以通过表单分配(以防止用户将子记录关联到他们不拥有的父记录)。我有一个解决方案(参见下面的代码),但这似乎是 Rails 可能为我提供的一种聪明、简单的方法。

这是代码...

有一个父对象(称为调查)有_许多子对象(称为问题):

# 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

有一个表单允许用户同时创建调查和该调查中的问题(为简单起见,下面的代码将调查视为只有问题):

# 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 是必填字段。

# 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

我能想到解决这个问题的唯一方法是将问题与调查分开构建,如下所示:

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)

更新

也许处理此问题的最佳方法是按照this Code Climate blog post 中的建议分解出“表单对象”。不过,由于我对其他观点感到好奇,因此我将问题悬而未决

【问题讨论】:

  • 你知道如何使用嵌套表单来解决这个问题吗?

标签: ruby-on-rails strong-parameters


【解决方案1】:

所以你遇到的问题是子对象没有通过验证,对吧?当子对象与父对象同时创建时,子对象不可能知道父对象的id才能通过验证,这是真的。

以下是解决该问题的方法。按如下方式更改模型:

# 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

这里的区别在于 :inverse_of 传递给 has_many 关联,并且问题现在仅在 :survey 而不是 :survey_id 上验证。

:inverse_of 使得当使用关联创建或构建子对象时,它还接收对创建它的父对象的反向引用。这似乎应该自动发生,但不幸的是,除非您指定此选项,否则它不会发生。

验证 :survey 而不是 :survey_id 是一种妥协。验证不再是简单地检查survey_id 字段中是否存在非空白内容;它现在实际上检查关联是否存在父对象。在这种情况下,由于:inverse_of,它很有帮助,但在其他情况下,它实际上必须使用 id 从数据库加载关联才能进行验证。这也意味着不匹配数据库中任何内容的 id 将无法通过验证。

希望对您有所帮助。

【讨论】:

  • nested_attributes_for :question 是否会包含问题可能具有的所有嵌套属性?如果有问题,has_one :answer。这是否也包括这些属性?
  • Surge - 如果您要嵌套更多类似的模型,那么您将不得不再次应用相同的逻辑,即,问题必须具有 accept_nested_attributes_for :answer。然后你可以嵌套任意数量。
  • 非常感谢,我在视图和控制器中寻找了一个错误一整天,然后我找到了您的提示。
  • 非常感谢,这个问题给我带来了很多项目的问题,很高兴我终于找到了正确的答案!
  • 对于那些因为我们的关系是polymorphic而无法设置inverse_of的不幸的人有什么建议吗?
猜你喜欢
  • 2013-08-28
  • 2017-10-03
  • 1970-01-01
  • 2019-07-22
  • 1970-01-01
  • 1970-01-01
  • 2014-06-12
  • 1970-01-01
相关资源
最近更新 更多