class Quby::Compiler::Entities::Questionnaire

Constants

RESPONDENT_TYPES
VALID_LICENSES

Attributes

abortable[RW]
allow_hotkeys[RW]
allow_switch_to_bulk[RW]
charts[RW]
check_key_clashes[RW]

whether to check for clashes between question input keys (HTML form keys)

check_score_keys_consistency[RW]

whether to check consistency of score subkeys during seed generation

deactivate_answers_requested_at[RW]
default_answer_value[RW]
description[RW]
enable_previous_questionnaire_button[RW]
extra_css[RW]
fields[R]
flags[RW]
key[RW]
language[RW]
last_author[RW]
last_update[RW]
leave_page_alert[RW]
license[RW]
licensor[RW]
lookup_tables[RW]
outcome_description[RW]
outcome_regeneration_requested_at[RW]
outcome_tables[RW]
panels[RW]
renderer_version[RW]
respondent_types[RW]
roqua_keys[RW]
sbg_domains[RW]
sbg_key[RW]
score_calculations[RW]
score_schemas[RW]
seeds_patch[RW]
short_description[RW]
tags[R]
textvars[RW]
title[RW]
validate_html[RW]

If false, we don't check html for validity (for mate1 and mate1_pre)

versions[RW]

Public Class Methods

new(key, last_update: Time.now) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 32
def initialize(key, last_update: Time.now)
  @key = key
  @sbg_domains = []
  @last_update = Time.at(last_update.to_i)
  @score_calculations = {}.with_indifferent_access
  @charts = Charting::Charts.new
  @fields = Fields.new(self)
  @license = :unknown
  @renderer_version = :v1
  @extra_css = ""
  @allow_switch_to_bulk = false
  @panels = []
  @flags = {}.with_indifferent_access
  @textvars = {}.with_indifferent_access
  @language = :nl
  @respondent_types = []
  @tags = OpenStruct.new
  @check_key_clashes = true
  @validate_html = true
  @score_schemas = {}.with_indifferent_access
  @outcome_tables = []
  @check_score_keys_consistency = true
  @lookup_tables = {}
  @versions = []
  @seeds_patch = {}
end

Public Instance Methods

actions() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 266
def actions
  score_calculations.values.select(&:action)
end
add_chart(chart) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 274
def add_chart(chart)
  charts.add chart
end
add_flag(flag_options) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 278
def add_flag(flag_options)
  if flag_options[:internal]
    flag_key = flag_options[:key].to_sym
  else
    flag_key = "#{key}_#{flag_options[:key]}".to_sym
  end
  flag_options[:key] = flag_key
  fail(ArgumentError, "Flag '#{flag_key}' already defined") if flags.key?(flag_key)
  flags[flag_key] = Flag.new(**flag_options)
end
add_outcome_table(outcome_table_options) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 403
def add_outcome_table(outcome_table_options)
  outcome_tables << OutcomeTable.new(**outcome_table_options, questionnaire: self)
end
add_panel(panel) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 131
def add_panel(panel)
  @panels << panel
end
add_score_calculation(builder) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 247
def add_score_calculation(builder)
  if score_calculations.key?(builder.key)
    fail InputKeyAlreadyDefined, "Score key `#{builder.key}` already defined."
  end
  score_calculations[builder.key] = builder
end
add_score_schema(score_schema) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 254
def add_score_schema(score_schema)
  score_schemas[score_schema.key] = score_schema
end
add_textvar(textvar_options) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 295
def add_textvar(textvar_options)
  textvar_key = "#{key}_#{textvar_options.fetch(:key)}".to_sym
  textvar_options[:key] = textvar_key
  validate_textvar_keys_unique(textvar_key)
  validate_depends_on_flag(textvar_key, textvar_options)
  textvars[textvar_key] = Textvar.new(**textvar_options)
end
answer_dsl_module() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 315
def answer_dsl_module # rubocop:disable Metrics/MethodLength
  # Have to put this in a local variable so the module definition block can access it
  questions_in_var = questions

  @answer_dsl_cache ||= Module.new do
    questions_in_var.each do |question|
      next if question&.key.blank?
      case question.type
      when :date
        question.components.each do |component|
          # assignment to 'value' hash must be done under string keys
          key = question.send("#{component}_key").to_s
          define_method(key) do
            self.value ||= Hash.new
            self.value[key]
          end

          define_method("#{key}=") do |v|
            self.value ||= Hash.new
            self.value[key] = v&.strip
          end
        end

        define_method(question.key) do
          self.value ||= Hash.new

          components = question.components.sort
          component_values = components.map do |component|
            value_key = question.send("#{component}_key").to_s
            self.value[value_key]
          end
          case components
          when [:day, :month, :year]
            component_values.reverse.take_while { |p| p.present? }.reverse.join('-')
          when [:month, :year]
            component_values.reject(&:blank?).join('-')
          when [:hour, :minute]
            component_values.all?(&:blank?) ? '' : component_values.join(':')
          end
        end

      when :check_box

        define_method(question.key) do
          self.value ||= Hash.new
          self.value[question.key.to_s] ||= Hash.new
        end

        question.options.each do |opt|
          next if opt&.key.blank?
          define_method("#{opt.key}") do
            self.value ||= Hash.new
            self.value[question.key.to_s] ||= Hash.new
            self.value[opt.key.to_s] ||= 0
          end

          define_method("#{opt.key}=") do |v|
            v = v.to_i
            self.value ||= Hash.new
            self.value[question.key.to_s] ||= Hash.new
            self.value[question.key.to_s][opt.key.to_s] = v
            self.value[opt.key.to_s] = v
          end
        end
      else
        # Includes:
        # question.type == :radio
        # question.type == :scale
        # question.type == :select
        # question.type == :string
        # question.type == :textarea
        # question.type == :integer
        # question.type == :float

        define_method(question.key) do
          self.value ||= Hash.new
          self.value[question.key.to_s]
        end

        define_method(question.key.to_s + "=") do |v|
          self.value ||= Hash.new
          self.value[question.key.to_s] = v
        end
      end rescue nil
    end
  end
end
as_json(options = {}) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 190
def as_json(options = {})
  {
    key: key,
    title: title,
    description: description,
    outcomeDescription: outcome_description,
    shortDescription: short_description,
    panels: panels,
    fields: fields,
    flags: flags,
    textvars: textvars,
    validations: validations,
    visibilityRules: visibility_rules
  }
end
callback_after_dsl_enhance_on_questions() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 143
def callback_after_dsl_enhance_on_questions
  question_hash.each_value do |q|
    q.run_callbacks :after_dsl_enhance
  end
  ensure_scores_have_schemas if Quby::Settings.require_score_schemas
end
completion() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 270
def completion
  score_calculations.values.select(&:completion).first
end
default_textvars() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 309
def default_textvars
  textvars.select { |key, textvar| textvar.default.present? }
          .map    { |key, textvar| [key, textvar.default] }
          .to_h
end
ensure_scores_have_schemas() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 150
def ensure_scores_have_schemas
  missing_schemas = scores.map(&:key).map(&:to_s) - score_schemas.keys
  missing_schemas.each do |key|
    errors.add "Score #{key}", 'is missing a score schema'
  end
end
filter_flags(given_flags) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 289
def filter_flags(given_flags)
  given_flags.select do |flag_key, _|
    flags.key? flag_key
  end
end
filter_textvars(given_textvars) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 303
def filter_textvars(given_textvars)
  given_textvars.select do |textvar_key, _|
    textvars.key? textvar_key
  end
end
find_plottable(key) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 262
def find_plottable(key)
  score_calculations[key] || question_hash.with_indifferent_access[key]
end
key_in_use?(key) click to toggle source

rubocop:enable Metrics/MethodLength

# File lib/quby/compiler/entities/questionnaire.rb, line 243
def key_in_use?(key)
  fields.key_in_use?(key) || score_calculations.key?(key)
end
license=(type) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 185
def license=(type)
  fail ArgumentError, 'Invalid license' unless VALID_LICENSES.include?(type)
  @license = type
end
questions() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 177
def questions
  question_hash.values
end
questions_of_type(type) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 181
def questions_of_type(type)
  questions.select { |question| question.type == type }
end
questions_tree() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 165
def questions_tree
  return @questions_tree_cache if @questions_tree_cache

  recurse = lambda do |question|
    [question, question.subquestions.map(&recurse)]
  end

  @questions_tree_cache = (@panels && @panels.map do |panel|
    panel.items.map { |item| recurse.call(item) if item.is_a?(Quby::Compiler::Entities::Question) }
  end)
end
register_question(question) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 135
def register_question(question)
  fields.add(question)

  if question.sets_textvar && !textvars.key?(question.sets_textvar)
    fail "Undefined textvar: #{question.sets_textvar}"
  end
end
scores() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 258
def scores
  score_calculations.values.select(&:score)
end
tags=(tags) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 108
def tags=(tags)
  tags.each do |tag|
    @tags[tag] = true
  end
end
to_codebook(options = {}) click to toggle source

rubocop:disable Metrics/MethodLength

# File lib/quby/compiler/entities/questionnaire.rb, line 207
def to_codebook(options = {})
  output = []
  output << title
  output << "Date unknown"
  output << ""

  options[:extra_vars]&.each do |var|
    output << "#{var[:key]} #{var[:type]}"
    output << "\"#{var[:description]}\""
    output << ""
  end

  top_questions = panels.map do |panel|
    panel.items.select { |item| item.is_a? Question }
  end.flatten.compact

  top_questions.each do |question|
    output << question.to_codebook(self, options)
    output << ""
  end

  flags.each_value do |flag|
    output << flag.to_codebook(options)
    output << ""
  end

  textvars.each_value do |textvar|
    output << textvar.to_codebook(options)
    output << ""
  end

  output = output.join("\n")
  strip_tags(output.gsub(/\<([ 1-9])/, '&lt;\1')).gsub("&lt;", "<")
end
to_param() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 127
def to_param
  key
end
validate_questions() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 157
def validate_questions
  question_hash.each_value do |q|
    unless q.valid?
      q.errors.each { |attr, err| errors.add(attr, err) }
    end
  end
end
validations() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 407
def validations
  @validations ||= fields.question_hash.values.flat_map do |question|
    question.validations.map do |validation|
      case validation[:type]
      when :answer_group_minimum, :answer_group_maximum
        Validation.new(validation.merge(field_keys: questions.select {|q| q.question_group == validation[:group]}.map(&:key)))
      else
        Validation.new(validation.merge(field_key: question.key))
      end
    end
  end.uniq(&:config)
end
visibility_rules() click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 420
def visibility_rules
  @visibility_rules ||= fields.question_hash.values.flat_map { |question| VisibilityRule.from(question) } \
                      + flags.values.flat_map { |flag| VisibilityRule.from_flag(flag) }
end

Private Instance Methods

validate_depends_on_flag(textvar_key, textvar_options) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 427
def validate_depends_on_flag(textvar_key, textvar_options)
  if textvar_options[:depends_on_flag].present? && !flags.key?(textvar_options[:depends_on_flag])
    fail(ArgumentError,
         "Textvar '#{textvar_key}' depends on nonexistent flag '#{textvar_options[:depends_on_flag]}'")
  end
end
validate_textvar_keys_unique(textvar_key) click to toggle source
# File lib/quby/compiler/entities/questionnaire.rb, line 434
def validate_textvar_keys_unique(textvar_key)
  fail(ArgumentError, "Textvar '#{textvar_key}' already defined") if textvars.key?(textvar_key)
end