-
Notifications
You must be signed in to change notification settings - Fork 118
Expand file tree
/
Copy pathanswers_controller.rb
More file actions
208 lines (184 loc) · 7.52 KB
/
answers_controller.rb
File metadata and controls
208 lines (184 loc) · 7.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# frozen_string_literal: true
# Controller that handles Answers to DMP questions
class AnswersController < ApplicationController
respond_to :html
include ConditionsHelper
# POST /answers/create_or_update
# TODO: Why!? This method is overly complex. Needs a serious refactor!
# We should break apart into separate create/update actions to simplify
# logic and we should stop using custom JSON here and instead use
# `remote: true` in the <form> tag and just send back the ERB.
# Consider using ActionCable for the progress bar(s)
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def create_or_update
p_params = permitted_params
# First it is checked plan exists and question exist for that plan
begin
p = Plan.find(p_params[:plan_id])
unless p.question_exists?(p_params[:question_id])
render(status: :not_found, json: {
msg: format(_('There is no question with id %{question_id} associated to ' \
'plan id %{plan_id} for which to create or update an answer'),
question_id: p_params[:question_id], plan_id: p_params[:plan_id])
})
return
end
rescue ActiveRecord::RecordNotFound
render(status: :not_found, json: {
msg: format(_('There is no plan with id %{id} for which to create or update an answer'),
id: p_params[:plan_id])
})
return
end
q = Question.find(p_params[:question_id])
# Execute transaction block to create, update, or handle stale answer object
handle_answer_transaction(p_params, q)
# TODO: Seems really strange to do this check. If it's true it returns a
# 200 with an empty body. We should update to send back some JSON. The
# check should probably happen on create/update
return unless @answer.present?
@plan = fetch_plan_with_associations(p_params[:plan_id])
@question = @answer.question
@section = @plan.sections.find_by(id: @question.section_id)
template = @section.phase.template
all_question_ids = @plan.questions.pluck(:id)
# Destroy all answers for removed questions
# - remove_list(@plan) returns a list of question to be removed from the plan based on any conditional questions.
Answer.where(question_id: remove_list(@plan), plan: @plan).destroy_all
# Now update @plan after removing answers of questions removed from the plan.
@plan = fetch_plan_with_associations(p_params[:plan_id])
# Now get list of question ids to remove based on remaining answers.
remove_list_question_ids = remove_list(@plan)
qn_data = {
to_show: all_question_ids - remove_list_question_ids,
to_hide: remove_list_question_ids
}
section_data = []
@plan.sections.each do |section|
next if section.number < @section.number
this_section_info = {
sec_id: section.id,
no_qns: num_section_questions(@plan, section),
no_ans: num_section_answers(@plan, section)
}
section_data << this_section_info
end
send_webhooks(current_user, @answer)
render json: {
qn_data: qn_data,
section_data: section_data,
'question' => {
'id' => @question.id,
'answer_lock_version' => @answer.lock_version,
'locking' => if @stale_answer
render_to_string(partial: 'answers/locking', locals: {
question: @question,
answer: @stale_answer,
user: @answer.user
}, formats: [:html])
end,
'form' => render_to_string(partial: 'answers/new_edit', locals: {
template: template,
question: @question,
answer: @answer,
readonly: false,
locking: false,
base_template_org: template.base_org
}, formats: [:html]),
'answer_status' => render_to_string(partial: 'answers/status', locals: {
answer: @answer
}, formats: [:html])
},
'plan' => {
'id' => @plan.id,
'progress' => render_to_string(partial: 'plans/progress', locals: {
plan: @plan,
current_phase: @section.phase
}, formats: [:html])
}
}.to_json
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
private
def handle_answer_transaction(p_params, question)
Answer.transaction do
args = p_params
# Answer model does not understand :standards so remove it from the params
standards = args[:standards]
args.delete(:standards)
@answer = Answer.find_by!(
plan_id: args[:plan_id],
question_id: args[:question_id]
)
update_answer(args, question, standards)
rescue ActiveRecord::RecordNotFound
create_answer(args, question, standards)
rescue ActiveRecord::StaleObjectError
handle_stale_answer_error(args)
end
end
def create_answer(args, question, standards)
@answer = Answer.new(args.merge(user_id: current_user.id))
@answer.lock_version = 1
authorize @answer
if question.question_format.rda_metadata?
@answer.update_answer_hash(
JSON.parse(standards.to_json), args[:text]
)
end
@answer.save!
end
def update_answer(args, question, standards)
authorize @answer
@answer.update(args.merge(user_id: current_user.id))
if args[:question_option_ids].present?
# Saves the record with the updated_at set to the current time.
# Needed if only answer.question_options is updated
@answer.touch
end
return unless question.question_format.rda_metadata?
@answer.update_answer_hash(
JSON.parse(standards.to_json), args[:text]
)
@answer.save!
end
def handle_stale_answer_error(args)
@stale_answer = @answer
@answer = Answer.find_by(
plan_id: args[:plan_id],
question_id: args[:question_id]
)
end
# rubocop:disable Metrics/AbcSize
def permitted_params
permitted = params.require(:answer)
.permit(:id, :text, :plan_id, :user_id, :question_id,
:lock_version, question_option_ids: [], standards: {})
# If question_option_ids has been filtered out because it was a
# scalar value (e.g. radiobutton answer)
if !params[:answer][:question_option_ids].nil? &&
!permitted[:question_option_ids].present?
permitted[:question_option_ids] = [params[:answer][:question_option_ids]]
end
permitted.delete(:id) unless permitted[:id].present?
# If no question options has been chosen.
permitted[:question_option_ids] = [] if params[:answer][:question_option_ids].nil?
permitted
end
# rubocop:enable Metrics/AbcSize
def fetch_plan_with_associations(plan_id)
Plan.includes(
sections: {
questions: %i[
answers
question_format
]
}
).find(plan_id)
end
def check_answered(section, q_array, all_answers)
n_qs = section.questions.count { |question| q_array.include?(question.id) }
n_ans = all_answers.count { |ans| q_array.include?(ans.question.id) and ans.answered? }
[n_qs, n_ans]
end
end