表单中多个提交的代码改进

时间:2015-11-06 11:02:12

标签: ruby-on-rails

我有一些不可靠的代码,我试图看看我是否可以改进,用户可以回答分配给他们的问题。通过迭代分配给它们的问题来填充答案文本字段,代码如下所示:

<%= form_for answer do |f| %>
  <% current_user.all_weekly_questions.each do |question| %>
    <%= hidden_field_tag 'questions[][id]', question.id %>
      <h4><%= question.name %></h4>
      <% if question.text_based? %>
        <%= text_area_tag 'questions[][reply]', nil, class: 'form-control', "required" => true, rows: 2 %>
      <% else %>
        <% (question.min_number_range..question.max_number_range).each do |question_value| %>
          <%= label_tag "questions[][reply]", class: 'question-value-label' do %>
            <%= question_value %>
            <%= radio_button_tag "questions[][reply]", "#{question_value}" %>
          <% end %>
        <% end %>
      <% end %>
  <% end %>
  <%= f.submit 'Send' %>
<% end %>

现在已经在这里我看到了一些问题,因为我无法真正验证他们是否回答了所有问题,因为我只是迭代它们并填写答案表。

现在,在我的控制器中,我有:

  def new
    @user = User.eager_load(:questions, :group_questions)
                .find(current_user.id)
    @user_answered_weekly_questions =
      current_user.answers.includes(user: [:answers])
      .detect { |a| a.week_number == @current_week.to_s }
      .present?
    @answer = current_user.answers.new
    @current_week = Time.zone.now.strftime('%V')
  end

  def create
    @questions = current_user.questions
    params[:questions].map do |question|
      current_user.answers.create(
        question_id: question[:id],
        reply:       question[:reply],
        week_number: Time.zone.now.strftime('%V')
      )
    end
    redirect_to answers_path
  end

在这里我还发现了一些问题,这些问题在创建中。我确实没有条件来检查它是否已保存。因此它成了问题。

现在我遇到的最后一个问题是,由于我有两个基于数字的问题填写表单的性质,我生成一组radio_button_tag,它有无法说出他们是不同的群体 - 因此用户无法回答这两个问题。

现在我想要改进/修复的是:

  • 表单代码使其更稳定
  • 问题我不能有2个基于数字的问题(单选按钮不知道它们是不同的组,因为它们的值都是questions[][reply]
  • 改善控制器中的创建操作,使其无法运行。
  • 检测用户是否未回答所有问题

架构:

  create_table "questions", force: :cascade do |t|
    t.string   "name"
    t.integer  "company_id"
    t.boolean  "optional",         default: false
    t.boolean  "active",           default: true
    t.datetime "created_at",                       null: false
    t.datetime "updated_at",                       null: false
    t.integer  "frequency",        default: 0
    t.integer  "answer_format",    default: 0
    t.integer  "min_number_range"
    t.integer  "max_number_range"
  end


  create_table "answers", force: :cascade do |t|
    t.integer  "user_id"
    t.text     "reply"
    t.integer  "question_id"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
    t.string   "week_number"
  end

1 个答案:

答案 0 :(得分:0)

关于模型的全部内容。

我首先回顾一下您的域名模型 - 您显然缺少一个将一堆问题组合在一起的组件。

我们还需要在任何Q / A或轮询应用中处理一个非常明显的问题。我们需要将表格和用户反馈的问题和答案分开。

# == Schema Information
#
# Table name: questionnaires
#
#  id          :integer          not null, primary key
#  created_at  :datetime         not null
#  updated_at  :datetime         not null
#  week_number :integer
#
class Questionnaire < ActiveRecord::Base
  has_many :questions
  has_many :answers, through: :questions
  has_many :user_questionnaires, class_name: 'Users::Questionnaire'
  scope :this_week, -> { where(week_number:  Time.zone.now.strftime('%V')) 
end

# == Schema Information
#
# Table name: answers
#
#  id          :integer          not null, primary key
#  question_id :integer
#  body        :string
#  created_at  :datetime         not null
#  updated_at  :datetime         not null
#
class Answer < ActiveRecord::Base
  belongs_to :question
end

# == Schema Information
#
# Table name: questions
#
#  id               :integer          not null, primary key
#  questionnaire_id :integer
#  body             :string
#  created_at       :datetime         not null
#  updated_at       :datetime         not null
#
class Question < ActiveRecord::Base
  belongs_to :questionnaire
  has_many :answers
  accepts_nested_attributes_for :answers
end

这提供了一个非常自然的层次结构 - 问卷调查有许多问题,其中有许多潜在的答案。

这些可以由管理员在管理GUI上创建。例如。

在你的域建模中,你的问答模型中有一些元数据 - 我会仔细考虑这些模型是否真的需要知道的不仅仅是它们的价值/文本以及它们立即属于谁。如果您需要添加数据 - 将其添加到问卷中,这样您就不会在表格中填充重复数据。

我们还没有完成建模。在处理用户回复的问题上,您可能希望为此目的创建单独的模型,而不是膨胀问题/答案模型:

# == Schema Information
#
# Table name: users_answers
#
#  id                     :integer          not null, primary key
#  users_questionnaire_id :integer
#  question_id            :integer
#  answer_id              :integer
#  created_at             :datetime         not null
#  updated_at             :datetime         not null
#
class Users::Reply < ActiveRecord::Base
  belongs_to :users_questionnaire, class_name: 'Users::Questionnaire',
                                   inverse_of: :users_replies
  belongs_to :question
  belongs_to :answer
end

# == Schema Information
#
# Table name: users_questionnaires
#
#  id               :integer          not null, primary key
#  user_id          :integer
#  questionnaire_id :integer
#  created_at       :datetime         not null
#  updated_at       :datetime         not null
#
class Users::Questionnaire < ActiveRecord::Base
  belongs_to :user
  belongs_to :questionnaire
  has_many :users_replies, class_name: 'Users::Reply'
  delegate :questions, to: :questionnaire
  accepts_nested_attributes_for :users_replies
end

使用模型:

class Users::QuestionnairesController < ApplicationController

  def index
    @questionnaires = current_user.questionnaires
  end

  def new
    @questionnaire = current_user.questionnaires.new(
      # We need to tell ruby to use the "root" namespace
      questionnaire: ::Questionnaire.this_week.first!
    )
    # @todo - handle case where no weekly questionnaire is found
  end

  def create
    @questionnaire = current_user.questionnaires.new(create_params)
    if @questionnaire.save
      redirect_to :index
    else
      render :new
    end
  end

  private

    def create_params
      params.require(:questionnaire)
            .permit(
                :questionnaire_id,
                users_replies: [:question_id, :answer_id]
            )
    end

end

控制器非常简单 - 我们在Users::Questionaire内存中创建#new,并接受用户输入并在Users::Questionaire中保留#create和相关模型。

我们可以使用fields_for

大大简化表单
<%= form_for @questionnaire do |f| %>

  <% f.object.questions.each do |q| %>
    <fieldset>
      <%= f.fields_for :users_answers, Users::Answer.new(question_id: q.id) do |a| %>
        <%= a.hidden_field(:question_id) %>
        <%= a.collection_radio_buttons(:answer_id, q.answers, :id, :body) %>
      <% end %>
      <legend><%= q.body %></legend>
    </fieldset>
  <% end %>

  <%= f.submit %>
<% end %>

验证怎么样?

class Users::Questionnaire < ActiveRecord::Base
  belongs_to :user
  belongs_to :questionnaire
  has_many :users_answers, class_name: 'Users::Answer'
  delegate :questions, to: :questionnaire
  accepts_nested_attributes_for :users_answers

  validate :number_of_answers

  def number_of_answers
    unless users_answers.size == questions.size
      errors[:users_answers] << "You have not answered all the questions"
    end
  end
end

这是验证用户输入的简化版本。要创建更复杂的验证,例如,如果您想为每个缺失的答案添加错误或确保答案有效,最好的方法是创建custom validator class。只是不要陷入在控制器中做所有事情的陷阱。

备注

  • 如果您总是处理简单的数字答案(“将此X从1到10评级”),您可以通过省略答案模型/关系来简化。

我忘了提到您需要在用户和Users::Questionnaire

之间添加关系
class User < ActiveRecord::Base
  # ...
  has_many :questionnaires, class_name: 'Users::Questionnaire'
end

设置路线非常简单:

namespace :users
  resources :questionnaires, only: [:new, :create]
end

为控制器使用命名空间是可选的,它实际上取决于您想要的应用程序路径。