diff --git a/.rubocop.yml b/.rubocop.yml index bede808..2ad2401 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -41,7 +41,7 @@ Style/StringLiterals: Metrics/AbcSize: Exclude: - db/migrate/**/* - Max: 18 + Max: 20 Metrics/LineLength: Max: 110 diff --git a/app/controllers/candidate_controller.rb b/app/controllers/candidate_controller.rb index 0daca3d..051b1ae 100644 --- a/app/controllers/candidate_controller.rb +++ b/app/controllers/candidate_controller.rb @@ -13,8 +13,7 @@ class CandidateController < ApplicationController end def question - @status = QuizStatus.new(current_candidate) - qid = @status.current_question_id + qid = prep_status.current_question_id redirect_to :summary and return if qid.nil? @@ -29,9 +28,8 @@ class CandidateController < ApplicationController def summary @quiz = current_candidate.my_quiz - @status = QuizStatus.new(current_candidate) - redirect_to :question and return unless @status.current_question_id.nil? + redirect_to :question and return unless prep_status.current_question_id.nil? end def update_text @@ -39,7 +37,7 @@ class CandidateController < ApplicationController @answer.update(answer: answer_params[:text], saved: answer_params[:save], submitted: answer_params[:next]) - validate_answer + route_answer end def update_radio @@ -47,7 +45,7 @@ class CandidateController < ApplicationController @answer.update(answer: answer_params[:radio], saved: answer_params[:save], submitted: answer_params[:next]) - validate_answer + route_answer end def update_checkbox @@ -55,7 +53,7 @@ class CandidateController < ApplicationController @answer.update(answer: answer_params[:checkbox], saved: answer_params[:save], submitted: answer_params[:next]) - validate_answer + route_answer end def update_live_code @@ -63,7 +61,7 @@ class CandidateController < ApplicationController @answer.update(answer: answer_params[:live_code], saved: answer_params[:save], submitted: answer_params[:next]) - validate_answer + route_answer end # TODO @@ -86,6 +84,10 @@ class CandidateController < ApplicationController @question = current_candidate.fetch_question(qid) end + def prep_status + @status ||= QuizStatus.new(current_candidate) + end + def answer_params params.require(:answer).permit( :question_id, :answer_id, @@ -100,10 +102,11 @@ class CandidateController < ApplicationController answer end - def validate_answer + def route_answer if @answer.errors.present? - flash[:error] = [answer_params[:question_id]] + prep_status prep_question answer_params[:question_id] + flash[:answer_error] = answer_params[:question_id].to_i render :question else # TODO: change params.key? to submit = save/next/summary diff --git a/app/models/answer.rb b/app/models/answer.rb index 1d6ebf2..b1dc993 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -3,4 +3,8 @@ class Answer < ApplicationRecord belongs_to :question belongs_to :candidate + + validates :candidate_id, presence: true + validates :question_id, presence: true + validates :answer, answer_format: true end diff --git a/app/validators/answer_format_validator.rb b/app/validators/answer_format_validator.rb new file mode 100644 index 0000000..32f0a3f --- /dev/null +++ b/app/validators/answer_format_validator.rb @@ -0,0 +1,38 @@ +class AnswerFormatValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + send(record.question.input_type, record, attribute, value) + end + + private + + def text record, attribute, value + return unless value.length.between? 1, 1000 + + if value.blank? + record.errors[attribute] << (options[:message] || "Please enter an answer.") + end + + if value.length > 1000 + record.errors[attribute] << (options[:message] || "The character limit for a textarea answer is 1000") + end + end + + def radio record, attribute, value + return unless value.nil? + + record.errors[attribute] << (options[:message] || "Please select an answer.") + end + + def checkbox record, attribute, value + return unless value.nil? || value.join.blank? + + record.errors[attribute] << (options[:message] || "Please select an answer.") + end + + def live_code record, attribute, value + return unless value.nil? + + msg = "You must write code in one of the above textareas to progress." + record.errors[attribute] << (options[:message] || msg) + end +end diff --git a/app/views/candidate/_answer_errors.html.erb b/app/views/candidate/_answer_errors.html.erb new file mode 100644 index 0000000..b69a5f1 --- /dev/null +++ b/app/views/candidate/_answer_errors.html.erb @@ -0,0 +1,5 @@ +<% if flash[:answer_error] == question.question_id %> + <% answer.errors.messages[:answer].each do |message| %> +
<%= message %>
+ <% end %> +<% end %> diff --git a/app/views/candidate/_checkbox.html.erb b/app/views/candidate/_checkbox.html.erb index ca06c9b..ad86f2a 100644 --- a/app/views/candidate/_checkbox.html.erb +++ b/app/views/candidate/_checkbox.html.erb @@ -14,6 +14,4 @@ <% end %> -<% if flash[:error].try(:include?, question.to_i) %> -
Please select an answer.
-<% end %> +<%= render partial: "candidate/answer_errors", locals: {question: question, answer: @answer} %> diff --git a/app/views/candidate/_radio.html.erb b/app/views/candidate/_radio.html.erb index 38a8bbd..3d1cd7d 100644 --- a/app/views/candidate/_radio.html.erb +++ b/app/views/candidate/_radio.html.erb @@ -9,6 +9,4 @@ <% end %> -<% if flash[:error].try(:include?, question.to_i) %> -
Please select an answer.
-<% end %> +<%= render partial: "candidate/answer_errors", locals: {question: question, answer: @answer} %> diff --git a/app/views/candidate/_text.html.erb b/app/views/candidate/_text.html.erb index db29ee0..d910317 100644 --- a/app/views/candidate/_text.html.erb +++ b/app/views/candidate/_text.html.erb @@ -3,6 +3,4 @@
Characters remaining:
-<% if flash[:error].try(:include?, question.to_i) %> -
Please select or enter an answer. (The character limit for a textarea answer is 1000)
-<% end %> +<%= render partial: "candidate/answer_errors", locals: {question: question, answer: @answer} %> diff --git a/app/views/candidate/live_code.html.erb b/app/views/candidate/live_coder.html.erb similarity index 80% rename from app/views/candidate/live_code.html.erb rename to app/views/candidate/live_coder.html.erb index 331e3e0..4a5c2bb 100644 --- a/app/views/candidate/live_code.html.erb +++ b/app/views/candidate/live_coder.html.erb @@ -13,8 +13,6 @@ -<% if flash[:error].try(:include?, @question.to_i) %> -
You must write code in one of the above textareas to progress.
-<% end %> +<%= render partial: "candidate/answer_errors", locals: {question: question, answer: @answer} %>
diff --git a/config/initializers/custom_error_wrapper.rb b/config/initializers/custom_error_wrapper.rb new file mode 100644 index 0000000..67cc908 --- /dev/null +++ b/config/initializers/custom_error_wrapper.rb @@ -0,0 +1,5 @@ +# TODO: needs better wrapping instead of nuking +# https://rubyplus.com/articles/3401 +ActionView::Base.field_error_proc = proc do |html_tag, _instance| + html_tag.html_safe +end diff --git a/test/controllers/candidate_controller_test.rb b/test/controllers/candidate_controller_test.rb index aab1e34..3df9b82 100644 --- a/test/controllers/candidate_controller_test.rb +++ b/test/controllers/candidate_controller_test.rb @@ -50,5 +50,13 @@ class CandidateControllerTest < ActionDispatch::IntegrationTest assert_response :success end - # should get flash message on bad question + test "should get flash message on bad radio response" do + setup_auth candidates(:martha) + qid = questions(:fed1).id + post post_radio_url, params: { answer: { question_id: qid, radio: nil } } + + assert_response :success + assert session[:test_id].present? + assert_equal qid, flash[:answer_error] + end end