Nested forms are a great way to allow users to input interconnected data all within one form. In this tutorial we are going to build a simple survey form that will allow a user to persist data with three model objects: Survey, Question, and Answer. First lets get our models set up.

survey.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Survey
  attr_reader :name

  SURVEYS = []

  def initialize(args)
    @name = args[:name]
    SURVEYS << self
  end

  def self.all
    SURVEYS
  end
end

question.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Question
  attr_reader :content

  QUESTIONS = []

  def initialize(args)
    @content = args[:content]
    QUESTIONS << self
  end

  def self.all
    QUESTIONS
  end
end

answer.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Answer
  attr_reader :content

  ANSWERS = []

  def initialize(args)
    @content = args[:content]
    ANSWERS << self
  end

  def self.all
    ANSWERS
  end
end

Within each model we are persisting a new instance at initialization. Now we can write our app.rb file to set up the routes we’ll need for the form post our data and redirect up to a nice show page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BlogNestedForms < Sinatra::Base

  set :public_folder => "public", :static => true

  get "/" do
    erb :welcome
  end

  get "/new" do
    erb :"survey/new"
  end

  post "/survey" do
    @survey = Survey.new(params[:survey])

    params[:survey][:questions].each do |question|
      Question.new(question)
      Answer.new(question[:answers])
    end
    @questions = Question.all
    @answers = Answer.all


    erb :"survey/show"
  end
end

Now we can get to the meat of nested forms, the form! Since we have multiple questions within a single survey, we need a way to organize the questions. Lets add a dash of ERB magic to pass the question data into an indexed array. Using [] allows ERB to automagically index the questions hash.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<form action="/survey" method="post">

  <h1>Survey</h1>
  <label for="survey_name">Survey Name</label>
  <input type="text" name="survey[name]">

  <h2>Questions</h2>
  <label for="question_content_1">Question 1</label>
  <input type="text" name="survey[questions][][content]"><br>
  <label for="answer_content_1">Answer</label>
  <input type="text" name="survey[questions][][answers][content]"><br>

  <label for="question_content_2">Question 2</label>
  <input type="text" name="survey[questions][][content]"><br>
  <label for="answer_content_2">Answer</label>
  <input type="text" name="survey[questions][][answers][content]"><br>

  <label for="question_content_3">Question 3</label>
  <input type="text" name="survey[questions][][content]"><br>
  <label for="answer_content_3">Answer</label>
  <input type="text" name="survey[questions][][answers][content]"><br>

  <input type="submit" value="Submit">

</form>

If we submit this form we get back the following params hash:

1
2
3
4
5
6
"params" => {"survey"=>
{"name"=>"Dog Survey",
"questions"=>
  [{"content"=>"What is your favorite dog?", "answers"=>{"content"=>"Lab"}},
  {"content"=>"What is your dogs favorite hike?", "answers"=>{"content"=>"Point Reyes"}},
  {"content"=>"What is your dogs favorite food?", "answers"=>{"content"=>"Salmon"}}]}}

As you can see in our app.rb file the post "/survey" action uses this params hash to iterate through each question and persist the survey, questions and answers. With our instance variables we can create a nice show page to list out the survey name and the question and answer content.

1
2
3
4
5
<h1><%= @survey.name %></h1>
<% @questions.zip(@answers) do |q, a| %>
  <h2><%= q.content %></h2>
  <h3><%= a.content %></h3>
<% end %>

The #zip method comes in handy here to alternate between the two arrays @questions and @answers. This is the basic overview of nested forms in Sinatra. In a later post I will go over how to code the same nested form with Rails and ActiveRecord.