quby-compiler 0.4.5 → 0.4.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/lib/quby/compiler/dsl/questions/base.rb +1 -11
- data/lib/quby/compiler/entities/flag.rb +0 -11
- data/lib/quby/compiler/entities/item.rb +0 -4
- data/lib/quby/compiler/entities/question.rb +18 -36
- data/lib/quby/compiler/entities/question_option.rb +6 -29
- data/lib/quby/compiler/entities/questionnaire.rb +20 -39
- data/lib/quby/compiler/entities/questions/checkbox_question.rb +2 -8
- data/lib/quby/compiler/entities/questions/date_question.rb +2 -14
- data/lib/quby/compiler/entities/questions/radio_question.rb +0 -4
- data/lib/quby/compiler/entities/questions/select_question.rb +0 -4
- data/lib/quby/compiler/entities/textvar.rb +0 -7
- data/lib/quby/compiler/outputs/quby_frontend_v1_serializer.rb +15 -4
- data/lib/quby/compiler/outputs/roqua_serializer.rb +28 -9
- data/lib/quby/compiler/services/definition_validator.rb +3 -0
- data/lib/quby/compiler/services/quby_proxy.rb +21 -15
- data/lib/quby/compiler/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8adddc572c511a6145f59379fa1c54d49f53fcd3487a3d9a5026fb0634b54c54
|
4
|
+
data.tar.gz: 5b173fb296fd7a7b88d9d60eb2357117c6f9f80eb1c800a5fe781a427d359efa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e23e0347aa449053801f045533b26d10108d8f4a62783a71a121b9cc41215dab348ca3da8e8acf528a58e4689f05219c96237218478c2368acbfcff07c220ce
|
7
|
+
data.tar.gz: 47e5d45f09c1452db50e29e3933967256b890090991ed715f91003b7622076aafa0adbe0d0f414506b79707187c03bc37c7217c94bad39bfd5f3a2d681dec72d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
# 0.4.9
|
2
|
+
|
3
|
+
* roqua.json: Fix html stripping for string with no whitespace
|
4
|
+
|
5
|
+
# 0.4.8
|
6
|
+
|
7
|
+
* roqua.json:
|
8
|
+
* sort parent questions before it's children, to make the order more logical to just dumbly show
|
9
|
+
* parse markdown before stripping tags (questiom.title, option.description)
|
10
|
+
* context-free-titles no longer contain the title as fallback
|
11
|
+
* quby2.json
|
12
|
+
* sort visibility rules: flags first, then in question order, so we can apply them in order without issue
|
13
|
+
|
14
|
+
# 0.4.7
|
15
|
+
|
16
|
+
* seeds.yml: Fix missing title questions (broken by 0.4.6)
|
17
|
+
|
18
|
+
# 0.4.6
|
19
|
+
|
20
|
+
* Added type validations for boolean attributes
|
21
|
+
* roqua.json
|
22
|
+
* add unit to questions
|
23
|
+
* add title_question_key to questions
|
24
|
+
* add parent_question_key and parent_option_key to questions
|
25
|
+
* add child_question_keys to question_options
|
26
|
+
* models
|
27
|
+
* Made title_question attribute on question instead of adding options.last.questions.
|
28
|
+
* Made v1 serializer create backward compatible json (for now)
|
29
|
+
* Made v1 serializer add the full title_question json to question.title_question.
|
30
|
+
|
1
31
|
# 0.4.5
|
2
32
|
|
3
33
|
* roqua.json: Don't add inner titles and placeholder options
|
@@ -114,16 +114,6 @@ module Quby
|
|
114
114
|
def initialize(key, **options, &block)
|
115
115
|
super
|
116
116
|
@default_question_options = options[:default_question_options] || {}
|
117
|
-
@title_question = nil
|
118
|
-
end
|
119
|
-
|
120
|
-
def build
|
121
|
-
if @title_question
|
122
|
-
@question.options.last.questions << @title_question
|
123
|
-
@title_question = nil
|
124
|
-
end
|
125
|
-
|
126
|
-
super
|
127
117
|
end
|
128
118
|
|
129
119
|
def title_question(key, **options, &block)
|
@@ -138,7 +128,7 @@ module Quby
|
|
138
128
|
question = QuestionBuilder.build(key, **options, &block)
|
139
129
|
|
140
130
|
@questionnaire.register_question(question)
|
141
|
-
@title_question = question
|
131
|
+
@question.title_question = question
|
142
132
|
end
|
143
133
|
|
144
134
|
def question(key, **options, &block)
|
@@ -31,17 +31,6 @@ module Quby
|
|
31
31
|
"#{description} (true - '#{description_true}', false - '#{description_false}')"
|
32
32
|
end
|
33
33
|
|
34
|
-
def to_codebook(_options = {})
|
35
|
-
output = []
|
36
|
-
output << "#{key} flag"
|
37
|
-
output << "'#{description}'" if description.present?
|
38
|
-
output << " 'true' - #{description_true}"
|
39
|
-
output << " 'false' - #{description_false}"
|
40
|
-
output << " '' (leeg) - Vlag niet ingesteld, informatie onbekend"
|
41
|
-
output << ""
|
42
|
-
output.join("\n")
|
43
|
-
end
|
44
|
-
|
45
34
|
private
|
46
35
|
|
47
36
|
def ensure_valid_descriptions
|
@@ -29,9 +29,11 @@ module Quby
|
|
29
29
|
|
30
30
|
# To hide old questions
|
31
31
|
attr_accessor :hidden
|
32
|
+
validates :hidden, inclusion: {in: [true, false, nil], message: "must be boolean"}
|
32
33
|
|
33
34
|
# Whether to skip the uniqueness validation on radio and select option values
|
34
35
|
attr_reader :allow_duplicate_option_values
|
36
|
+
validates :allow_duplicate_option_values, inclusion: {in: [true, false, nil], message: "must be boolean"}
|
35
37
|
|
36
38
|
# In what modes do we display this question
|
37
39
|
# NOTE We always display questions in print-view (if they have an answer)
|
@@ -40,9 +42,13 @@ module Quby
|
|
40
42
|
# Multiple-choice questions have options to choose from
|
41
43
|
attr_accessor :options
|
42
44
|
|
45
|
+
# string question that is displayed after the title of this question.
|
46
|
+
attr_accessor :title_question
|
47
|
+
|
43
48
|
# Question validation fails when there are no title and no context_free_title.
|
44
49
|
# When :allow_blank_titles => true passed, validation does not fail. Any other value will raise the failure.
|
45
50
|
attr_accessor :allow_blank_titles
|
51
|
+
validates :allow_blank_titles, inclusion: {in: [true, false, nil], message: "must be boolean"}
|
46
52
|
|
47
53
|
# Minimum and maximum values for float and integer types
|
48
54
|
attr_accessor :minimum
|
@@ -72,6 +78,7 @@ module Quby
|
|
72
78
|
|
73
79
|
# Whether this radio question is deselectable
|
74
80
|
attr_accessor :deselectable
|
81
|
+
validates :deselectable, inclusion: {in: [true, false, nil], message: "must be boolean"}
|
75
82
|
|
76
83
|
# Some questions are a tree.
|
77
84
|
attr_accessor :parent
|
@@ -79,6 +86,7 @@ module Quby
|
|
79
86
|
|
80
87
|
# Whether we can collapse this in bulk view
|
81
88
|
attr_accessor :disallow_bulk
|
89
|
+
validates :disallow_bulk, inclusion: {in: [true, false, nil], message: "must be boolean"}
|
82
90
|
|
83
91
|
# This question should not validate itself unless the depends_on question is answered.
|
84
92
|
# May also be an array of "#{question_key}_#{option_key}" strings that specify options
|
@@ -115,6 +123,7 @@ module Quby
|
|
115
123
|
attr_accessor :row_span
|
116
124
|
|
117
125
|
attr_accessor :default_invisible
|
126
|
+
validates :default_invisible, inclusion: {in: [true, false], message: "must be boolean"}
|
118
127
|
|
119
128
|
# Slider only: where to place the sliding thing by default
|
120
129
|
# Can have value :hidden for a hidden handle.
|
@@ -200,6 +209,10 @@ module Quby
|
|
200
209
|
end
|
201
210
|
# rubocop:enable CyclomaticComplexity, Metrics/MethodLength
|
202
211
|
|
212
|
+
def context_free_title_or_title
|
213
|
+
context_free_title || title
|
214
|
+
end
|
215
|
+
|
203
216
|
# rubocop:disable AccessorMethodName
|
204
217
|
def set_depends_on(keys)
|
205
218
|
return if keys.blank?
|
@@ -208,10 +221,6 @@ module Quby
|
|
208
221
|
end
|
209
222
|
# rubocop:enable AccessorMethodName
|
210
223
|
|
211
|
-
def context_free_title
|
212
|
-
@context_free_title || @title
|
213
|
-
end
|
214
|
-
|
215
224
|
def expand_depends_on_input_keys
|
216
225
|
return unless @depends_on
|
217
226
|
@depends_on = questionnaire.expand_input_keys(@depends_on)
|
@@ -247,6 +256,10 @@ module Quby
|
|
247
256
|
end
|
248
257
|
end
|
249
258
|
|
259
|
+
def title_question?
|
260
|
+
presentation == :next_to_title
|
261
|
+
end
|
262
|
+
|
250
263
|
# Returns all keys belonging to html inputs generated by this question.
|
251
264
|
def input_keys
|
252
265
|
if options.blank?
|
@@ -316,39 +329,8 @@ module Quby
|
|
316
329
|
!parent_option_key.nil?
|
317
330
|
end
|
318
331
|
|
319
|
-
def to_codebook(questionnaire, opts = {})
|
320
|
-
output = []
|
321
|
-
question_key = codebook_key(key, questionnaire, opts)
|
322
|
-
output << "#{question_key} #{codebook_output_type} #{codebook_output_range}#{' deprecated' if hidden}"
|
323
|
-
output << "\"#{context_free_title}\"" unless context_free_title.blank?
|
324
|
-
options_string = options.map do |option|
|
325
|
-
option.to_codebook(questionnaire, opts)
|
326
|
-
end.compact.join("\n")
|
327
|
-
output << options_string unless options.blank?
|
328
|
-
output.join("\n")
|
329
|
-
end
|
330
|
-
|
331
|
-
def codebook_key(key, questionnaire, opts = {})
|
332
|
-
key.to_s.gsub(/^v_/, "#{opts[:roqua_key] || questionnaire.key.to_s}_")
|
333
|
-
end
|
334
|
-
|
335
|
-
def codebook_output_type
|
336
|
-
type
|
337
|
-
end
|
338
|
-
|
339
|
-
def codebook_output_range
|
340
|
-
range_min = validations.find { |i| i[:type] == :minimum }&.fetch(:value, nil)
|
341
|
-
range_max = validations.find { |i| i[:type] == :maximum }&.fetch(:value, nil)
|
342
|
-
|
343
|
-
if range_min || range_max
|
344
|
-
"(#{[range_min, "value", range_max].compact.join(" <= ")})"
|
345
|
-
else
|
346
|
-
""
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
332
|
def variable_descriptions
|
351
|
-
{key =>
|
333
|
+
{key => context_free_title_or_title}.with_indifferent_access
|
352
334
|
end
|
353
335
|
end
|
354
336
|
end
|
@@ -4,23 +4,26 @@ module Quby
|
|
4
4
|
module Compiler
|
5
5
|
module Entities
|
6
6
|
class QuestionOption
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
7
9
|
MARKDOWN_ATTRIBUTES = %w(description).freeze
|
8
10
|
|
9
11
|
attr_reader :key
|
10
12
|
attr_reader :value
|
11
|
-
attr_reader :description
|
13
|
+
attr_reader :description, :context_free_description
|
12
14
|
attr_reader :questions
|
13
15
|
attr_reader :inner_title
|
16
|
+
validates :inner_title, inclusion: {in: [true, false, nil], message: "must be boolean"}
|
14
17
|
attr_reader :hides_questions
|
15
18
|
attr_reader :shows_questions
|
16
19
|
attr_reader :hidden
|
20
|
+
validates :hidden, inclusion: {in: [true, false], message: "must be boolean"}
|
17
21
|
attr_reader :placeholder
|
22
|
+
validates :placeholder, inclusion: {in: [true, false], message: "must be boolean"}
|
18
23
|
attr_reader :question
|
19
24
|
attr_reader :view_id
|
20
25
|
attr_reader :input_key
|
21
26
|
|
22
|
-
attr_reader :start_chosen
|
23
|
-
|
24
27
|
def initialize(key, question, options = {})
|
25
28
|
@key = key
|
26
29
|
@question = question
|
@@ -49,10 +52,6 @@ module Quby
|
|
49
52
|
false
|
50
53
|
end
|
51
54
|
|
52
|
-
def context_free_description
|
53
|
-
@context_free_description || @description
|
54
|
-
end
|
55
|
-
|
56
55
|
def as_json(options = {})
|
57
56
|
{
|
58
57
|
key: key,
|
@@ -72,28 +71,6 @@ module Quby
|
|
72
71
|
viewId: view_id
|
73
72
|
}
|
74
73
|
end
|
75
|
-
|
76
|
-
def to_codebook(questionnaire, opts)
|
77
|
-
return nil if inner_title
|
78
|
-
output = []
|
79
|
-
|
80
|
-
if question.type == :check_box
|
81
|
-
option_key = question.codebook_key(key, questionnaire, opts)
|
82
|
-
output << "#{option_key} #{question.codebook_output_type}#{' deprecated' if hidden || question.hidden }"
|
83
|
-
output << "\"#{question.title} -- #{description}\"" unless question.title.blank? and description.blank?
|
84
|
-
output << "1\tChecked"
|
85
|
-
output << "0\tUnchecked"
|
86
|
-
output << "empty\tUnchecked"
|
87
|
-
else
|
88
|
-
output << "#{value || key}\t\"#{description}\"#{' deprecated' if hidden}"
|
89
|
-
end
|
90
|
-
|
91
|
-
questions.each do |subquestion|
|
92
|
-
output << "\t#{subquestion.to_codebook(questionnaire, opts).gsub("\n", "\n\t")}"
|
93
|
-
end
|
94
|
-
|
95
|
-
output.join("\n")
|
96
|
-
end
|
97
74
|
end
|
98
75
|
end
|
99
76
|
end
|
@@ -172,6 +172,19 @@ module Quby
|
|
172
172
|
end)
|
173
173
|
end
|
174
174
|
|
175
|
+
# sorts parents before children, so showing makes more sense and visiblity rules are ordered correctly
|
176
|
+
def sorted_questions
|
177
|
+
return @sorted_questions if @sorted_questions
|
178
|
+
|
179
|
+
key_to_order_by = questions.map.with_index.to_h { [_1.key, [_2]] }
|
180
|
+
questions.each do |question|
|
181
|
+
if question.parent
|
182
|
+
key_to_order_by[question.key].unshift(key_to_order_by[question.parent.key].first)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
@sorted_questions = questions.sort_by { key_to_order_by[_1.key] }
|
186
|
+
end
|
187
|
+
|
175
188
|
def questions
|
176
189
|
question_hash.values
|
177
190
|
end
|
@@ -201,43 +214,6 @@ module Quby
|
|
201
214
|
}
|
202
215
|
end
|
203
216
|
|
204
|
-
# rubocop:disable Metrics/MethodLength
|
205
|
-
def to_codebook(options = {})
|
206
|
-
output = []
|
207
|
-
output << title
|
208
|
-
output << "Date unknown"
|
209
|
-
output << ""
|
210
|
-
|
211
|
-
options[:extra_vars]&.each do |var|
|
212
|
-
output << "#{var[:key]} #{var[:type]}"
|
213
|
-
output << "\"#{var[:description]}\""
|
214
|
-
output << ""
|
215
|
-
end
|
216
|
-
|
217
|
-
top_questions = panels.map do |panel|
|
218
|
-
panel.items.select { |item| item.is_a? Question }
|
219
|
-
end.flatten.compact
|
220
|
-
|
221
|
-
top_questions.each do |question|
|
222
|
-
output << question.to_codebook(self, options)
|
223
|
-
output << ""
|
224
|
-
end
|
225
|
-
|
226
|
-
flags.each_value do |flag|
|
227
|
-
output << flag.to_codebook(options)
|
228
|
-
output << ""
|
229
|
-
end
|
230
|
-
|
231
|
-
textvars.each_value do |textvar|
|
232
|
-
output << textvar.to_codebook(options)
|
233
|
-
output << ""
|
234
|
-
end
|
235
|
-
|
236
|
-
output = output.join("\n")
|
237
|
-
strip_tags(output.gsub(/\<([ 1-9])/, '<\1')).gsub("<", "<")
|
238
|
-
end
|
239
|
-
# rubocop:enable Metrics/MethodLength
|
240
|
-
|
241
217
|
def key_in_use?(key)
|
242
218
|
fields.key_in_use?(key) || score_calculations.key?(key)
|
243
219
|
end
|
@@ -415,9 +391,14 @@ module Quby
|
|
415
391
|
end.uniq(&:config)
|
416
392
|
end
|
417
393
|
|
394
|
+
# Order is important
|
395
|
+
# quby2 follows them one by one and ignores rules by hidden question, so needs to be in order of interface.
|
396
|
+
# Flags don't have this issue, so as long as they are first, their order doesn't matter.
|
418
397
|
def visibility_rules
|
419
|
-
@visibility_rules ||=
|
420
|
-
|
398
|
+
@visibility_rules ||= [
|
399
|
+
*flags.values.flat_map { |flag| VisibilityRule.from_flag(flag) },
|
400
|
+
*sorted_questions.flat_map { |question| VisibilityRule.from(question) }
|
401
|
+
]
|
421
402
|
end
|
422
403
|
|
423
404
|
private
|
@@ -51,9 +51,9 @@ module Quby
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def variable_descriptions
|
54
|
-
options.each_with_object(key =>
|
54
|
+
options.each_with_object(key => context_free_title_or_title) do |option, hash|
|
55
55
|
next if option.input_key.blank?
|
56
|
-
hash[option.input_key] = "#{
|
56
|
+
hash[option.input_key] = "#{context_free_title_or_title} - #{option.description}"
|
57
57
|
end.with_indifferent_access
|
58
58
|
end
|
59
59
|
|
@@ -69,12 +69,6 @@ module Quby
|
|
69
69
|
def as_json(options = {})
|
70
70
|
super.merge(options: @options.as_json)
|
71
71
|
end
|
72
|
-
|
73
|
-
def to_codebook(questionnaire, opts = {})
|
74
|
-
options.map do |option|
|
75
|
-
option.to_codebook(questionnaire, opts)
|
76
|
-
end.compact.join("\n\n")
|
77
|
-
end
|
78
72
|
end
|
79
73
|
end
|
80
74
|
end
|
@@ -53,9 +53,9 @@ module Quby
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def variable_descriptions
|
56
|
-
components.each_with_object(key =>
|
56
|
+
components.each_with_object(key => context_free_title_or_title) do |component, hash|
|
57
57
|
key = send("#{component}_key")
|
58
|
-
hash[key] = "#{
|
58
|
+
hash[key] = "#{context_free_title_or_title} (#{I18n.t component})"
|
59
59
|
end.with_indifferent_access
|
60
60
|
end
|
61
61
|
|
@@ -65,18 +65,6 @@ module Quby
|
|
65
65
|
end
|
66
66
|
super.merge(components: components).merge(component_keys)
|
67
67
|
end
|
68
|
-
|
69
|
-
def to_codebook(questionnaire, opts = {})
|
70
|
-
output = []
|
71
|
-
components.each do |component|
|
72
|
-
output << "#{codebook_key(send("#{component}_key"), questionnaire, opts)} " \
|
73
|
-
"#{type}_#{component} #{codebook_output_range}"
|
74
|
-
output << "\"#{title}\"" unless title.blank?
|
75
|
-
output << options.map(&:to_codebook).join("\n") unless options.blank?
|
76
|
-
output << ""
|
77
|
-
end
|
78
|
-
output.join("\n")
|
79
|
-
end
|
80
68
|
end
|
81
69
|
end
|
82
70
|
end
|
@@ -10,13 +10,6 @@ module Quby
|
|
10
10
|
super(key, description, default, depends_on_flag)
|
11
11
|
end
|
12
12
|
# rubocop:enable ParameterLists
|
13
|
-
|
14
|
-
def to_codebook(_options = {})
|
15
|
-
output = []
|
16
|
-
output << "#{key} Textvariabele"
|
17
|
-
output << description
|
18
|
-
output.join("\n")
|
19
|
-
end
|
20
13
|
end
|
21
14
|
end
|
22
15
|
end
|
@@ -61,6 +61,7 @@ module Quby
|
|
61
61
|
question_type: question.type,
|
62
62
|
key: question.key,
|
63
63
|
title: question.title,
|
64
|
+
title_question: (question_as_json(question.title_question) if question.title_question),
|
64
65
|
context_free_title: question.context_free_title,
|
65
66
|
description: question.description,
|
66
67
|
presentation: question.presentation,
|
@@ -96,7 +97,7 @@ module Quby
|
|
96
97
|
case question
|
97
98
|
when Quby::Compiler::Entities::Questions::CheckboxQuestion
|
98
99
|
base_options.merge(
|
99
|
-
options: question
|
100
|
+
options: options_as_json(question),
|
100
101
|
check_all_option: question.check_all_option,
|
101
102
|
uncheck_all_option: question.uncheck_all_option,
|
102
103
|
maximum_checked_allowed: question.maximum_checked_allowed,
|
@@ -114,7 +115,7 @@ module Quby
|
|
114
115
|
)
|
115
116
|
when Quby::Compiler::Entities::Questions::DeprecatedQuestion
|
116
117
|
base_options.merge(
|
117
|
-
options: question
|
118
|
+
options: options_as_json(question)
|
118
119
|
)
|
119
120
|
when Quby::Compiler::Entities::Questions::FloatQuestion
|
120
121
|
base_options.merge(
|
@@ -130,11 +131,11 @@ module Quby
|
|
130
131
|
)
|
131
132
|
when Quby::Compiler::Entities::Questions::RadioQuestion
|
132
133
|
base_options.merge(
|
133
|
-
options: question
|
134
|
+
options: options_as_json(question)
|
134
135
|
)
|
135
136
|
when Quby::Compiler::Entities::Questions::SelectQuestion
|
136
137
|
base_options.merge(
|
137
|
-
options: question
|
138
|
+
options: options_as_json(question)
|
138
139
|
)
|
139
140
|
when Quby::Compiler::Entities::Questions::StringQuestion
|
140
141
|
base_options.merge(
|
@@ -177,6 +178,16 @@ module Quby
|
|
177
178
|
end
|
178
179
|
end
|
179
180
|
|
181
|
+
# Also adds the title question under the last option.
|
182
|
+
# TODO old dsl put it there, remove after quby gem uses title_question
|
183
|
+
def options_as_json(question)
|
184
|
+
question.options.map { |option| option_as_json(option) }.tap do |options_json|
|
185
|
+
if question.title_question
|
186
|
+
options_json.last[:questions] << question_as_json(question.title_question)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
180
191
|
def option_as_json(option)
|
181
192
|
{
|
182
193
|
key: option.key,
|
@@ -160,31 +160,39 @@ module Quby
|
|
160
160
|
# sbg_key [nil/string] _not_ defaulted to key
|
161
161
|
# type [string]
|
162
162
|
# title [nil/string] no html
|
163
|
-
# context_free_title [nil/string] no html, defaulted to title
|
163
|
+
# context_free_title [nil/string] no html, not defaulted to title!
|
164
|
+
# unit [nil/string] no html (not in quby either)
|
164
165
|
# deprecated [nil/true] only show if filled for old answers
|
165
166
|
# default_invisible [nil/true] only show if filled or when all questions are shown
|
167
|
+
# parent_question_key [nil/string] title_questions or question defined under option
|
168
|
+
# parent_option_key [nil/string] question defined under option
|
169
|
+
# title_question_key [nil/string]
|
166
170
|
# date_parts [nil/{..}]
|
167
171
|
# options: [nil/{..}]
|
168
172
|
def questions
|
169
|
-
questionnaire.
|
173
|
+
questionnaire.sorted_questions.reject{_1.type == :hidden}.to_h { |question|
|
170
174
|
[
|
171
175
|
question.key,
|
172
176
|
{
|
173
177
|
key: question.key,
|
174
178
|
sbg_key: question.sbg_key,
|
175
179
|
type: QUBY_TYPE_TO_ROQUA_TYPE.fetch(question.type),
|
176
|
-
title:
|
177
|
-
context_free_title:
|
180
|
+
title: parse_markdown_and_strip_tags(question.title),
|
181
|
+
context_free_title: question.context_free_title,
|
182
|
+
unit: question.unit,
|
178
183
|
deprecated: question.hidden.presence,
|
179
184
|
default_invisible: question.default_invisible.presence,
|
185
|
+
parent_question_key: question.parent&.key,
|
186
|
+
parent_option_key: question.parent_option_key,
|
187
|
+
title_question_key: question.title_question&.key,
|
180
188
|
date_parts: date_parts_for(question),
|
181
|
-
options: options_for(question)
|
189
|
+
options: options_for(question)
|
182
190
|
}.compact
|
183
191
|
]
|
184
192
|
}
|
185
193
|
end
|
186
194
|
|
187
|
-
# {
|
195
|
+
# {year: {key: "v_date_yyyy"}, ..}
|
188
196
|
# key [string]
|
189
197
|
def date_parts_for(question)
|
190
198
|
return nil unless question.type == :date
|
@@ -202,7 +210,8 @@ module Quby
|
|
202
210
|
# {a1: {..}}
|
203
211
|
# key [string]
|
204
212
|
# value [nil/string] nil for check_box, string otherwise
|
205
|
-
# description [nil/string] context_free_description, no html
|
213
|
+
# description [nil/string] context_free_description or parsed/stripped description, no html
|
214
|
+
# child_question_keys [nil/[string]]
|
206
215
|
def options_for(question)
|
207
216
|
return nil if question.options.empty?
|
208
217
|
|
@@ -213,7 +222,8 @@ module Quby
|
|
213
222
|
{
|
214
223
|
key: option.key,
|
215
224
|
value: (option.value.to_s unless question.type == :check_box),
|
216
|
-
description:
|
225
|
+
description: option.context_free_description || parse_markdown_and_strip_tags(option.description),
|
226
|
+
child_question_keys: option.questions.map(&:key).presence
|
217
227
|
}.compact
|
218
228
|
]
|
219
229
|
}
|
@@ -245,8 +255,17 @@ module Quby
|
|
245
255
|
}
|
246
256
|
end
|
247
257
|
|
258
|
+
def parse_markdown_and_strip_tags(markdown)
|
259
|
+
strip_tags_without_html_encode(Quby::MarkdownParser.new(markdown).to_html)
|
260
|
+
end
|
261
|
+
|
248
262
|
def strip_tags_without_html_encode(html)
|
249
|
-
Nokogiri::HTML(html).
|
263
|
+
Nokogiri::HTML(html).tap do |doc|
|
264
|
+
doc.css('br, hr, img').each { |node| node.replace(' ') }
|
265
|
+
end \
|
266
|
+
.text
|
267
|
+
.gsub(/\s+/, ' ')
|
268
|
+
.presence
|
250
269
|
end
|
251
270
|
end
|
252
271
|
end
|
@@ -100,6 +100,9 @@ module Quby
|
|
100
100
|
def validate_question_options(questionnaire, question)
|
101
101
|
question.options.each do |option|
|
102
102
|
msg_base = "Question #{option.question.key} option #{option.key}"
|
103
|
+
unless option.valid?
|
104
|
+
fail "#{msg_base} #{option.errors.full_messages.to_sentence}"
|
105
|
+
end
|
103
106
|
to_be_hidden_questions_exist_and_not_subquestion?(questionnaire, option, msg_base: msg_base)
|
104
107
|
to_be_shown_questions_exist_and_not_subquestion?(questionnaire, option, msg_base: msg_base)
|
105
108
|
end
|
@@ -40,7 +40,7 @@ module Quby
|
|
40
40
|
when :select
|
41
41
|
d_qtypes[question.key.to_s] = { type: :discrete }
|
42
42
|
for option in question.options
|
43
|
-
d_qtypes[question.key.to_s][option.value.to_s] = strip_p_tag(option.context_free_description || "") unless option.placeholder
|
43
|
+
d_qtypes[question.key.to_s][option.value.to_s] = strip_p_tag(option.context_free_description || option.description || "") unless option.placeholder
|
44
44
|
end
|
45
45
|
update_hidden_questions_for(question)
|
46
46
|
when :check_box
|
@@ -49,16 +49,19 @@ module Quby
|
|
49
49
|
next if option.inner_title
|
50
50
|
vars << option.key.to_s
|
51
51
|
if question.hidden
|
52
|
-
question_titles[option.key.to_s] = strip_tags
|
52
|
+
question_titles[option.key.to_s] = strip_tags(question.context_free_title_or_title)
|
53
53
|
end
|
54
54
|
value = 1
|
55
55
|
option_type = { type: :discrete }
|
56
|
-
option_type[value.to_s] = (option.context_free_description || "")
|
56
|
+
option_type[value.to_s] = (option.context_free_description || option.description || "")
|
57
57
|
option_type[:depends] = { values: [value, value.to_s].uniq, variable: option.key.to_s } unless options[:without_depends]
|
58
58
|
d_qtypes[option.key.to_s] = option_type
|
59
59
|
values = [value, value.to_s].uniq
|
60
60
|
handle_subquestions(question, question_titles, d_qtypes, vars, option, values, option.key.to_s)
|
61
61
|
end
|
62
|
+
if question.title_question
|
63
|
+
subquestion(question, question_titles, d_qtypes, vars, question.title_question, nil, nil)
|
64
|
+
end
|
62
65
|
update_hidden_questions_for(question, for_checkbox: true)
|
63
66
|
when :textarea
|
64
67
|
d_qtypes[question.key.to_s] = { type: :text_field }
|
@@ -72,7 +75,7 @@ module Quby
|
|
72
75
|
end
|
73
76
|
when :hidden
|
74
77
|
if question.options.blank? # string
|
75
|
-
question_titles[question.key.to_s] = strip_tags
|
78
|
+
question_titles[question.key.to_s] = strip_tags(question.context_free_title_or_title)
|
76
79
|
vars << question.key.to_s unless vars.include? question.key.to_s
|
77
80
|
d_qtypes[question.key.to_s] = { type: :text }
|
78
81
|
d_qtypes[question.key.to_s][:depends] = :present unless options[:without_depends]
|
@@ -85,16 +88,16 @@ module Quby
|
|
85
88
|
next if option.inner_title
|
86
89
|
d_qtypes[question.key.to_s] ||= { type: :scale }
|
87
90
|
values << option.value.to_s
|
88
|
-
d_qtypes[question.key.to_s][option.value.to_s] = strip_p_tag(option.context_free_description || "")
|
91
|
+
d_qtypes[question.key.to_s][option.value.to_s] = strip_p_tag(option.context_free_description || option.description || "")
|
89
92
|
# TODO: missing sub-questions
|
90
93
|
else # check_box
|
91
94
|
d_qtypes[question.key.to_s] ||= { type: :check_box }
|
92
95
|
no_keys = false
|
93
|
-
question_titles[option.key.to_s] = strip_tags
|
96
|
+
question_titles[option.key.to_s] = strip_tags(question.context_free_title_or_title)
|
94
97
|
vars << option.key.to_s
|
95
98
|
value = option.value || 1
|
96
99
|
option_type = { type: :discrete }
|
97
|
-
option_type[value.to_s] = (option.context_free_description || "")
|
100
|
+
option_type[value.to_s] = (option.context_free_description || option.description || "")
|
98
101
|
option_type[:depends] = { values: [value, value.to_s].uniq, variable: option.key.to_s } unless options[:without_depends]
|
99
102
|
d_qtypes[option.key.to_s] = option_type
|
100
103
|
# TODO: missing sub-questions
|
@@ -102,7 +105,7 @@ module Quby
|
|
102
105
|
end
|
103
106
|
if no_keys # scale or radio
|
104
107
|
d_qtypes[question.key.to_s][:depends] = { values: values, variable: question.key.to_s } unless options[:without_depends]
|
105
|
-
question_titles[question.key.to_s] = strip_tags
|
108
|
+
question_titles[question.key.to_s] = strip_tags(question.context_free_title_or_title)
|
106
109
|
end
|
107
110
|
end
|
108
111
|
else
|
@@ -191,7 +194,7 @@ module Quby
|
|
191
194
|
|
192
195
|
for question in questions_flat
|
193
196
|
unless question.hidden && (question.type == :check_box || question.type == :hidden)
|
194
|
-
title = question.
|
197
|
+
title = question.context_free_title_or_title || question.description || ""
|
195
198
|
question_titles[question.key.to_s] = strip_tags(title)
|
196
199
|
end
|
197
200
|
end
|
@@ -237,24 +240,24 @@ module Quby
|
|
237
240
|
end
|
238
241
|
end
|
239
242
|
d_qtypes[quest.key.to_s][:label] = quest.unit unless quest.unit.blank?
|
240
|
-
quests[quest.key.to_s] = strip_tags(quest.context_free_title || "")
|
243
|
+
quests[quest.key.to_s] = strip_tags(quest.context_free_title || quest.title || "")
|
241
244
|
vars << quest.key.to_s
|
242
245
|
end
|
243
246
|
|
244
247
|
def sub_textfield(question, quests, d_qtypes, vars, quest, values, key)
|
245
248
|
d_qtypes[quest.key.to_s] = { type: :text_field }
|
246
249
|
d_qtypes[quest.key.to_s][:depends] = { values: values, variable: key } unless options[:without_depends]
|
247
|
-
quests[quest.key.to_s] = strip_tags(quest.context_free_title || "")
|
250
|
+
quests[quest.key.to_s] = strip_tags(quest.context_free_title || quest.title || "")
|
248
251
|
vars << quest.key.to_s
|
249
252
|
end
|
250
253
|
|
251
254
|
def sub_radio(question, quests, d_qtypes, vars, quest, values, key)
|
252
255
|
d_qtypes[quest.key.to_s] = { type: :scale }
|
253
256
|
d_qtypes[quest.key.to_s][:depends] = { values: values, variable: key } unless options[:without_depends]
|
254
|
-
quests[quest.key.to_s] = strip_tags(quest.context_free_title || "")
|
257
|
+
quests[quest.key.to_s] = strip_tags(quest.context_free_title || quest.title || "")
|
255
258
|
for option in quest.options
|
256
259
|
next if option.inner_title
|
257
|
-
d_qtypes[quest.key.to_s][option.value.to_s] = strip_p_tag(option.context_free_description || "")
|
260
|
+
d_qtypes[quest.key.to_s][option.value.to_s] = strip_p_tag(option.context_free_description || option.description || "")
|
258
261
|
end
|
259
262
|
vars << quest.key.to_s
|
260
263
|
update_hidden_questions_for(quest)
|
@@ -266,7 +269,7 @@ module Quby
|
|
266
269
|
vars << key
|
267
270
|
hash[component] = key.to_s
|
268
271
|
end
|
269
|
-
quests[quest.key.to_s] = strip_tags(quest.context_free_title || "")
|
272
|
+
quests[quest.key.to_s] = strip_tags(quest.context_free_title || quest.title || "")
|
270
273
|
end
|
271
274
|
|
272
275
|
def handle_scale(question, quests, d_qtypes, vars)
|
@@ -275,11 +278,14 @@ module Quby
|
|
275
278
|
update_hidden_questions_for(question)
|
276
279
|
for option in question.options
|
277
280
|
next if option.inner_title
|
278
|
-
d_qtypes[question.key.to_s][option.value.to_s] = strip_p_tag(option.context_free_description || "")
|
281
|
+
d_qtypes[question.key.to_s][option.value.to_s] = strip_p_tag(option.context_free_description || option.description || "")
|
279
282
|
values << option.value.to_s
|
280
283
|
key = question.key.to_s
|
281
284
|
handle_subquestions(question, quests, d_qtypes, vars, option, [option.value.to_s], key)
|
282
285
|
end
|
286
|
+
if question.title_question
|
287
|
+
subquestion(question, quests, d_qtypes, vars, question.title_question, nil, nil)
|
288
|
+
end
|
283
289
|
end
|
284
290
|
|
285
291
|
def handle_textfield(question, d_qtypes)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quby-compiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marten Veldthuis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|