Rails Multi-step form
<p>I'm writing a quiz app with rails 5. I have got a multi-step form for question building. </p>

<p>Models:</p>

<pre><code>class Mcq &lt; ApplicationRecord
  attr_accessor :option_count
  has_many :options, dependent: :destroy
  belongs_to :quiz

  accepts_nested_attributes_for :options
  validates :question_text, presence: true
end

class Option &lt; ApplicationRecord
  belongs_to :mcq, optional: true
  validates :option_text, presence: true
end
</code></pre>

<p>Schema:</p>

<pre><code>create_table "mcqs", force: :cascade do |t|
  t.string   "question_text"
  t.boolean  "required"
  t.boolean  "multiselect"
  t.integer  "quiz_id"
  t.datetime "created_at",    null: false
  t.datetime "updated_at",    null: false
end


create_table "options", force: :cascade do |t|
  t.string   "option_text"
  t.integer  "mcq_id"
  t.datetime "created_at",  null: false
  t.datetime "updated_at",  null: false
end
</code></pre>

<p>The first page is for question setup and has the following fields:</p>

<ol>
<li>Option Count</li>
<li>Required (Yes / No)</li>
<li>No of options that can be selected (Single / Multiple)</li>
</ol>

<p>The second page is for options and has the following fields:</p>

<ol>
<li>Question Text</li>
<li>Nested Form for Options</li>
</ol>

<p>Controller:</p>

<pre><code>class McqsController &lt; ApplicationController
  def new
    session[:current_step] ||= 'setup'
    session[:mcq_params] ||= {}

    @current_step = session[:current_step]
    @quiz = Quiz.find(params[:quiz_id])
    @mcq = Mcq.new(session[:mcq_params])

    if session[:current_step] == 'options'
      @option_count = session[:mcq_params]['option_count']
      @option_count.times { @mcq.options.build }
    end
  end

  def create
    if params[:previous_button]
      session[:current_step] = 'setup'
      redirect_to new_quiz_mcq_path
    elsif session[:current_step] == 'setup'
      save_session(params[:mcq])
      redirect_to new_quiz_mcq_path
    elsif session[:current_step] == 'options'
      @mcq = Mcq.new(whitelisted_mcq_params)
      @mcq.quiz_id = params[:quiz_id]
      @quiz = Quiz.find(params[:quiz_id])
      if @mcq.save
        session[:current_step] = session[:mcq_params] = nil
        redirect_to quiz_new_question_path(@mcq.quiz_id)
      else
        @current_step = session[:current_step]
        render :new
      end
    end
  end

  private

  def whitelisted_mcq_params
    params.require(:mcq)
        .permit(:question_text, :multiselect, :required,    options_attributes: [:option_text])
  end

  def save_session(mcq_params)
     session[:mcq_params][:option_count] = mcq_params[:option_count].to_i
     session[:mcq_params][:required] = mcq_params[:required]
     session[:mcq_params][:multiselect] = mcq_params[:multiselect]
     session[:current_step] = 'options'
  end
end
</code></pre>

<p>The above solution works, but the code is messy and difficult to understand. I came across this <a href="http://railscasts.com/episodes/217-multistep-forms" rel="nofollow">railscasts episode</a> which does something similar in a cleaner way. I've updated my code as follows:</p>

<pre><code>class Mcq &lt; ApplicationRecord
  has_many :options, dependent: :destroy
  belongs_to :quiz

  attr_writer :current_step
  attr_accessor :option_count

  accepts_nested_attributes_for :options
  validates :question_text, presence: true

  def current_step
    @current_step || steps.first
  end

  def steps
    %w[setup options]
  end

  def next_step
    self.current_step = steps[steps.index(current_step)+1]
  end

  def previous_step
    self.current_step = steps[steps.index(current_step)-1]
  end

  def last_step?
    current_step == steps.last
  end
end

class McqsController &lt; ApplicationController
  def new
    session[:mcq_params] ||= {}
    @quiz = Quiz.find(params[:quiz_id])
    @mcq = Mcq.new(session[:mcq_params])
    @mcq.current_step = session[:mcq_step]
  end

  def create
    @quiz = Quiz.find(params[:quiz_id])
    session[:mcq_params].deep_merge!(params[:mcq]) if params[:mcq]
    @mcq = Mcq.new(session[:mcq_params])

    @option_count = session[:mcq_params]['option_count']
    @option_count.times { @mcq.options.build }

    @mcq.quiz_id = params[:quiz_id]
    @mcq.current_step = session[:mcq_step]

    if params[:previous_button]
      @mcq.previous_step
    elsif @mcq.last_step?
      @mcq.save if @mcq.valid?
    else
      @mcq.next_step
    end
    session[:mcq_step] = @mcq.current_step

    if @mcq.new_record?
      render "new"
    else
      session[:mcq_step] = session[:mcq_params] = nil
      redirect_to edit_quiz_path(@mcq.quiz_id)
    end
  end
end
</code></pre>

<p>But each time the second page is shown, the no of fields for options doubles or in case of invalid entry only the field for question_text is shown. How do I show the options correctly? Should I just go with my first solution? I'm new to rails and don't know much about the best practices.</p>

<p>Edited : </p>

<p>new.html.erb</p>

<pre><code>&lt;div class="sub-heading"&gt;Add a Multiple Choice Question:&lt;/div&gt;

&lt;%= render "mcq_#{@mcq.current_step}", quiz: @quiz, mcq: @mcq %&gt;
</code></pre>

<p>_mcq_setup.html.erb</p>

<pre><code>&lt;div class="form-container"&gt;
  &lt;%= form_for [quiz, mcq] do |f| %&gt;

    &lt;div class="form-row"&gt;
      &lt;div class="response-count"&gt;How many options should the question have?&lt;/div&gt;
      &lt;%= f.select(:option_count, (2..5)) %&gt;
    &lt;/div&gt;

    &lt;div class="form-row"&gt;
      &lt;div class="response-count"&gt;How many options can be selected?&lt;/div&gt;

      &lt;div class="option"&gt;
        &lt;%= f.radio_button :multiselect, 'false', checked: true %&gt;
        &lt;%= f.label :multiselect, 'Just One', value: 'false' %&gt;
      &lt;/div&gt;

      &lt;div class="option"&gt;
        &lt;%= f.radio_button :multiselect, 'true' %&gt;
        &lt;%= f.label :multiselect, 'Multiple', value: 'true' %&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="form-row"&gt;
      &lt;div class="response-count"&gt;Is the question required?&lt;/div&gt;

      &lt;div class="option"&gt;
        &lt;%= f.radio_button :required, 'true', checked: true %&gt;
        &lt;%= f.label :required, 'Yes', value: 'true' %&gt;
      &lt;/div&gt;

      &lt;div class="option"&gt;
        &lt;%= f.radio_button :required, 'false' %&gt;
        &lt;%= f.label :required, 'No', value: 'false' %&gt;
      &lt;/div&gt;

    &lt;/div&gt;
    &lt;%= f.submit "Continue to the Next Step" %&gt;
  &lt;% end %&gt;
&lt;/div&gt;
</code></pre>

<p>_mcq_options.html.erb</p>

<pre><code>&lt;%= form_for [quiz, mcq] do |f| %&gt;

  &lt;%= f.label :question_text, 'What is your question?' %&gt;
  &lt;%= f.text_field :question_text %&gt;

  &lt;%= f.fields_for :options do |option_fields| %&gt;
    &lt;%= option_fields.label :option_text, "Option #{option_fields.options[:child_index] + 1}:" %&gt;
    &lt;%= option_fields.text_field :option_text %&gt;
  &lt;% end %&gt;

  &lt;%= f.hidden_field :multiselect %&gt;
  &lt;%= f.hidden_field :required %&gt;

  &lt;%= f.submit "Add Question" %&gt;
  &lt;%= f.submit "Back to previous step", name: 'previous_button' %&gt;
&lt;% end %&gt;
</code></pre>