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
-
#answers_by_question ⇒ Object
answers for this sheet, grouped by question id.
-
#answers_digest(page, cached_answers = nil) ⇒ Object
Compute an MD5 digest of all Fe::Answer values for this answer_sheet on a given page.
- #collat_title ⇒ Object
- #complete? ⇒ Boolean
- #completely_filled_out? ⇒ Boolean
- #has_answer_for?(question_id) ⇒ Boolean
- #languages ⇒ Object
-
#load_answers_for_page(page) ⇒ Object
Load all answers for this answer_sheet on a given page in a single query.
- #pages ⇒ Object
- #percent_complete(required_only = true, restrict_to_pages = []) ⇒ Object
-
#question_sheet ⇒ Object
Convenience method if there is only one question sheet in your system.
- #question_sheet_ids ⇒ Object
- #question_sheets_all_reference_elements ⇒ Object
- #reference? ⇒ Boolean
Instance Method Details
#answers_by_question ⇒ Object
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_title ⇒ Object
109 |
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 109 def collat_title() "" end |
#complete? ⇒ 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
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
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 |
#languages ⇒ Object
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 |
#pages ⇒ Object
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_sheet ⇒ Object
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_ids ⇒ Object
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_elements ⇒ Object
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
68 69 70 |
# File 'app/models/concerns/fe/answer_sheet_concern.rb', line 68 def reference? false end |