title: Rails Multi-step form
body: <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>