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