decidim-forms 0.19.0 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/config/admin/decidim_forms_manifest.js +1 -0
- data/app/assets/images/decidim/surveys/icon.svg +1 -19
- data/app/assets/javascripts/decidim/forms/admin/collapsible_questions.js.es6 +13 -0
- data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +95 -7
- data/app/assets/javascripts/decidim/forms/admin/live_text_update.component.js.es6 +52 -0
- data/app/assets/javascripts/decidim/forms/forms.js.es6 +42 -1
- data/app/assets/javascripts/decidim/forms/max_choices_alert.component.js.es6 +44 -0
- data/app/assets/stylesheets/decidim/forms/forms.scss +39 -0
- data/app/cells/decidim/forms/matrix_readonly/show.erb +5 -0
- data/app/cells/decidim/forms/matrix_readonly_cell.rb +12 -0
- data/app/cells/decidim/forms/question_readonly/show.erb +5 -1
- data/app/cells/decidim/forms/question_readonly_cell.rb +5 -0
- data/app/cells/decidim/forms/step_navigation/show.erb +35 -0
- data/app/cells/decidim/forms/step_navigation_cell.rb +46 -0
- data/app/commands/decidim/forms/admin/update_questionnaire.rb +8 -0
- data/app/commands/decidim/forms/answer_questionnaire.rb +7 -2
- data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +6 -2
- data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +58 -2
- data/app/forms/decidim/forms/admin/question_form.rb +20 -1
- data/app/forms/decidim/forms/admin/question_matrix_row_form.rb +26 -0
- data/app/forms/decidim/forms/answer_choice_form.rb +1 -0
- data/app/forms/decidim/forms/answer_form.rb +16 -2
- data/app/forms/decidim/forms/questionnaire_form.rb +33 -3
- data/app/helpers/decidim/forms/admin/application_helper.rb +16 -0
- data/app/models/decidim/forms/answer.rb +2 -2
- data/app/models/decidim/forms/answer_choice.rb +7 -0
- data/app/models/decidim/forms/question.rb +18 -2
- data/app/models/decidim/forms/question_matrix_row.rb +12 -0
- data/app/models/decidim/forms/questionnaire.rb +2 -1
- data/app/queries/decidim/forms/questionnaire_user_answers.rb +1 -1
- data/app/types/decidim/forms/answer_option_type.rb +14 -0
- data/app/types/decidim/forms/question_type.rb +23 -0
- data/app/types/decidim/forms/questionnaire_type.rb +22 -0
- data/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +1 -1
- data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +37 -4
- data/app/views/decidim/forms/admin/questionnaires/_matrix_row.html.erb +34 -0
- data/app/views/decidim/forms/admin/questionnaires/_matrix_row_template.html.erb +7 -0
- data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +29 -6
- data/app/views/decidim/forms/admin/questionnaires/_separator.html.erb +41 -0
- data/app/views/decidim/forms/questionnaires/_answer.html.erb +21 -92
- data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_matrix_multiple.html.erb +43 -0
- data/app/views/decidim/forms/questionnaires/answers/_matrix_single.html.erb +43 -0
- data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +23 -0
- data/app/views/decidim/forms/questionnaires/answers/_separator.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +30 -0
- data/app/views/decidim/forms/questionnaires/answers/_sorting.html.erb +23 -0
- data/app/views/decidim/forms/questionnaires/show.html.erb +60 -27
- data/config/locales/ar.yml +12 -3
- data/config/locales/bg-BG.yml +16 -0
- data/config/locales/ca.yml +45 -7
- data/config/locales/cs.yml +41 -3
- data/config/locales/da-DK.yml +1 -0
- data/config/locales/de.yml +41 -3
- data/config/locales/el-GR.yml +1 -0
- data/config/locales/el.yml +120 -0
- data/config/locales/en.yml +41 -3
- data/config/locales/es-MX.yml +41 -3
- data/config/locales/es-PY.yml +41 -3
- data/config/locales/es.yml +41 -3
- data/config/locales/et-EE.yml +1 -0
- data/config/locales/eu.yml +7 -3
- data/config/locales/fi-plain.yml +41 -3
- data/config/locales/fi.yml +42 -4
- data/config/locales/fr-CA.yml +120 -0
- data/config/locales/fr.yml +41 -3
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +7 -3
- data/config/locales/hr-HR.yml +1 -0
- data/config/locales/hu.yml +18 -4
- data/config/locales/id-ID.yml +7 -3
- data/config/locales/is-IS.yml +1 -0
- data/config/locales/it.yml +42 -4
- data/config/locales/ja-JP.yml +120 -0
- data/config/locales/lt-LT.yml +1 -0
- data/config/locales/lv-LV.yml +119 -0
- data/config/locales/mt-MT.yml +1 -0
- data/config/locales/nl.yml +41 -3
- data/config/locales/no.yml +86 -1
- data/config/locales/pl.yml +63 -25
- data/config/locales/pt-BR.yml +8 -4
- data/config/locales/pt.yml +62 -24
- data/config/locales/ro-RO.yml +118 -0
- data/config/locales/ru.yml +47 -0
- data/config/locales/sk-SK.yml +88 -0
- data/config/locales/sk.yml +92 -0
- data/config/locales/sl.yml +5 -0
- data/config/locales/sr-CS.yml +1 -0
- data/config/locales/sv.yml +41 -3
- data/config/locales/tr-TR.yml +7 -3
- data/db/migrate/20190315203056_add_session_token_to_decidim_forms_answers.rb +17 -0
- data/db/migrate/20190930094710_add_ip_hash_to_decidim_form_answers.rb +12 -0
- data/db/migrate/20200225123810_create_decidim_forms_question_matrix_rows.rb +11 -0
- data/db/migrate/20200304152939_add_matrix_row_id_to_decidim_forms_answer_choices.rb +11 -0
- data/lib/decidim/api/questionnaire_entity_interface.rb +18 -0
- data/lib/decidim/forms.rb +1 -0
- data/lib/decidim/forms/api.rb +7 -0
- data/lib/decidim/forms/test/factories.rb +55 -11
- data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +320 -43
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +347 -16
- data/lib/decidim/forms/user_answers_serializer.rb +23 -4
- data/lib/decidim/forms/version.rb +1 -1
- metadata +56 -9
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
# This cell renders a possible matrix answer of a question (readonly)
|
6
|
+
class MatrixReadonlyCell < Decidim::ViewModel
|
7
|
+
def answer_options
|
8
|
+
model.question.answer_options.map { |option| translated_attribute(option.body) }.join(" / ")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -9,7 +9,11 @@
|
|
9
9
|
|
10
10
|
<% if model.multiple_choice? %>
|
11
11
|
<ul class="questionnaire-question_readonly-answers <%= model.question_type %>">
|
12
|
-
|
12
|
+
<% if model.matrix? %>
|
13
|
+
<%= cell("decidim/forms/matrix_readonly", collection: model.matrix_rows) %>
|
14
|
+
<% else %>
|
15
|
+
<%= cell("decidim/forms/answer_readonly", collection: model.answer_options) %>
|
16
|
+
<% end %>
|
13
17
|
</ul>
|
14
18
|
<% end %>
|
15
19
|
</li>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<div class="button--double form-general-submit answer-questionnaire__submit">
|
2
|
+
<% if first_step? %>
|
3
|
+
<a></a>
|
4
|
+
<% else %>
|
5
|
+
<%= link_to(
|
6
|
+
icon("caret-left", class: "icon--small", role: "img") + " " + t("decidim.forms.step_navigation.show.back"),
|
7
|
+
"#",
|
8
|
+
class: "hollow secondary",
|
9
|
+
data: {
|
10
|
+
toggle: [previous_step_dom_id, current_step_dom_id].join(" ")
|
11
|
+
}
|
12
|
+
) %>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<% if last_step? %>
|
16
|
+
<%= form.submit(
|
17
|
+
t("decidim.forms.step_navigation.show.submit"),
|
18
|
+
class: "button button--sc",
|
19
|
+
disabled: button_disabled?,
|
20
|
+
data: {
|
21
|
+
confirm: t("decidim.forms.step_navigation.show.are_you_sure"),
|
22
|
+
disable: true
|
23
|
+
}
|
24
|
+
) %>
|
25
|
+
<% else %>
|
26
|
+
<%= link_to(
|
27
|
+
t("decidim.forms.step_navigation.show.continue"),
|
28
|
+
"#",
|
29
|
+
class: "button button--sc",
|
30
|
+
data: {
|
31
|
+
toggle: [next_step_dom_id, current_step_dom_id].join(" ")
|
32
|
+
}
|
33
|
+
) %>
|
34
|
+
<% end %>
|
35
|
+
</div>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
# This cell renders the navigation of a questionnaire step.
|
6
|
+
class StepNavigationCell < Decidim::ViewModel
|
7
|
+
include Decidim::LayoutHelper
|
8
|
+
|
9
|
+
def current_step_index
|
10
|
+
model
|
11
|
+
end
|
12
|
+
|
13
|
+
def first_step?
|
14
|
+
current_step_index.zero?
|
15
|
+
end
|
16
|
+
|
17
|
+
def last_step?
|
18
|
+
current_step_index + 1 == total_steps
|
19
|
+
end
|
20
|
+
|
21
|
+
def total_steps
|
22
|
+
options[:total_steps]
|
23
|
+
end
|
24
|
+
|
25
|
+
def form
|
26
|
+
options[:form]
|
27
|
+
end
|
28
|
+
|
29
|
+
def button_disabled?
|
30
|
+
options[:button_disabled]
|
31
|
+
end
|
32
|
+
|
33
|
+
def previous_step_dom_id
|
34
|
+
"step-#{current_step_index - 1}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def next_step_dom_id
|
38
|
+
"step-#{current_step_index + 1}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def current_step_dom_id
|
42
|
+
"step-#{current_step_index}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -56,6 +56,14 @@ module Decidim
|
|
56
56
|
|
57
57
|
update_nested_model(form_answer_option, answer_option_attributes, question.answer_options)
|
58
58
|
end
|
59
|
+
|
60
|
+
form_question.matrix_rows.each do |form_matrix_row|
|
61
|
+
matrix_row_attributes = {
|
62
|
+
body: form_matrix_row.body
|
63
|
+
}
|
64
|
+
|
65
|
+
update_nested_model(form_matrix_row, matrix_row_attributes, question.matrix_rows)
|
66
|
+
end
|
59
67
|
end
|
60
68
|
end
|
61
69
|
|
@@ -24,16 +24,20 @@ module Decidim
|
|
24
24
|
broadcast(:ok)
|
25
25
|
end
|
26
26
|
|
27
|
+
attr_reader :form
|
28
|
+
|
27
29
|
private
|
28
30
|
|
29
31
|
def answer_questionnaire
|
30
32
|
Answer.transaction do
|
31
|
-
|
33
|
+
form.responses_by_step.flatten.each do |form_answer|
|
32
34
|
answer = Answer.new(
|
33
35
|
user: @current_user,
|
34
36
|
questionnaire: @questionnaire,
|
35
37
|
question: form_answer.question,
|
36
|
-
body: form_answer.body
|
38
|
+
body: form_answer.body,
|
39
|
+
session_token: form.context.session_token,
|
40
|
+
ip_hash: form.context.ip_hash
|
37
41
|
)
|
38
42
|
|
39
43
|
form_answer.selected_choices.each do |choice|
|
@@ -41,6 +45,7 @@ module Decidim
|
|
41
45
|
body: choice.body,
|
42
46
|
custom_body: choice.custom_body,
|
43
47
|
decidim_answer_option_id: choice.answer_option_id,
|
48
|
+
decidim_question_matrix_row_id: choice.matrix_row_id,
|
44
49
|
position: choice.position
|
45
50
|
)
|
46
51
|
end
|
@@ -15,7 +15,7 @@ module Decidim
|
|
15
15
|
|
16
16
|
included do
|
17
17
|
helper Decidim::Forms::Admin::ApplicationHelper
|
18
|
-
helper_method :questionnaire_for, :questionnaire, :blank_question, :blank_answer_option, :question_types, :update_url
|
18
|
+
helper_method :questionnaire_for, :questionnaire, :blank_question, :blank_answer_option, :blank_matrix_row, :question_types, :update_url
|
19
19
|
|
20
20
|
def edit
|
21
21
|
enforce_permission_to :update, :questionnaire, questionnaire: questionnaire
|
@@ -82,8 +82,12 @@ module Decidim
|
|
82
82
|
@blank_answer_option ||= Admin::AnswerOptionForm.new
|
83
83
|
end
|
84
84
|
|
85
|
+
def blank_matrix_row
|
86
|
+
@blank_matrix_row ||= Admin::QuestionMatrixRowForm.new
|
87
|
+
end
|
88
|
+
|
85
89
|
def question_types
|
86
|
-
@question_types ||= Question::
|
90
|
+
@question_types ||= Question::QUESTION_TYPES.map do |question_type|
|
87
91
|
[question_type, I18n.t("decidim.forms.question_types.#{question_type}")]
|
88
92
|
end
|
89
93
|
end
|
@@ -16,7 +16,9 @@ module Decidim
|
|
16
16
|
helper Decidim::Forms::ApplicationHelper
|
17
17
|
include FormFactory
|
18
18
|
|
19
|
-
helper_method :questionnaire_for, :questionnaire, :allow_answers?, :update_url
|
19
|
+
helper_method :questionnaire_for, :questionnaire, :allow_answers?, :visitor_can_answer?, :visitor_already_answered?, :update_url, :form_path
|
20
|
+
|
21
|
+
invisible_captcha on_spam: :spam_detected
|
20
22
|
|
21
23
|
def show
|
22
24
|
@form = form(Decidim::Forms::QuestionnaireForm).from_model(questionnaire)
|
@@ -26,7 +28,7 @@ module Decidim
|
|
26
28
|
def answer
|
27
29
|
enforce_permission_to :answer, :questionnaire
|
28
30
|
|
29
|
-
@form = form(Decidim::Forms::QuestionnaireForm).from_params(params)
|
31
|
+
@form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token: session_token, ip_hash: ip_hash)
|
30
32
|
|
31
33
|
Decidim::Forms::AnswerQuestionnaire.call(@form, current_user, questionnaire) do
|
32
34
|
on(:ok) do
|
@@ -49,6 +51,22 @@ module Decidim
|
|
49
51
|
raise "#{self.class.name} is expected to implement #allow_answers?"
|
50
52
|
end
|
51
53
|
|
54
|
+
# Public: Method to be implemented at the controller if needed. You need to
|
55
|
+
# return true if the questionnaire can receive answers by unregistered users
|
56
|
+
def allow_unregistered?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: return true if the current user (if logged) can answer the questionnaire
|
61
|
+
def visitor_can_answer?
|
62
|
+
current_user || allow_unregistered?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public: return true if the current user (or session visitor) can answer the questionnaire
|
66
|
+
def visitor_already_answered?
|
67
|
+
questionnaire.answered_by?(current_user || tokenize(session[:session_id]))
|
68
|
+
end
|
69
|
+
|
52
70
|
# Public: Returns a String or Object that will be passed to `redirect_to` after
|
53
71
|
# answering the questionnaire. By default it redirects to the questionnaire_for.
|
54
72
|
#
|
@@ -63,6 +81,15 @@ module Decidim
|
|
63
81
|
url_for([questionnaire_for, action: :answer])
|
64
82
|
end
|
65
83
|
|
84
|
+
# Points to the shortest path accessing the current form. This will be
|
85
|
+
# used to detect whether a user is leaving the form with some partial
|
86
|
+
# answers, so that we can warn them.
|
87
|
+
#
|
88
|
+
# Overwrite this method at the controller.
|
89
|
+
def form_path
|
90
|
+
url_for([questionnaire_for, only_path: true])
|
91
|
+
end
|
92
|
+
|
66
93
|
# Public: Method to be implemented at the controller. You need to
|
67
94
|
# return the object that will hold the questionnaire.
|
68
95
|
def questionnaire_for
|
@@ -78,6 +105,35 @@ module Decidim
|
|
78
105
|
def questionnaire
|
79
106
|
@questionnaire ||= Questionnaire.includes(questions: :answer_options).find_by(questionnaire_for: questionnaire_for)
|
80
107
|
end
|
108
|
+
|
109
|
+
def spam_detected
|
110
|
+
enforce_permission_to :answer, :questionnaire
|
111
|
+
|
112
|
+
@form = form(Decidim::Forms::QuestionnaireForm).from_params(params)
|
113
|
+
|
114
|
+
flash.now[:alert] = I18n.t("answer.spam_detected", scope: i18n_flashes_scope)
|
115
|
+
render template: "decidim/forms/questionnaires/show"
|
116
|
+
end
|
117
|
+
|
118
|
+
def ip_hash
|
119
|
+
return nil unless request&.remote_ip
|
120
|
+
|
121
|
+
@ip_hash ||= tokenize(request&.remote_ip)
|
122
|
+
end
|
123
|
+
|
124
|
+
# token is used as a substitute of user_id if unregistered
|
125
|
+
def session_token
|
126
|
+
id = current_user&.id
|
127
|
+
session_id = request.session[:session_id] if request&.session
|
128
|
+
|
129
|
+
return nil unless id || session_id
|
130
|
+
|
131
|
+
@session_token ||= tokenize(id || session_id)
|
132
|
+
end
|
133
|
+
|
134
|
+
def tokenize(id)
|
135
|
+
Digest::MD5.hexdigest("#{id}-#{Rails.application.secrets.secret_key_base}")
|
136
|
+
end
|
81
137
|
end
|
82
138
|
end
|
83
139
|
end
|
@@ -11,6 +11,7 @@ module Decidim
|
|
11
11
|
attribute :mandatory, Boolean, default: false
|
12
12
|
attribute :question_type, String
|
13
13
|
attribute :answer_options, Array[AnswerOptionForm]
|
14
|
+
attribute :matrix_rows, Array[QuestionMatrixRowForm]
|
14
15
|
attribute :max_choices, Integer
|
15
16
|
attribute :deleted, Boolean, default: false
|
16
17
|
|
@@ -20,7 +21,9 @@ module Decidim
|
|
20
21
|
validates :position, numericality: { greater_than_or_equal_to: 0 }
|
21
22
|
validates :question_type, inclusion: { in: Decidim::Forms::Question::TYPES }
|
22
23
|
validates :max_choices, numericality: { only_integer: true, greater_than: 1, less_than_or_equal_to: ->(form) { form.number_of_options } }, allow_blank: true
|
23
|
-
validates :body, translatable_presence: true,
|
24
|
+
validates :body, translatable_presence: true, if: :requires_body?
|
25
|
+
validates :matrix_rows, presence: true, if: :matrix?
|
26
|
+
validates :answer_options, presence: true, if: :matrix?
|
24
27
|
|
25
28
|
def to_param
|
26
29
|
return id if id.present?
|
@@ -31,6 +34,22 @@ module Decidim
|
|
31
34
|
def number_of_options
|
32
35
|
answer_options.size
|
33
36
|
end
|
37
|
+
|
38
|
+
def separator?
|
39
|
+
question_type == Decidim::Forms::Question::SEPARATOR_TYPE
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def matrix?
|
45
|
+
question_type == "matrix_single" || question_type == "matrix_multiple"
|
46
|
+
end
|
47
|
+
|
48
|
+
def requires_body?
|
49
|
+
return false if separator?
|
50
|
+
|
51
|
+
!deleted
|
52
|
+
end
|
34
53
|
end
|
35
54
|
end
|
36
55
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Admin
|
6
|
+
# This class holds a Form to update questionnaire question matrixes from Decidim's admin panel.
|
7
|
+
class QuestionMatrixRowForm < Decidim::Form
|
8
|
+
include TranslatableAttributes
|
9
|
+
|
10
|
+
attribute :position, Integer, default: 0
|
11
|
+
attribute :deleted, Boolean, default: false
|
12
|
+
|
13
|
+
translatable_attribute :body, String
|
14
|
+
|
15
|
+
validates :position, numericality: { greater_than_or_equal_to: 0 }
|
16
|
+
validates :body, translatable_presence: true, unless: :deleted
|
17
|
+
|
18
|
+
def to_param
|
19
|
+
return id if id.present?
|
20
|
+
|
21
|
+
"questionnaire-question-matrix-row-id"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -9,14 +9,16 @@ module Decidim
|
|
9
9
|
attribute :question_id, String
|
10
10
|
attribute :body, String
|
11
11
|
attribute :choices, Array[AnswerChoiceForm]
|
12
|
+
attribute :matrix_choices, Array[AnswerChoiceForm]
|
12
13
|
|
13
14
|
validates :body, presence: true, if: :mandatory_body?
|
14
15
|
validates :selected_choices, presence: true, if: :mandatory_choices?
|
15
16
|
|
16
17
|
validate :max_choices, if: -> { question.max_choices }
|
17
18
|
validate :all_choices, if: -> { question.question_type == "sorting" }
|
19
|
+
validate :min_choices, if: -> { question.matrix? && question.mandatory? }
|
18
20
|
|
19
|
-
delegate :mandatory_body?, :mandatory_choices?, to: :question
|
21
|
+
delegate :mandatory_body?, :mandatory_choices?, :matrix?, to: :question
|
20
22
|
|
21
23
|
attr_writer :question
|
22
24
|
|
@@ -49,8 +51,20 @@ module Decidim
|
|
49
51
|
|
50
52
|
private
|
51
53
|
|
54
|
+
def grouped_choices
|
55
|
+
selected_choices.group_by(&:matrix_row_id).values
|
56
|
+
end
|
57
|
+
|
52
58
|
def max_choices
|
53
|
-
|
59
|
+
if matrix?
|
60
|
+
errors.add(:choices, :too_many) if grouped_choices.any? { |choices| choices.count > question.max_choices }
|
61
|
+
elsif selected_choices.size > question.max_choices
|
62
|
+
errors.add(:choices, :too_many)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def min_choices
|
67
|
+
errors.add(:choices, :missing) if grouped_choices.count != question.matrix_rows.count
|
54
68
|
end
|
55
69
|
|
56
70
|
def all_choices
|
@@ -4,20 +4,50 @@ module Decidim
|
|
4
4
|
module Forms
|
5
5
|
# This class holds a Form to answer a questionnaire from Decidim's public page.
|
6
6
|
class QuestionnaireForm < Decidim::Form
|
7
|
-
|
7
|
+
# as questionnaire uses "answers" for the database relationships is
|
8
|
+
# important not to use the same word here to avoid querying all the entries, resulting in a high performance penalty
|
9
|
+
attribute :responses, Array[AnswerForm]
|
8
10
|
attribute :user_group_id, Integer
|
9
11
|
|
10
12
|
attribute :tos_agreement, Boolean
|
13
|
+
|
11
14
|
validates :tos_agreement, allow_nil: false, acceptance: true
|
15
|
+
validate :session_token_in_context
|
12
16
|
|
13
|
-
# Private: Create the
|
17
|
+
# Private: Create the responses from the questionnaire questions
|
14
18
|
#
|
15
19
|
# Returns nothing.
|
16
20
|
def map_model(model)
|
17
|
-
self.
|
21
|
+
self.responses = model.questions.map do |question|
|
18
22
|
AnswerForm.from_model(Decidim::Forms::Answer.new(question: question))
|
19
23
|
end
|
20
24
|
end
|
25
|
+
|
26
|
+
# Public: Splits reponses by step, keeping the separator.
|
27
|
+
#
|
28
|
+
# Returns an array of steps. Each step is a list of the questions in that
|
29
|
+
# step, including the separator.
|
30
|
+
def responses_by_step
|
31
|
+
@responses_by_step ||=
|
32
|
+
begin
|
33
|
+
steps = responses.chunk_while do |a, b|
|
34
|
+
!a.question.separator? || b.question.separator?
|
35
|
+
end.to_a
|
36
|
+
|
37
|
+
steps = [[]] if steps == []
|
38
|
+
steps
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def total_steps
|
43
|
+
responses_by_step.count
|
44
|
+
end
|
45
|
+
|
46
|
+
def session_token_in_context
|
47
|
+
return if context&.session_token
|
48
|
+
|
49
|
+
errors.add(:tos_agreement, I18n.t("activemodel.errors.models.questionnaire.request_invalid"))
|
50
|
+
end
|
21
51
|
end
|
22
52
|
end
|
23
53
|
end
|