quby-compiler 0.4.5 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|