Rails多步骤表单

时间:2016-10-16 14:07:03

标签: ruby-on-rails forms

我正在用rails 5编写一个测验应用程序。我有一个用于构建问题的多步骤表单。

型号:

class Mcq < 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 < ApplicationRecord
  belongs_to :mcq, optional: true
  validates :option_text, presence: true
end

架构:

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

第一页用于问题设置,并包含以下字段:

  1. 选项计数
  2. 必填(是/否)
  3. 没有可以选择的选项(单个/多个)
  4. 第二页用于选项,并包含以下字段:

    1. 问题文本
    2. 选项的嵌套表单
    3. 控制器:

      class McqsController < 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
      

      上述解决方案有效,但代码混乱且难以理解。我遇到了这个railscasts episode,它以更清洁的方式做了类似的事情。我更新了我的代码如下:

      class Mcq < 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 < 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
      

      但是每次显示第二页时,选项的字段数加倍,或者在无效输入的情况下,仅显示question_text的字段。如何正确显示选项?我应该选择第一个解决方案吗?我是rails的新手,对最佳实践知之甚少。

      编辑:

      new.html.erb

      <div class="sub-heading">Add a Multiple Choice Question:</div>
      
      <%= render "mcq_#{@mcq.current_step}", quiz: @quiz, mcq: @mcq %>
      

      _mcq_setup.html.erb

      <div class="form-container">
        <%= form_for [quiz, mcq] do |f| %>
      
          <div class="form-row">
            <div class="response-count">How many options should the question have?</div>
            <%= f.select(:option_count, (2..5)) %>
          </div>
      
          <div class="form-row">
            <div class="response-count">How many options can be selected?</div>
      
            <div class="option">
              <%= f.radio_button :multiselect, 'false', checked: true %>
              <%= f.label :multiselect, 'Just One', value: 'false' %>
            </div>
      
            <div class="option">
              <%= f.radio_button :multiselect, 'true' %>
              <%= f.label :multiselect, 'Multiple', value: 'true' %>
            </div>
          </div>
      
          <div class="form-row">
            <div class="response-count">Is the question required?</div>
      
            <div class="option">
              <%= f.radio_button :required, 'true', checked: true %>
              <%= f.label :required, 'Yes', value: 'true' %>
            </div>
      
            <div class="option">
              <%= f.radio_button :required, 'false' %>
              <%= f.label :required, 'No', value: 'false' %>
            </div>
      
          </div>
          <%= f.submit "Continue to the Next Step" %>
        <% end %>
      </div>
      

      _mcq_options.html.erb

      <%= form_for [quiz, mcq] do |f| %>
      
        <%= f.label :question_text, 'What is your question?' %>
        <%= f.text_field :question_text %>
      
        <%= f.fields_for :options do |option_fields| %>
          <%= option_fields.label :option_text, "Option #{option_fields.options[:child_index] + 1}:" %>
          <%= option_fields.text_field :option_text %>
        <% end %>
      
        <%= f.hidden_field :multiselect %>
        <%= f.hidden_field :required %>
      
        <%= f.submit "Add Question" %>
        <%= f.submit "Back to previous step", name: 'previous_button' %>
      <% end %>
      

1 个答案:

答案 0 :(得分:0)

您可以查看state_machine的方向。通过使用它,您可以将您的步骤用作状态机的状态,并使用其定义仅对给定状态有效的验证的能力(在state :first_gear, :second_gear do中查看here),以便第二步所需的字段不会首先需要。此外,它还允许您避免对当前步骤进行复杂检查(因为状态将保留在模型中),并且将来很容易扩展,并且将来会有更多步骤。