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

这是密码...

有一个父对象(称为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

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

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

UPDATE

也许处理这个问题的最佳方法是按照this Code Climate blog post中的建议,计算出一个"Form object".不过,由于我对其他观点很好奇,所以我将这个问题留待讨论

推荐答案

所以你遇到的问题是子对象没有通过验证,对吗?当子对象与父对象同时创建时,子对象不可能知道父对象的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

这里的差异是传递给has_many协会的:inverse_of个,而这个问题现在只验证了:survey个,而不是:survey_id个.

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

:survey而不是:survey_id上验证是一种折衷.验证不再是简单地判断survey_id字段中是否存在非空白的内容;它现在实际判断关联是否存在父对象.在本例中,由于:inverse_of,它是有用的,但在其他情况下,它实际上必须使用id从数据库中加载关联以进行验证.这也意味着与数据库中的任何内容都不匹配的ID将无法通过验证.

希望有帮助.

Ruby-on-rails相关问答推荐

使用Hotwire/Turbo的Rails在链接悬停时获取请求

在PDF生成中渲染 colored颜色

如何使用继承类 (STI) 获取 Ruby on Rails 类中基类的实例

Rails 创建自定义函数/方法以及存储位置

如何在 Rails 中声明动态路由范围/命名空间变量?

Mongoid 3 + Heroku (MongoHQ) 导致 Moped::Errors::OperationFailure

为 ActionMailer 渲染不同的视图(模板)

有没有办法在模型而不是视图中使用复数()?

如何在rails中发回js.haml

Ruby如何写入Tempfile

form_tag 是否与 Simple_form 一起使用?

使用 RSpec 2 关闭一个规范的事务性固定装置

如何在 rspec 测试中输出变量?

如何验证 ActiveRecord 中邮箱字段的格式?

导轨链接到:远程

默认呈现 JSON 而不是 HTML?

过滤器在渲染之前但在控制器之后执行?

我可以在 twitter-bootstrap popover 数据内容中使用 html 标签吗?

停止 Rails 为视图和助手生成规范测试?

如何在 Ruby 类/模块命名空间中翻译模型?