I've got a situation much like is presented in Railscast 196-197: Nested Model Form. 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). 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.
Here's the code...
There's a parent object (call it Survey) that has_many child objects (call them Questions):
# 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.
# 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)
UPDATE
Perhaps the best way to handle this problem is to factor out a "Form object" as suggested in this Code Climate blog post. I'm leaving the question open, though, as I'm curious to other points of view