Class: Quby::Compiler::Services::DefinitionValidator

Inherits:
ActiveModel::Validator
  • Object
show all
Defined in:
lib/quby/compiler/services/definition_validator.rb

Constant Summary collapse

MAX_KEY_LENGTH =
19
KEY_PREFIX =
'v_'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#definitionObject (readonly)

Returns the value of attribute definition.



13
14
15
# File 'lib/quby/compiler/services/definition_validator.rb', line 13

def definition
  @definition
end

#questionnaireObject (readonly)

Returns the value of attribute questionnaire.



14
15
16
# File 'lib/quby/compiler/services/definition_validator.rb', line 14

def questionnaire
  @questionnaire
end

Class Method Details

.check_duplicate_headers(seed) ⇒ Object



234
235
236
237
238
239
240
# File 'lib/quby/compiler/services/definition_validator.rb', line 234

def self.check_duplicate_headers(seed)
  return
  # TODO
  # column_headers = DataExport::QuestionnaireHeaders.new(questionnaire).headers
  # duplicate_header_names = column_headers.find_all { |e| column_headers.rindex(e) != column_headers.index(e) }.uniq
  # raise "key clashes for: #{duplicate_header_names}" if duplicate_header_names.present?
end

.check_score_keys_consistency(seed) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/quby/compiler/services/definition_validator.rb', line 218

def self.check_score_keys_consistency(seed)
  score_keys = seed["properties"][:score_keys]
  most_keys = score_keys.map { |score| keys_for_score(score).join }.max_by(&:length)

  faulty_scores = score_keys.reject do |score|
    most_keys.starts_with? keys_for_score(score).join
  end

  if faulty_scores.present?
    raise "scores mismatch other scores, check if this was intentional: #{faulty_scores}

If this was intentional, rerun quby proxy with the flag '--skip_score_keys_consistency_check' *and* manually add \
scores_schema tables to the resulting seed."
  end
end

Instance Method Details

#ensure_valid_descriptions(questionnaire, flag) ⇒ Object



138
139
140
141
142
# File 'lib/quby/compiler/services/definition_validator.rb', line 138

def ensure_valid_descriptions(questionnaire, flag)
  unless (flag.description_false.present? && flag.description_true.present?) || flag.description.present?
    fail ArgumentError, "Flag '#{flag.key}' Requires at least either both description_true and description_false or a description"
  end
end

#subquestions_cant_have_default_invisible(question) ⇒ Object



212
213
214
215
216
# File 'lib/quby/compiler/services/definition_validator.rb', line 212

def subquestions_cant_have_default_invisible(question)
  if question.subquestion? && question.default_invisible
    fail "Question #{question.key} is a subquestion with default_invisible."
  end
end

#to_be_hidden_questions_exist_and_not_subquestion?(questionnaire, option, msg_base:) ⇒ Boolean

Returns:

  • (Boolean)


194
195
196
197
198
199
200
201
# File 'lib/quby/compiler/services/definition_validator.rb', line 194

def to_be_hidden_questions_exist_and_not_subquestion?(questionnaire, option, msg_base:)
  return if option.hides_questions.blank?
  msg_base += " hides_questions"
  option.hides_questions.each do |key|
    validate_question_key_exists?(questionnaire, key, msg_base: msg_base)
    validate_not_subquestion(questionnaire, key, msg_base: msg_base)
  end
end

#to_be_shown_questions_exist_and_not_subquestion?(questionnaire, option, msg_base:) ⇒ Boolean

Returns:

  • (Boolean)


203
204
205
206
207
208
209
210
# File 'lib/quby/compiler/services/definition_validator.rb', line 203

def to_be_shown_questions_exist_and_not_subquestion?(questionnaire, option, msg_base:)
  return if option.shows_questions.blank?
  msg_base += " shows_questions"
  option.shows_questions.each do |key|
    validate_question_key_exists?(questionnaire, key, msg_base: msg_base)
    validate_not_subquestion(questionnaire, key, msg_base: msg_base)
  end
end

#validate(definition) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/quby/compiler/services/definition_validator.rb', line 16

def validate(definition)
  questionnaire = DSL.build_from_definition(definition)
  (questionnaire)
  validate_fields(questionnaire)
  validate_questions(questionnaire)
  validate_scores(questionnaire)
  validate_table_edgecases(questionnaire)
  validate_flags(questionnaire)
  validate_key_uniqueness(questionnaire)
  validate_respondent_types(questionnaire)
  validate_outcome_tables(questionnaire)
  validate_markdown_fields(questionnaire) if questionnaire.validate_html
  validate_raw_content_items(questionnaire) if questionnaire.validate_html
# Some compilation errors are Exceptions (pure syntax errors) and some StandardErrors (NameErrors)
rescue Exception => exception # rubocop:disable Lint/RescueException
  definition.errors.add(:sourcecode, message: "Questionnaire error: #{definition.key}\n" \
                                              "#{exception.message}",
                                     backtrace: exception.backtrace[0..20])
end

#validate_fields(questionnaire) ⇒ Object



50
51
52
53
54
55
56
57
# File 'lib/quby/compiler/services/definition_validator.rb', line 50

def validate_fields(questionnaire)
  questionnaire.fields.input_keys
                      .find { |k| !k.is_a?(Symbol) }
                     &.tap { |k| fail "Input key #{k} is not a symbol" }
  questionnaire.fields.answer_keys
                      .find { |k| !k.is_a?(Symbol) }
                     &.tap { |k| fail "Answer key #{k} is not a symbol" }
end

#validate_flag_depends_on(questionnaire, flag) ⇒ Object



158
159
160
161
162
# File 'lib/quby/compiler/services/definition_validator.rb', line 158

def validate_flag_depends_on(questionnaire, flag)
  return if (missing = flag.depends_on - questionnaire.flags.keys).blank?

  fail ArgumentError, "Flag #{flag.key} depends_on nonexistent flag '#{missing.to_sentence}'"
end

#validate_flag_hides(questionnaire, flag) ⇒ Object



151
152
153
154
155
156
# File 'lib/quby/compiler/services/definition_validator.rb', line 151

def validate_flag_hides(questionnaire, flag)
  unknown_questions = flag.hides_questions.select { |key| !questionnaire.key_in_use?(key) }
  return if unknown_questions.blank?

  fail ArgumentError, "Flag '#{flag.key}' has unknown hides_questions keys #{unknown_questions}"
end

#validate_flag_shows(questionnaire, flag) ⇒ Object



144
145
146
147
148
149
# File 'lib/quby/compiler/services/definition_validator.rb', line 144

def validate_flag_shows(questionnaire, flag)
  unknown_questions = flag.shows_questions.select { |key| !questionnaire.key_in_use?(key) }
  return if unknown_questions.blank?

  fail ArgumentError, "Flag '#{flag.key}' has unknown shows_questions keys #{unknown_questions}"
end

#validate_flags(questionnaire) ⇒ Object



129
130
131
132
133
134
135
136
# File 'lib/quby/compiler/services/definition_validator.rb', line 129

def validate_flags(questionnaire)
  questionnaire.flags.each_value do |flag|
    ensure_valid_descriptions(questionnaire, flag)
    validate_flag_shows(questionnaire, flag)
    validate_flag_hides(questionnaire, flag)
    validate_flag_depends_on(questionnaire, flag)
  end
end

#validate_key_uniqueness(questionnaire) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
# File 'lib/quby/compiler/services/definition_validator.rb', line 164

def validate_key_uniqueness(questionnaire)
  keys = questionnaire.fields.answer_keys.to_a \
    + questionnaire.score_schemas.keys.map(&:to_sym) \
    + questionnaire.flags.keys.map { delete_prefix(_1, questionnaire).to_sym } \
    + questionnaire.textvars.keys.map { delete_prefix(_1, questionnaire).to_sym }

  return if keys.size == keys.uniq.size

  duplicates = keys.tally.filter_map { |k,v| k if v > 1 }
  fail ArgumentError, "Duplicate keys: #{duplicates.to_sentence}"
end

#validate_metadata(questionnaire) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/quby/compiler/services/definition_validator.rb', line 36

def (questionnaire)
  if questionnaire.title.blank?
    fail "Questionnaire title is missing."
  end

  if questionnaire.short_description && questionnaire.short_description.size > 255
    fail "Questionnaire short_description is too long."
  end

  if questionnaire.description && questionnaire.description.size > 255
    fail "Questionnaire description is too long."
  end
end

#validate_outcome_tables(questionnaire) ⇒ Object



187
188
189
190
191
192
# File 'lib/quby/compiler/services/definition_validator.rb', line 187

def validate_outcome_tables(questionnaire)
  questionnaire.outcome_tables.each do |table|
    next if table.valid?
    fail "Outcome table #{table.errors.full_messages}"
  end
end

#validate_presence_of_titles(question) ⇒ Object



112
113
114
115
116
117
# File 'lib/quby/compiler/services/definition_validator.rb', line 112

def validate_presence_of_titles(question)
  return if question.allow_blank_titles
  if !question.subquestion? && question.title.blank? && question.context_free_title.blank?
    fail "Question #{question.key} must define either `:title` or `:context_free_title`."
  end
end

#validate_question(question) ⇒ Object



77
78
79
80
81
# File 'lib/quby/compiler/services/definition_validator.rb', line 77

def validate_question(question)
  unless question.valid?
    fail "Question #{question.key} is invalid: #{question.errors.full_messages.join(', ')}"
  end
end

#validate_question_options(questionnaire, question) ⇒ Object



101
102
103
104
105
106
107
108
109
110
# File 'lib/quby/compiler/services/definition_validator.rb', line 101

def validate_question_options(questionnaire, question)
  question.options.each do |option|
    msg_base = "Question #{option.question.key} option #{option.key}"
    unless option.valid?
      fail "#{msg_base} #{option.errors.full_messages.to_sentence}"
    end
    to_be_hidden_questions_exist_and_not_subquestion?(questionnaire, option, msg_base: msg_base)
    to_be_shown_questions_exist_and_not_subquestion?(questionnaire, option, msg_base: msg_base)
  end
end

#validate_questions(questionnaire) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/quby/compiler/services/definition_validator.rb', line 59

def validate_questions(questionnaire)
  questionnaire.answer_keys.each do |key|
    validate_key_format(key)
  end

  questionnaire.question_hash.each_value do |question|
    validate_question(question)
    subquestions_cant_have_default_invisible question
    validate_subquestion_absence_in_select question
    validate_placeholder_options_nil_values question
    validate_values_unique question

    validate_question_options(questionnaire, question)
    validate_presence_of_titles question
    validate_no_spaces_before_question_nr_in_title question
  end
end

#validate_respondent_types(questionnaire) ⇒ Object



176
177
178
179
180
181
182
183
184
185
# File 'lib/quby/compiler/services/definition_validator.rb', line 176

def validate_respondent_types(questionnaire)
  valid_respondent_types = Entities::Questionnaire::RESPONDENT_TYPES

  invalid_types = questionnaire.respondent_types - valid_respondent_types

  if invalid_types.present?
    fail "Invalid respondent types: :#{invalid_types.join(', :')}\n"\
         "Choose one or more from: :#{valid_respondent_types.join(', :')}"
  end
end

#validate_scores(questionnaire) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/quby/compiler/services/definition_validator.rb', line 83

def validate_scores(questionnaire)
  questionnaire.scores.each do |score|
    validate_score_key_length(score)
    validate_score_label_present(score)

    score_schema = questionnaire.score_schemas[score.key]
    fail "Score #{score.key} does not have a score schema" unless score_schema
    fail "Score label langer dan 100 tekens (geeft problemen oru accare)\n #{score_schema.label}" if score_schema.label&.length > 100
  end

  export_keys = questionnaire.score_schemas.flat_map { |_key, score_schema|
    score_schema.subscore_schemas.map(&:export_key)
  }

  duplicate_export_keys = export_keys.tally.select { |key, count| count > 1 }.keys
  fail "Score export keys not unique, duplicates: #{duplicate_export_keys}" if duplicate_export_keys.present?
end

#validate_table_edgecases(questionnaire) ⇒ Object



119
120
121
122
123
124
125
126
127
# File 'lib/quby/compiler/services/definition_validator.rb', line 119

def validate_table_edgecases(questionnaire)
  questionnaire.panels.each do |panel|
    tables = panel.items.select { |item| item.is_a?(Entities::Table) }
    tables.each do |table|
      questions = table.items.select { |item| item.is_a?(Entities::Question) }
      questions.each { |question| validate_table_question(question) }
    end
  end
end