lib/quby/answers/entities/answer.rb
# frozen_string_literal: true
require 'active_model'
require 'quby/answers/entities/outcome'
require 'quby/answers/dsl'
module Quby
module Answers
module Entities
class Answer
extend ActiveModel::Naming
extend ActiveModel::Translation
# @return [String]
attr_accessor :_id
# @return [String]
attr_accessor :questionnaire_id
# @return [String]
attr_accessor :questionnaire_key
# The raw form data (for recovery purposes)
# @return [Hash]
attr_accessor :raw_params
# The filtered and transformed form data
# @return [Hash]
attr_accessor :value
# @return [String]
attr_accessor :patient_id
# @return [Hash]
attr_accessor :patient
# @return [String]
attr_accessor :token
# @return [Boolean]
attr_accessor :active
# @return [Boolean]
attr_accessor :test
# @return [Time]
attr_accessor :created_at
# @return [Time]
attr_accessor :updated_at
# @return [Time]
attr_accessor :started_at
# @return [Time]
attr_accessor :completed_at
# @return [Outcome]
attr_accessor :outcome
# @return [Time]
attr_accessor :dsl_last_update
# For answers that are imported from external sources
# @return [Hash]
attr_accessor :import_notes
# @return [Hash<String, Boolean>]
attr_accessor :flags
# @return [Hash<String, String>]
attr_accessor :textvars
attr_accessor :outcome_generated_at
attr_writer :scores
attr_writer :actions
attr_writer :completion
attr_accessor :aborted
# For setting raw content values and failed validations
attr_accessor :extra_question_values
attr_accessor :extra_failed_validations
def initialize(_id: nil, questionnaire_id: nil, questionnaire_key: nil, questionnaire: nil,
raw_params: nil, value: nil, patient_id: nil, patient: nil,
token: nil, active: true, test: false, created_at: nil, updated_at: nil,
started_at: nil, completed_at: nil, outcome: nil, outcome_generated_at: nil,
scores: nil, actions: nil, completion: nil, dsl_last_update: nil, import_notes: nil,
flags: nil, textvars: nil)
self._id = _id
self.questionnaire_id = questionnaire_id
self.questionnaire_key = questionnaire_key
self.raw_params = raw_params || {}
self.value = value || {}
self.patient_id = patient_id
self.patient = patient || {}
self.token = token
self.active = active
self.test = test
self.created_at = created_at
self.updated_at = updated_at
self.started_at = started_at
self.completed_at = completed_at
self.outcome_generated_at = outcome_generated_at
self.scores = scores || {}
self.actions = actions || {}
self.completion = completion || {}
self.dsl_last_update = dsl_last_update
self.import_notes = import_notes || {}
self.flags = flags
self.textvars = textvars
@questionnaire = questionnaire
end
def id
_id
end
def _id=(value)
@_id = value.to_s
end
def to_param
id
end
def attributes
HashWithIndifferentAccess.new({
_id: _id,
questionnaire_id: questionnaire_id,
questionnaire_key: questionnaire_key,
raw_params: raw_params,
value: value,
patient_id: patient_id,
patient: patient,
token: token,
active: active,
test: test,
created_at: created_at,
updated_at: updated_at,
started_at: started_at,
completed_at: completed_at,
outcome_generated_at: outcome_generated_at,
scores: scores,
actions: actions,
completion: completion,
dsl_last_update: dsl_last_update,
import_notes: import_notes,
flags: flags,
textvars: textvars
})
end
def errors
@errors ||= ActiveModel::Errors.new(self)
end
def valid?
errors.empty?
end
# Faux belongs_to :questionnaire
def questionnaire
@questionnaire ||= Quby.questionnaires.find(questionnaire_key)
end
def mark_completed(start_time)
if completed? || @aborted
self.started_at = start_time if started_at.blank?
self.completed_at = Time.now if completed_at.blank?
end
end
def enhance_by_dsl
DSL.enhance(self)
end
def patient_id
patient[:id] || @patient_id
end
def extra_question_values
@extra_question_values = {}
questionnaire.questions.each do |q|
next unless q
unless q.raw_content.blank?
@extra_question_values[q.key] = send(q.key)
end
end
@extra_question_values.to_json
end
def extra_failed_validations
@extra_failed_validations = {}
questionnaire.questions.each do |q|
next unless q
unless q.raw_content.blank?
@extra_failed_validations[q.key] = errors[q.key] if errors[q.key].present?
end
end
@extra_failed_validations.to_json
end
def value_by_values
result = {}
if value
result = value.dup
value.each_key do |key|
question = questionnaire.questions.find { |q| q&.key.to_s == key.to_s }
if question and (question.type == :radio || question.type == :scale || question.type == :select)
option = question.options.find { |o| o.key.to_s == value[key].to_s }
if option
result[key] = option.value.to_s
end
end
end
end
result
rescue Exception => e
if defined? Roqua::Support::Errors
Roqua::Support::Errors.report e, root_path: Rails.root.to_s
end
raise e if Rails.env.development? || Rails.env.test?
Rails.logger.error "RESCUED #{e.message} \n #{e.backtrace.join('\n')}"
{}
end
def value_by_regular_values
result = {}
if value
result = value.dup
value.each do |key, answer|
question = questionnaire.questions.find { |q| q&.key.to_s == key.to_s }
next unless question
if question.type == :radio || question.type == :scale || question.type == :select
option = question.options.find { |o| o.key.to_s == value[key].to_s }
result[key] = option.value if option
elsif question.type == :integer
result[key] = answer.to_i if answer
elsif question.type == :float
result[key] = answer.to_f if answer
end
end
end
result
rescue Exception => e
if defined? Roqua::Support::Errors
Roqua::Support::Errors.report e, root_path: Rails.root.to_s
end
raise e if Rails.env.development? || Rails.env.test?
Rails.logger.error "RESCUED #{e.message} \n #{e.backtrace.join('\n')}"
{}
end
def outcome
Outcome.new(scores: @scores, actions: @actions, completion: @completion, generated_at: @outcome_generated_at)
end
def outcome=(outcome)
self.scores = outcome.scores
self.actions = outcome.actions
self.completion = outcome.completion
self.outcome_generated_at = outcome.generated_at
end
def scores
outcome.scores
end
def score_objects
scores.map do |score_key, score_hash|
score = Score.new score_schema: questionnaire.score_schemas[score_key],
score_hash: score_hash
[score_key, score]
end.to_h.with_indifferent_access
end
def actions
outcome.actions
end
def completion
outcome.completion
end
def action
outcome.action
end
def flags=(value)
return unless value
@flags = value.symbolize_keys
end
def textvars=(value)
return unless value
@textvars = value.symbolize_keys
end
def as_json(options = {})
attributes.merge(
id: id,
value_by_values: value_by_values,
scores: scores,
is_completed: self.completed? ? true : false
)
end
def completed?
!all_blank? && valid?
end
def all_blank?
questionnaire.questions.reduce(true) do |all_blank, question|
next all_blank unless question
all_blank and send(question.key).blank?
end
end
def url_params(options = {})
timestamp = Time.now.getgm.strftime("%Y-%m-%dT%H:%M:%S+00:00")
plain_token = [Quby::Settings.shared_secret, token, timestamp].join('|')
# double slash removed from return_url (it's either this or removing the
# final slash in Settings.application_url)
options.merge(
display_mode: options[:display_mode] || "paged",
token: token,
timestamp: timestamp,
hmac: Digest::SHA1.hexdigest(plain_token)
)
end
protected
def calc_answered(qkeys)
answered = 0
qkeys.each do |qk|
ans = send(qk)
if ans.is_a? Hash # in case of check_box, only count checked check_boxes as answered
answered += (ans.values.sum >= 1 ? 1 : 0)
elsif send(qk).present?
answered += 1
end
end
answered
end
def add_error(question, validationtype, message)
errors.add(question.key, {message: message, valtype: validationtype})
end
end
end
end
end