Nested Model Form in Rails 4

Quick guide on how to build a simple survey application using Rails 4 and simple_form.

There are some good topics on it on RailsCasts (Rails 3 and no simple_form):

And also a bunch of questions answered on stackoverflow.

Please use the official documentation to understand things you’re doing http://edgeguides.rubyonrails.org/association_basics.html

I just want to cover the most basic setup.

Make sure you’re using Rails 4

1
2
% rails -v
Rails 4.0.0

Create a new Rails app

1
% rails new myapp

I’m going to use haml instead of erb and simple_form for creating forms. Add to Gemfile

1
2
gem 'haml-rails'
gem 'simple_form', '>= 3.0.0.rc'

Install all required gems using bundler

1
% bundle install

Generate a new resource for surveys, questions and answers

1
2
3
% bundle exec rails g scaffold Answer content:string
% bundle exec rails g scaffold Question content:string
% bundle exec rails g scaffold Survey title:string

Create associations between models

We’re going to use has_many and belongs_to associations as the simplest for our case.

Create additional columns

First of all we need to create and additional column inside anwers and questions database tables containing an ID of associated object

1
2
% bundle exec rails g migration add_question_id_to_answers question_id:integer
% bundle exec rails g migration add_survey_id_to_questions survey_id:integer

Run migrations

Run migrations to get tables actually created

1
% bundle exec rake db:migrate

Create associations between models

Then we need to add the associations to our models. We’re assuming that each question belongs to only one survey and can have multiple answers. Each survey can have multiple questions. We need to add accepts_nested_attributes_for method to be able to manage associated records in a form.

Edit app/models/answer.rb

1
2
3
class Answer < ActiveRecord::Base
  belongs_to :question
end

Edit app/models/question.rb

1
2
3
4
5
class Question < ActiveRecord::Base
  belongs_to :survey
  has_many :answers, dependent: :destroy
  accepts_nested_attributes_for :answers
end

Edit app/models/survey.rb

1
2
3
4
class Survey < ActiveRecord::Base
  has_many :questions, dependent: :destroy
  accepts_nested_attributes_for :questions
end

Edit controllers

Rails 4 uses strong parameters, so you need to edit your controllers.

Edit the private method called question_params in app/controllers/questions_controller.rb

1
2
3
4
5
  # Never trust parameters from the scary internet, only allow the white list through.
  def question_params
    params.require(:question).permit(:content,
                                     answers_attributes: [:content])
  end

Edit the private method called survey_params in app/controllers/surveys_controller.rb

1
2
3
4
5
6
7
  # Never trust parameters from the scary internet, only allow the white list through.
  def survey_params
    params.require(:survey).permit(:title,
                                   questions_attributes: [:content,
                                   answers_attributes:   [:content]]
                                  )
  end

Edit views

We’re going only to edit survey view to be able to create survey containing question and answer on one page.

Edit app/views/surveys/_form.html.haml

1
2
3
4
5
6
7
8
9
10
= simple_form_for(@survey) do |f|
  = f.error_notification
  = f.input :title, label: "Survey title"

  = f.simple_fields_for :questions, @survey.questions.build do |q|
    = q.input :content, label: "Question content"
    = q.simple_fields_for :answers, q.object.answers.build do |a|
      = a.input :content, label: "Answer content"

  = f.button :submit

Run rails server

Run default rails server to see the results

1
% bundle exec rails server

Head to the app in your web browser http://localhost:3000/surveys/new. And you should see the similar page:

Fill all the fields and submit the form. Check out your server output or logs: log/development.log

1
2
3
4
5
6
7
8
9
10
Started POST "/surveys" for 127.0.0.1 at 2013-10-14 02:01:02 +0400
Processing by SurveysController#create as HTML
  Parameters: {"utf8"=>"v", "authenticity_token"=>"0sHaymbefj2pnNHki3Rc/UTkwDkSvM5rxtDVHtXWtzI=", "survey"=>{"title"=>"Rails guide", "questions_attributes"=>{"0"=>{"content"=>"How was the guide?", "answers_attributes"=>{"0"=>{"content"=>"Awesome"}}}}}, "commit"=>"Create Survey"}
   (0.1ms)  begin transaction
  SQL (5.0ms)  INSERT INTO "surveys" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 13 Oct 2013 22:01:02 UTC +00:00], ["title", "Rails guide"], ["updated_at", Sun, 13 Oct 2013 22:01:02 UTC +00:00]]
  SQL (0.5ms)  INSERT INTO "questions" ("content", "created_at", "survey_id", "updated_at") VALUES (?, ?, ?, ?)  [["content", "How was the guide?"], ["created_at", Sun, 13 Oct 2013 22:01:02 UTC +00:00], ["survey_id", 1], ["updated_at", Sun, 13 Oct 2013 22:01:02 UTC +00:00]]
  SQL (0.3ms)  INSERT INTO "answers" ("content", "created_at", "question_id", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Awesome"], ["created_at", Sun, 13 Oct 2013 22:01:02 UTC +00:00], ["question_id", 1], ["updated_at", Sun, 13 Oct 2013 22:01:02 UTC +00:00]]
   (0.7ms)  commit transaction
Redirected to http://localhost:3000/surveys/1
Completed 302 Found in 47ms (ActiveRecord: 6.6ms)

That’s all for now.

More Fun in snej.com

Snej.com - Little Party

Long Night of Museums