openpprn/opn

View on GitHub
app/models/surveys/answer_session.rb

Summary

Maintainability
A
3 hrs
Test Coverage
class AnswerSession < ActiveRecord::Base
  belongs_to :question_flow
  belongs_to :first_answer, class_name: "Answer", foreign_key: "first_answer_id"
  belongs_to :last_answer, class_name: "Answer", foreign_key: "last_answer_id"
  belongs_to :user
  has_many :answer_edges

  def self.most_recent(question_flow_id, user_id)
    answer_sessions = AnswerSession.where(question_flow_id: question_flow_id, user_id: user_id).order(updated_at: :desc)
    answer_sessions.empty? ? nil : answer_sessions.first
  end

  def calculate_status_stats(answer=nil)
    completed = completed_path
    remaining = remaining_path

    if answer.present?
      current_path = path_until_answer(answer)
      percent_completed =  (current_path.sum{|x| x.question.time_estimate}.to_f / (completed[:time] + remaining[:time])) * 100
    else
      percent_completed = (completed[:time] / (completed[:time] + remaining[:time])) * 100
    end

    {
        percent_completed: percent_completed,
        completed_questions: completed[:distance],
        remaining_questions: remaining[:distance],
        total_questions: completed[:distance] + remaining[:distance],
        completed_time: completed[:time],
        remaining_time: remaining[:time],
        total_time: completed[:time] + remaining[:time]

    }
  end

  def completed_answers
    if first_answer
      Answer.joins(:in_edge).where(answer_session_id: self.id).select{|a| a.in_edge.present? }.append(first_answer)
    else
      []
    end
  end

  def completed?
    remaining_path[:distance] == 0
  end

  def process_answer(question, params)
    # adding should always be at tail!

    # Create answer object
    # Create answer edge from tail to new answer

    #answer_values =


    # Create new or find old answer object
    answer = Answer.where(question_id: question.id, answer_session_id: self.id).first || Answer.new(question_id: question.id, answer_session_id: self.id)

    # Options:
    # If new, create answer values and save
    #   also, since we're always adding new answers at the tail, set edge from end to new answer, and set new end

    # If existing
    ## If value is changing
    ### set new value
    #### if more than one possible route out
    ##### destroy descendents and set last_answer to this one
    #### else, do nothing
    ## else do nothing

    #

=begin
  new record that's also the first answer in answer session:
    - set value
    - save
    - set to first answer
    - set to last answer
  new record in an existing answer session
    - set value
    - save
    - set edge from previous answer
    - set to last answer

  existing record with new value(s) and multiple downstream options and no in edge and is first answer
    - set value
    - save
    - destroy downstream edges
    - set to last answer

  existing record with new value(s) and multiple downstream options and no in edge
    - set value
    - save
    - destroy downstream edges
    - set edge from previous answer
    - set to last answer

  existing record with new value(s) and multiple downstream options and an in edge
    - set value
    - save
    - destroy downstream edges
    - set to last answer

  existing record with new value(s) and one downstream option and no in edge and is first answer
    - set value
    - save

  existing record with new value(s) and one downstream option and no in edge
    - set value
    - save
    - set edge from previous answer
    - set to last answer


  existing record with new value(s) one downstream option and an in edge
    - set value
    - save

  existing record with no new values and is first and no in edge
    - nothing!!

  existing record with no new values and no in edge
    - set edge from previous answer
    - set to last answer

  existing record with no new values and an in edge
    - nothing!!




  new record: do everything


  existing record with
=end
    # New Record: do everything
    answer_modified = false

    if answer.new_record? or answer.string_value != params[question.id.to_s]
      # Set Value and Save

      answer.value = params[question.id.to_s]
      answer.save
      answer_modified = true
    end

    if first_answer_id.blank?
      # if no first answer, set it!
      self[:first_answer_id] = answer.id
      self[:last_answer_id] = answer.id
    elsif answer.in_edge.blank? and self[:first_answer_id] != answer.id
      # No in edge (and not first answer)...you need to set it
      answer_edges.create(parent_answer_id: last_answer.id, child_answer_id: answer.id)
      self[:last_answer_id] = answer.id
    end

    if answer_modified and answer.multiple_options?
      answer.destroy_descendant_edges
      self[:last_answer_id] = answer.id
    end

    self.save
    answer
  end

  def all_answers
    if first_answer
      [first_answer] + first_answer.descendants
    end
  end

  def all_reportable_answers
    all_answers.select {|answer| answer.answer_values.map{|av| av.answer_template.data_type }.include? "answer_option_id" and answer.show_value.present? } if all_answers
  end

  def grouped_reportable_answers
    all_reportable_answers.group_by{|a| a.question.question_help_message ? a.question.question_help_message.message : ""}
  end

  def get_answer(question_id)
    Question.find(question_id).answers.where(answer_session_id: self.id).first
  end

  def started?
    last_answer.present?
  end

  def reset_answers
    if first_answer.present?
      connected_answers = all_answers
      first_answer.destroy_descendant_edges
      self.first_answer = nil
      self.last_answer = nil
      save
      connected_answers.each(&:destroy)
    end
  end

  def path_until_answer(answer)
    if last_answer.blank?
      coll = []
      current_answer = answer
    elsif answer.new_record?
      coll = [answer]
      current_answer = last_answer
    else
      current_answer = answer.clone
      coll = []
    end

    while current_answer
      coll << current_answer
      current_answer = current_answer.previous_answer
    end

    coll
  end


  ## Reports



  private

  def completed_path
    time = completed_answers.map(&:question).map(&:time_estimate).reduce(:+) || 0.0
    distance = completed_answers.length

    {time: time, distance: distance}
  end


  def remaining_path

    if last_answer.blank?
      {time: question_flow.total_time, distance: question_flow.total_questions}
    else
      s = last_answer.next_question

      if s
        l = question_flow.leaf

        result = question_flow.find_longest_path(s,l)
        corrections = {time: 0.0, distance: 0}

        {time: result[:time] - corrections[:time], distance: result[:distance] - corrections[:distance]}
      else
        {time: 0.0, distance: 0}
      end
    end
  end


end