在 rails 中创建嵌套对象的复选框
Check box that creates nested objects in rails
我正在构建一个创建考试的应用程序。对于用户选择考试答案的部分,我想使用复选框(或单选按钮)让他们选择答案。
我希望所有用户选择的答案都是 table 本身,称为 "responses"。我不知道如何使用单选按钮创建记录。
所有响应记录需要做的就是获取考试、用户和分数的 ID。 Score 是一个 table ,用于跟踪用户的分数和正确答案的数量。
这是我的考试模型(rails 不让我使用 "exam" 这个词)。我为嵌套属性设置了它。
class Examination < ApplicationRecord
belongs_to :user
has_many :questions, dependent: :destroy
has_many :scores
has_many :responses
has_secure_password
accepts_nested_attributes_for :responses, allow_destroy: true
end
响应模型非常基础:
class Response < ApplicationRecord
belongs_to :user
belongs_to :score
belongs_to :examination
end
这是 "take an exam" 页面:
<%= link_to "Back to all exams", examinations_path %>
<h2><%= @exam.name %></h2>
<h3><%= @exam.intro %></h3>
<%= form_for @exam do |f| %>
<%= f.hidden_field :name, value: @exam.name %>
<%= fields_for :responses do |res_f| %>
<% @exam.questions.each_with_index do |question, i| %>
<% index = i + 1 %>
<h2>Question #<%=index%></h2><span style="font-size: 24px; font-weight: normal">(<%= question.points %> Points)</span>
<hr>
<h3><%= question.body %></h3>
<% question.answers.each do |ans| %>
<table>
<tr>
<td><%= res_f.check_box :answer_id , ans.id, :examination_id , @exam.id, :user_id %></td>
<td><%= ans.body %></td>
</tr>
</table>
<% end %>
<% end %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
此代码不 运行 因为 Rails 期望响应记录存在才能使用表单。它抛出这个错误:
undefined method `merge' for 484:Integer
如果我将复选框代码调整为:
<%= res_f.check_box :answer_id %>
代码将 运行 并在提交时给我以下参数:
Started PATCH "/examinations/34" for 127.0.0.1 at 2018-02-24 16:22:41 -0800
Processing by ExaminationsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"y4vcPByUKnDdM6NsWDhwxh8MxJLZU4TQo+/fUrmKYEfb3qLn5FVieJAYirNRaSl0w5hJax20w5Ycs/wz1bMEKw==", "examination"=>{"name"=>"Samuel Smith’s Oatmeal Stout"}, "responses"=>{"answer_id"=>"1"}, "commit"=>"Submit", "id"=>"34"}
我知道这不对,但我希望它至少能创造一个记录。所有复选框都必须创建响应记录。它应该能够获取 answer_id、exam_id 和 user_id。就是这样。
有人知道怎么做吗?
编辑以回应 Pablo 7:
这是其他模型(它们现在非常基本)
class Score < ApplicationRecord
belongs_to :user
belongs_to :examination
has_many :responses, dependent: :destroy
end
class User < ApplicationRecord
has_many :examinations, dependent: :destroy
has_many :scores, dependent: :destroy
has_many :responses, dependent: :destroy
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
class Question < ApplicationRecord
belongs_to :examination
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers, allow_destroy: true
validates_presence_of :body
validates_presence_of :question_type
end
@exam和Examination是一样的。 Examination 控制器中有一个 "take" 操作允许用户参加考试:
def take
@exam = Examination.find(params[:id])
@score = @exam.scores.build
@score.user_id = current_user.id
@score.save
end
所以考试属于创建它的用户。同一个用户或不同的用户可以使用 take 操作参加考试。然后他们就会有一个属于他们的分数。
我认为你必须对你的模型做一些修改:
一个响应应该属于一个问题(这是用户正在响应的问题)。
一个响应应该属于一个答案(它是问题的正确答案;用户检查的那个)。如果你想允许多个正确答案,这个应该改变。
回复不应属于检查,也不应属于用户。事实上,一个 Response 属于一个 Score 就足够了,因为 Score 已经属于一个 Examination 和一个 User。
一个考试不应该有很多答案。事实上,一次考试有很多分数,分数有很多答案。如果需要,可以使用 has_many :responses, through: :scores
一个用户不应该有很多回复。他们有很多分数,分数有很多回应。如果需要,可以使用 has_many :responses, through: :scores
创建新分数(in take)时,您应该为考试中的每个问题创建空答案:
def take
@exam = Examination.find(params[:id])
@score = @exam.scores.build(user_id: current_user.id)
@exam.questions.each { |question| @score.responses.build(question_id: question.id) }
#I don't think you should save here.
#This method is like the new method
#You should save when the score is submitted
#@score.save
end
在您的表单中:
我会将表格更改为分数模型(不是考试)。如果您使用嵌套路由,则可以是 [@exam, @score]
这可能有很多错误,因为我现在无法测试它。我希望思路清晰:
<%= form_for @score do |f| %>
<%= f.hidden_field :name, value: @score.examination.name %>
<% @score.responses.each_with_index do |response, i| %>
<%= f.fields_for response do |res_f| %>
<% index = i + 1 %>
<h2>Question #<%= index %></h2>
<span style="font-size: 24px; font-weight: normal">
(<%= response.question.points %> Points)
</span>
<hr>
<h3><%= response.question.body %></h3>
<%= res_f.collection_radio_buttons :answer_id, response.question.answers, :id, :body %>
<% end %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
提交应该调用 Score 模型中的方法来创建 Score (ScoresController.create)
谢谢 Pablo,我终于让它工作了。你的代码不太管用,但它让我走上了正确的道路。我按照你的建议改变了模型关联。这确实更有意义。
这是我的模型:
class Answer < ApplicationRecord
belongs_to :question
has_many :responses, dependent: :destroy
end
class Examination < ApplicationRecord
belongs_to :user
has_many :questions, dependent: :destroy
has_many :answers, :through => :questions
has_many :scores
has_secure_password
end
class Question < ApplicationRecord
belongs_to :examination
has_many :answers, dependent: :destroy
has_many :responses
accepts_nested_attributes_for :answers, allow_destroy: true, :reject_if => :all_blank
validates_presence_of :body
validates_presence_of :question_type
end
class Response < ApplicationRecord
belongs_to :score
belongs_to :answer
belongs_to :question
end
class Score < ApplicationRecord
belongs_to :user
belongs_to :examination
has_many :responses, dependent: :destroy
accepts_nested_attributes_for :responses, allow_destroy: true, reject_if: :no_answer_id?
private
def no_answer_id?(att)
att['answer_id'].blank?
end
end
我不得不将该特殊方法添加到评分模型中以解决未经检查的响应。否则,它会抛出错误。
我将 "take a test" 逻辑和视图移至分数控制器。使用您的代码,我得到了一个双循环(多次列出的问题)。我了解到您实际上可以使用 "res_f.object" 通过 "form_for" 表单循环访问响应。这很酷。
我还必须在单选按钮集合表单上添加一个隐藏字段以获取问题 ID。
这里是:
<%= link_to "Back to all exams", examinations_path %><br/>
<h2><%= @exam.name %></h2>
<h3><%= @exam.intro %></h3>
<%= form_for [@exam, @score] do |f| %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.fields_for :responses do |res_f| %>
<h2>Question # <%= res_f.object.question.position %></h2>
<span style="font-size: 24px; font-weight: normal">
(<%= res_f.object.question.points %> Points)
</span>
<hr>
<h3><%= res_f.object.question.body %></h3>
<p><%= res_f.collection_radio_buttons :answer_id, res_f.object.question.answers, :id, :body do |b| %></p>
<div>
<%= b.radio_button %>
<%= b.label %>
<%= res_f.hidden_field :question_id, value: res_f.object.question.id %>
</div>
<% end %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
分数控制器:
class ScoresController < ApplicationController
def new
@exam = Examination.find(params[:examination_id])
@score = @exam.scores.build(user_id: current_user.id)
@exam.questions.each do |question|
res = @score.responses.build(question_id: question.id)
logger.info question.id
logger.info res
end
end
def create
@exam = Examination.find(params[:examination_id])
@score = @exam.scores.build(score_params)
if @score.save
redirect_to examination_path(@exam)
else
logger.info @score.errors.full_messages
redirect_to root_path
end
end
protected
def score_params
params.require(:score).permit(:examination_id, :user_id,
responses_attributes: [:id, :answer_id, :question_id, :selected])
end
end
如果只有一个正确答案,这一切都很好。稍后我将不得不修改它以解释多个答案。至少它有效!我会把功劳归于巴勃罗。
干杯
我正在构建一个创建考试的应用程序。对于用户选择考试答案的部分,我想使用复选框(或单选按钮)让他们选择答案。
我希望所有用户选择的答案都是 table 本身,称为 "responses"。我不知道如何使用单选按钮创建记录。
所有响应记录需要做的就是获取考试、用户和分数的 ID。 Score 是一个 table ,用于跟踪用户的分数和正确答案的数量。 这是我的考试模型(rails 不让我使用 "exam" 这个词)。我为嵌套属性设置了它。
class Examination < ApplicationRecord
belongs_to :user
has_many :questions, dependent: :destroy
has_many :scores
has_many :responses
has_secure_password
accepts_nested_attributes_for :responses, allow_destroy: true
end
响应模型非常基础:
class Response < ApplicationRecord
belongs_to :user
belongs_to :score
belongs_to :examination
end
这是 "take an exam" 页面:
<%= link_to "Back to all exams", examinations_path %>
<h2><%= @exam.name %></h2>
<h3><%= @exam.intro %></h3>
<%= form_for @exam do |f| %>
<%= f.hidden_field :name, value: @exam.name %>
<%= fields_for :responses do |res_f| %>
<% @exam.questions.each_with_index do |question, i| %>
<% index = i + 1 %>
<h2>Question #<%=index%></h2><span style="font-size: 24px; font-weight: normal">(<%= question.points %> Points)</span>
<hr>
<h3><%= question.body %></h3>
<% question.answers.each do |ans| %>
<table>
<tr>
<td><%= res_f.check_box :answer_id , ans.id, :examination_id , @exam.id, :user_id %></td>
<td><%= ans.body %></td>
</tr>
</table>
<% end %>
<% end %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
此代码不 运行 因为 Rails 期望响应记录存在才能使用表单。它抛出这个错误:
undefined method `merge' for 484:Integer
如果我将复选框代码调整为:
<%= res_f.check_box :answer_id %>
代码将 运行 并在提交时给我以下参数:
Started PATCH "/examinations/34" for 127.0.0.1 at 2018-02-24 16:22:41 -0800
Processing by ExaminationsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"y4vcPByUKnDdM6NsWDhwxh8MxJLZU4TQo+/fUrmKYEfb3qLn5FVieJAYirNRaSl0w5hJax20w5Ycs/wz1bMEKw==", "examination"=>{"name"=>"Samuel Smith’s Oatmeal Stout"}, "responses"=>{"answer_id"=>"1"}, "commit"=>"Submit", "id"=>"34"}
我知道这不对,但我希望它至少能创造一个记录。所有复选框都必须创建响应记录。它应该能够获取 answer_id、exam_id 和 user_id。就是这样。
有人知道怎么做吗?
编辑以回应 Pablo 7: 这是其他模型(它们现在非常基本)
class Score < ApplicationRecord
belongs_to :user
belongs_to :examination
has_many :responses, dependent: :destroy
end
class User < ApplicationRecord
has_many :examinations, dependent: :destroy
has_many :scores, dependent: :destroy
has_many :responses, dependent: :destroy
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
class Question < ApplicationRecord
belongs_to :examination
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers, allow_destroy: true
validates_presence_of :body
validates_presence_of :question_type
end
@exam和Examination是一样的。 Examination 控制器中有一个 "take" 操作允许用户参加考试:
def take
@exam = Examination.find(params[:id])
@score = @exam.scores.build
@score.user_id = current_user.id
@score.save
end
所以考试属于创建它的用户。同一个用户或不同的用户可以使用 take 操作参加考试。然后他们就会有一个属于他们的分数。
我认为你必须对你的模型做一些修改:
一个响应应该属于一个问题(这是用户正在响应的问题)。
一个响应应该属于一个答案(它是问题的正确答案;用户检查的那个)。如果你想允许多个正确答案,这个应该改变。
回复不应属于检查,也不应属于用户。事实上,一个 Response 属于一个 Score 就足够了,因为 Score 已经属于一个 Examination 和一个 User。
一个考试不应该有很多答案。事实上,一次考试有很多分数,分数有很多答案。如果需要,可以使用
has_many :responses, through: :scores
一个用户不应该有很多回复。他们有很多分数,分数有很多回应。如果需要,可以使用
has_many :responses, through: :scores
创建新分数(in take)时,您应该为考试中的每个问题创建空答案:
def take
@exam = Examination.find(params[:id])
@score = @exam.scores.build(user_id: current_user.id)
@exam.questions.each { |question| @score.responses.build(question_id: question.id) }
#I don't think you should save here.
#This method is like the new method
#You should save when the score is submitted
#@score.save
end
在您的表单中: 我会将表格更改为分数模型(不是考试)。如果您使用嵌套路由,则可以是 [@exam, @score]
这可能有很多错误,因为我现在无法测试它。我希望思路清晰:
<%= form_for @score do |f| %>
<%= f.hidden_field :name, value: @score.examination.name %>
<% @score.responses.each_with_index do |response, i| %>
<%= f.fields_for response do |res_f| %>
<% index = i + 1 %>
<h2>Question #<%= index %></h2>
<span style="font-size: 24px; font-weight: normal">
(<%= response.question.points %> Points)
</span>
<hr>
<h3><%= response.question.body %></h3>
<%= res_f.collection_radio_buttons :answer_id, response.question.answers, :id, :body %>
<% end %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
提交应该调用 Score 模型中的方法来创建 Score (ScoresController.create)
谢谢 Pablo,我终于让它工作了。你的代码不太管用,但它让我走上了正确的道路。我按照你的建议改变了模型关联。这确实更有意义。
这是我的模型:
class Answer < ApplicationRecord
belongs_to :question
has_many :responses, dependent: :destroy
end
class Examination < ApplicationRecord
belongs_to :user
has_many :questions, dependent: :destroy
has_many :answers, :through => :questions
has_many :scores
has_secure_password
end
class Question < ApplicationRecord
belongs_to :examination
has_many :answers, dependent: :destroy
has_many :responses
accepts_nested_attributes_for :answers, allow_destroy: true, :reject_if => :all_blank
validates_presence_of :body
validates_presence_of :question_type
end
class Response < ApplicationRecord
belongs_to :score
belongs_to :answer
belongs_to :question
end
class Score < ApplicationRecord
belongs_to :user
belongs_to :examination
has_many :responses, dependent: :destroy
accepts_nested_attributes_for :responses, allow_destroy: true, reject_if: :no_answer_id?
private
def no_answer_id?(att)
att['answer_id'].blank?
end
end
我不得不将该特殊方法添加到评分模型中以解决未经检查的响应。否则,它会抛出错误。
我将 "take a test" 逻辑和视图移至分数控制器。使用您的代码,我得到了一个双循环(多次列出的问题)。我了解到您实际上可以使用 "res_f.object" 通过 "form_for" 表单循环访问响应。这很酷。
我还必须在单选按钮集合表单上添加一个隐藏字段以获取问题 ID。
这里是:
<%= link_to "Back to all exams", examinations_path %><br/>
<h2><%= @exam.name %></h2>
<h3><%= @exam.intro %></h3>
<%= form_for [@exam, @score] do |f| %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.fields_for :responses do |res_f| %>
<h2>Question # <%= res_f.object.question.position %></h2>
<span style="font-size: 24px; font-weight: normal">
(<%= res_f.object.question.points %> Points)
</span>
<hr>
<h3><%= res_f.object.question.body %></h3>
<p><%= res_f.collection_radio_buttons :answer_id, res_f.object.question.answers, :id, :body do |b| %></p>
<div>
<%= b.radio_button %>
<%= b.label %>
<%= res_f.hidden_field :question_id, value: res_f.object.question.id %>
</div>
<% end %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
分数控制器:
class ScoresController < ApplicationController
def new
@exam = Examination.find(params[:examination_id])
@score = @exam.scores.build(user_id: current_user.id)
@exam.questions.each do |question|
res = @score.responses.build(question_id: question.id)
logger.info question.id
logger.info res
end
end
def create
@exam = Examination.find(params[:examination_id])
@score = @exam.scores.build(score_params)
if @score.save
redirect_to examination_path(@exam)
else
logger.info @score.errors.full_messages
redirect_to root_path
end
end
protected
def score_params
params.require(:score).permit(:examination_id, :user_id,
responses_attributes: [:id, :answer_id, :question_id, :selected])
end
end
如果只有一个正确答案,这一切都很好。稍后我将不得不修改它以解释多个答案。至少它有效!我会把功劳归于巴勃罗。
干杯