我正在用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
第一页用于问题设置,并包含以下字段:
第二页用于选项,并包含以下字段:
控制器:
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 %>
答案 0 :(得分:0)
您可以查看state_machine的方向。通过使用它,您可以将您的步骤用作状态机的状态,并使用其定义仅对给定状态有效的验证的能力(在state :first_gear, :second_gear do
中查看here),以便第二步所需的字段不会首先需要。此外,它还允许您避免对当前步骤进行复杂检查(因为状态将保留在模型中),并且将来很容易扩展,并且将来会有更多步骤。