创建、编辑和更新不适用于 Rails 4.2 中第一层嵌套形式的第三层对象

Create and Edit and Update won't work for 3rd laryer objects from Nested form on 1st layer in Rails 4.2

我正在使用 Rails 4.2 制作嵌套表格。我在对第 3 层对象 Answer 进行创建、编辑和更新操作时遇到问题。我在下面总结了信息。如果您知道我做错了什么或没有做,请告诉我。

范围:

问题: 我已经在 Survey > Questions > Answers 和 setup 'accepts_nested_attributes_for' 之间设置了嵌套的 has_many 关联,并在许可参数中添加了模型的属性。并制作了包含所有模型字段的调查表。它可以很好地创建带有问题的调查,从一个表单提交一个,并且这两个模型的属性也出现在 Show 操作中。但答案不会被保存,并且答案来自不会出现在编辑表单中。

代码:

型号

Survey.rb

class Survey < ActiveRecord::Base
    has_many :questions, dependent: :destroy
    accepts_nested_attributes_for :questions, allow_destroy: true
end

Question.rb

class Question < ActiveRecord::Base
    belongs_to :survey
    has_many :answers, dependent: :destroy
    accepts_nested_attributes_for :answers, allow_destroy: true
end

Answer.rb

class Answer < ActiveRecord::Base
  belongs_to :question
end

控制器

Surveys_controller.rb

class SurveysController < ApplicationController
  before_action :set_survey, only: [:show, :edit, :update, :destroy]

  # GET /surveys
  # GET /surveys.json
  def index
    @surveys = Survey.all
  end

  # GET /surveys/1
  # GET /surveys/1.json
  def show
  end

  # GET /surveys/new
  def new
    @survey = Survey.new
    @questions = @survey.questions.build
    @answers = @questions.answers.build
    #@survey.questions.build
    #@questions.answers.build
 end

  # GET /surveys/1/edit
  def edit
  #  @questions = @survey.questions.update(survey_params) #This will get ActionController::ParameterMissing in SurveysController#edit 
  #  @answers = @questions.answers.update(survey_params) #This will get ActionController::ParameterMissing in SurveysController#edit 
  end

  # POST /surveys
  # POST /surveys.json
  def create
    @survey = Survey.new(survey_params)
    respond_to do |format|
      if @survey.save
        format.html { redirect_to @survey, notice: 'Survey was successfully created.' }
        format.json { render :show, status: :created, location: @survey }
      else
        format.html { render :new }
        format.json { render json: @survey.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /surveys/1
  # PATCH/PUT /surveys/1.json
  def update
    respond_to do |format|
      if @survey.update(survey_params)
        format.html { redirect_to @survey, notice: 'Survey was successfully updated.' }
        format.json { render :show, status: :ok, location: @survey }
      else
        format.html { render :edit }
        format.json { render json: @survey.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /surveys/1
  # DELETE /surveys/1.json
  def destroy
    @survey.destroy
    respond_to do |format|
      format.html { redirect_to surveys_url, notice: 'Survey was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_survey
      @survey = Survey.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def survey_params
    #  params.require(:survey).permit(:name, :description)
      params.require(:survey).permit(:name, :description, questions_attributes: [:id, :name, :description, :_destroy])
    #  params.require(:survey).permit(:name, :description, questions_attributes: [:id, :name, :description, :_destroy, answers_attributes: [:id, :content, :_destroy]]) #I don't see any change from addin ansewrs attributes here
    end
end

Questions_controller.rb

class QuestionsController < ApplicationController
  before_action :set_question, only: [:show, :edit, :update, :destroy]

  # GET /questions
  # GET /questions.json
  def index
    @questions = Question.all
  end

  # GET /questions/1
  # GET /questions/1.json
  def show
  end

  # GET /questions/new
  def new
    @question = Question.new
    @question.answers.build
  end

  # GET /questions/1/edit
  def edit
  end

  # POST /questions
  # POST /questions.json
  def create
    @question = Question.new(question_params)

    respond_to do |format|
      if @question.save
        format.html { redirect_to @question, notice: 'Question was successfully created.' }
        format.json { render :show, status: :created, location: @question }
      else
        format.html { render :new }
        format.json { render json: @question.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /questions/1
  # PATCH/PUT /questions/1.json
  def update
    respond_to do |format|
      if @question.update(question_params)
        format.html { redirect_to @question, notice: 'Question was successfully updated.' }
        format.json { render :show, status: :ok, location: @question }
      else
        format.html { render :edit }
        format.json { render json: @question.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /questions/1
  # DELETE /questions/1.json
  def destroy
    @question.destroy
    respond_to do |format|
      format.html { redirect_to questions_url, notice: 'Question was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_question
      @question = Question.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def question_params
      params.require(:question).permit(:name, :description, :survey_id, :_destroy, answers_attributes: [:id, :content, :_destroy])
    end
end

Answers_controller.rb

class AnswersController < ApplicationController
  before_action :set_answer, only: [:show, :edit, :update, :destroy]

  # GET /answers
  # GET /answers.json
  def index
    @answers = Answer.all
  end

  # GET /answers/1
  # GET /answers/1.json
  def show
  end

  # GET /answers/new
  def new
    @answer = Answer.new
  end

  # GET /answers/1/edit
  def edit
  end

  # POST /answers
  # POST /answers.json
  def create
    @answer = Answer.new(answer_params)

    respond_to do |format|
      if @answer.save
        format.html { redirect_to @answer, notice: 'Answer was successfully created.' }
        format.json { render :show, status: :created, location: @answer }
      else
        format.html { render :new }
        format.json { render json: @answer.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /answers/1
  # PATCH/PUT /answers/1.json
  def update
    respond_to do |format|
      if @answer.update(answer_params)
        format.html { redirect_to @answer, notice: 'Answer was successfully updated.' }
        format.json { render :show, status: :ok, location: @answer }
      else
        format.html { render :edit }
        format.json { render json: @answer.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /answers/1
  # DELETE /answers/1.json
  def destroy
    @answer.destroy
    respond_to do |format|
      format.html { redirect_to answers_url, notice: 'Answer was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_answer
      @answer = Answer.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def answer_params
      params.require(:answer).permit(:content, :question_id)
    end
end

数据库

schema.rb

ActiveRecord::Schema.define(version: 20160602204457) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "answers", force: :cascade do |t|
    t.string   "content"
    t.integer  "question_id"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  add_index "answers", ["question_id", "created_at"], name: "index_answers_on_question_id_and_created_at", using: :btree
  add_index "answers", ["question_id"], name: "index_answers_on_question_id", using: :btree

  create_table "questions", force: :cascade do |t|
    t.string   "name"
    t.string   "description"
    t.integer  "survey_id"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  add_index "questions", ["survey_id", "created_at"], name: "index_questions_on_survey_id_and_created_at", using: :btree
  add_index "questions", ["survey_id"], name: "index_questions_on_survey_id", using: :btree

  create_table "surveys", force: :cascade do |t|
    t.string   "name"
    t.string   "description"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  add_foreign_key "answers", "questions"
  add_foreign_key "questions", "surveys"
end

观看次数

app/views/surveys/new.html.erb

<h1>New Survey</h1>

<%= render 'form' %>

<%= link_to 'Back', surveys_path %>

app/views/surveys/new.html.erb

<h1>Editing Survey</h1>

<%= render 'form' %>

<%= link_to 'Show', @survey %> |
<%= link_to 'Back', surveys_path %>

app/views/surveys/_form.html.erb

<%= form_for(@survey) do |f| %>
<%#= form_for(setup_survey(@survey)) do |f| %>
  <% if @survey.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@survey.errors.count, "error") %> prohibited this survey from being saved:</h2>

      <ul>
      <% @survey.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_field :description %>
  </div>
    <%= f.fields_for :questions do |ff| %>
    <%= render 'question_fields', ff: ff %>
    <% end %>

    <%= link_to_add_fields "Add Question", f, :questions %> 

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

app/views/surveys/_question_fields.html.erb

<fieldset>
  <div class="field">
    <%= ff.label :name, "Question Name" %><br>
    <%= ff.text_field :name %><br>
  </div>
  <div class="field">
    <%= ff.label :description, "Question Description" %><br>
    <%= ff.text_field :description %>
  </div>
  <div class="field">
    <%= ff.check_box :_destroy %>
    <%= ff.label :_destroy, "Remove Question"  %>
  </div>
    <%= ff.fields_for :answers do |fff| %>
    <%= render 'answer_fields', fff: fff %>
    <% end %>

</fieldset>

app/views/surveys/_answer_fields.html.erb

<fieldset>
  <div class="field">
    <%= fff.label :content, "Answer" %>
    <%= fff.text_field :content %>
  </div>
  <div class="field">
    <%= fff.check_box :_destroy %>
    <%= fff.label :_destroy, "Remove Answer" %>
  </div>
</fieldset>

app/views/surveys/show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @survey.name %>
</p>
<p>
  <strong>Description:</strong>
  <%= @survey.description %>
</p>

<ul>
<% @survey.questions.each do |question| %>
<li>
<%= question.name %><br>
<%= question.description %>
<% question.answers.each do |answer| %>
<%= answer.content %>
<% end %>
</li>
<% end %>
</ul>
<%= link_to 'Edit', edit_survey_path(@survey) %> |
<%= link_to 'Back', surveys_path %>

谢谢!

您应该将 survey_params 中的 answers_attributes 列入白名单。像下面这样更改它应该可以让你继续。

def survey_params
  params.require(:survey).permit(:name, :description, questions_attributes: [:id, :name, :description, :_destroy, answers_attributes: [:id, :content, :_destroy]])
end