I'm making nested form with Rails 4.2. And I'm having problems with Create and Edit and Update actions on 3rd layer object Answer. I've summarized information in below. If you know what I'm doing wrong or did not do, please kindly let me know.
Scope:
Issues: I've set up nested has_many associations between Survey > Questions > Answers and setup 'accepts_nested_attributes_for' and added model's attributes in permit parameters. And made Survey form that has fields for all models. It works fine for creating Survey with Questions with one submit from one form, and these two model's attributes appear in Show action as well. But Answer won't saved, and answer from doesn't appear in Edit form.
Code:
class Survey < ActiveRecord::Base
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions, allow_destroy: true
end
class Question < ActiveRecord::Base
belongs_to :survey
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers, allow_destroy: true
end
class Answer < ActiveRecord::Base
belongs_to :question
end
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
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
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
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
<h1>New Survey</h1>
<%= render 'form' %>
<%= link_to 'Back', surveys_path %>
<h1>Editing Survey</h1>
<%= render 'form' %>
<%= link_to 'Show', @survey %> |
<%= link_to 'Back', surveys_path %>
<%= 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 %>
<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>
<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>
<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 %>
Thanks!
You should whitelist the answers_attributes
in survey_params
. Changing it like below should get you going.
def survey_params
params.require(:survey).permit(:name, :description, questions_attributes: [:id, :name, :description, :_destroy, answers_attributes: [:id, :content, :_destroy]])
end