Module: Fe::AnswerSheetConcern

Extended by:
ActiveSupport::Concern
Included in:
AnswerSheet, ReferenceSheet
Defined in:
app/models/concerns/fe/answer_sheet_concern.rb

Instance Method Summary collapse

Instance Method Details

#answers_by_questionObject

answers for this sheet, grouped by question id



47
48
49
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 47

def answers_by_question
  @answers_by_question ||= answers.group_by { |answer| answer.question_id }
end

#answers_digest(page, cached_answers = nil) ⇒ Object

Compute an MD5 digest of all Fe::Answer values for this answer_sheet on a given page. Used for optimistic concurrency: if the digest changes between load and save, another tab/user has modified the answers.

When cached_answers is provided (an array of Fe::Answer for this page), the digest is computed from those in-memory records instead of hitting the DB.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 117

def answers_digest(page, cached_answers = nil)
  element_ids = page.all_element_ids_arr
  return Digest::MD5.hexdigest("") if element_ids.blank?

  if cached_answers
    pairs = cached_answers
      .select { |a| element_ids.include?(a.question_id) }
      .sort_by { |a| [a.question_id, a.id] }
      .map { |a| [a.question_id, a.id, a.value] }
  else
    pairs = Fe::Answer
      .where(answer_sheet_id: self.id, question_id: element_ids)
      .order(:question_id, :id)
      .pluck(:question_id, :id, :value)
  end

  answer_values_json = pairs.to_json
  digest = Digest::MD5.hexdigest(answer_values_json)

  if Fe.verbose_logging?
    Rails.logger.info("[fe concurrency] answers_digest: answer_sheet=#{self.id} page=#{page.id} " \
      "answers=[#{pairs.map { |qid, aid, val| "q#{qid}/a#{aid}=#{val.to_s.truncate(40)}" }.join(', ')}] " \
      "digest=#{digest}")
  end

  digest
end

#collat_titleObject



109
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 109

def collat_title() "" end

#complete?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 42

def complete?
  !completed_at.nil?
end

#completely_filled_out?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 60

def completely_filled_out?
  pages.all? {|p| p.complete?(self)}
end

#has_answer_for?(question_id) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 64

def has_answer_for?(question_id)
  !answers_by_question[question_id].nil?
end

#languagesObject



32
33
34
35
36
37
38
39
40
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 32

def languages
  return [] unless question_sheets.first

  unless @languages
    @languages = question_sheets.first.languages
    question_sheets[1..-1].each { |qs| @languages &= qs.languages&.select(&:present?) }
  end
  @languages
end

#load_answers_for_page(page) ⇒ Object

Load all answers for this answer_sheet on a given page in a single query. Returns an array of Fe::Answer records, useful as a cache to pass to answers_digest() and QuestionSet#post().



148
149
150
151
152
153
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 148

def load_answers_for_page(page)
  element_ids = page.all_element_ids_arr
  return [] if element_ids.blank?

  Fe::Answer.where(answer_sheet_id: self.id, question_id: element_ids).to_a
end

#pagesObject



56
57
58
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 56

def pages
  Page.where(question_sheet_id: question_sheets.collect(&:id)).visible.order('number')
end

#percent_complete(required_only = true, restrict_to_pages = []) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 72

def percent_complete(required_only = true, restrict_to_pages = [])
  # build an element to page lookup using page's cached all_element_ids
  # this will make the hidden? calls on element faster because we can pass the page
  # (the page builds a list of hidden elements for quick lookup)
  elements_to_pages = {}
  pages = question_sheets.collect(&:pages).flatten
  pages = pages & restrict_to_pages if restrict_to_pages.present?
  pages.each do |p|
    p.all_element_ids_arr.each do |e_id|
      elements_to_pages[e_id] = p
    end
  end

  # determine which questions should count towards the questions total in the percent calculation
  countable_questions = question_sheets.collect{ |qs| qs.all_elements.questions }.flatten
  countable_questions.select!{ |e| elements_to_pages[e.id] } if restrict_to_pages.present?
  countable_questions.reject!{ |e| e.hidden?(self, elements_to_pages[e.id]) }
  countable_questions.select!{ |e| e.required } if required_only

  # no progress if there are no questions
  num_questions = countable_questions.length
  return 0 if num_questions == 0

  # count questions with answers in Fe::Answer
  answers = self.answers.where("(question_id IN (?) AND value IS NOT NULL) AND (value != '')", countable_questions.collect(&:id))
  answered_question_ids = answers.pluck(Arel.sql('distinct(question_id)'))

  # need to look for answers for the remaining questions using has_response?
  # so that questions with object_name/attribute_name set are counted
  other_answered_questions = countable_questions.reject{ |e| answered_question_ids.include?(e.id) }
  other_answered_questions.select!{ |e| e.has_response?(self) }

  # count total
  num_answers = answered_question_ids.count + other_answered_questions.count
  [ [ (num_answers.to_f / num_questions.to_f * 100.0).to_i, 100 ].min, 0 ].max
end

#question_sheetObject

Convenience method if there is only one question sheet in your system



52
53
54
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 52

def question_sheet
  question_sheets.first
end

#question_sheet_idsObject



155
156
157
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 155

def question_sheet_ids
  question_sheets.collect(&:id)
end

#question_sheets_all_reference_elementsObject



159
160
161
162
163
164
165
166
167
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 159

def question_sheets_all_reference_elements
  # forms are generally not changed often so caching on the last updated elementd
  # will provide a good balance of speed and cache invalidation
  element_ids = Rails.cache.fetch(question_sheets + ['answer_sheet#answer_sheet_all_reference_elements', Fe::Element.order('updated_at desc, id desc').first]) do
    question_sheets.compact.collect { |q| q.all_elements.reference_kinds.pluck(:id) }.flatten
  end

  Fe::Element.find(element_ids)
end

#reference?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 68

def reference?
  false
end