Newer
Older
dmpopidor / app / models / question.rb
@Quentin SONREL Quentin SONREL on 21 Jan 2020 6 KB Fix on migration
# frozen_string_literal: true
# == Schema Information
#
# Table name: questions
#
#  id                        :integer          not null, primary key
#  text                      :text
#  default_value             :text
#  number                    :integer
#  section_id                :integer
#  created_at                :datetime
#  updated_at                :datetime
#  question_format_id        :integer
#  option_comment_display    :boolean          default("true")
#  modifiable                :boolean
#  versionable_id            :string(36)
#  structured                :boolean          default("false"), not null
#  structured_data_schema_id :integer
#
# Indexes
#
#  index_questions_on_structured_data_schema_id  (structured_data_schema_id)
#  index_questions_on_versionable_id             (versionable_id)
#  questions_question_format_id_idx              (question_format_id)
#  questions_section_id_idx                      (section_id)
#

class Question < ActiveRecord::Base

  include ValidationMessages
  include ActsAsSortable
  include VersionableModel

  # ==============
  # = Attributes =
  # ==============

  alias_attribute :to_s, :text

  # include
  ##
  # Sort order: Number ASC
  default_scope { order(number: :asc) }

  # ================
  # = Associations =
  # ================

  has_many :answers, dependent: :destroy

  # inverse_of needed for nested forms
  has_many :question_options, dependent: :destroy, inverse_of: :question

  has_many :annotations, dependent: :destroy, inverse_of: :question

  has_and_belongs_to_many :themes, join_table: "questions_themes"

  belongs_to :section

  belongs_to :question_format

  has_one :phase, through: :section

  has_one :template, through: :section

  belongs_to :structured_data_schema

  # ===============
  # = Validations =
  # ===============

  validate :ensure_has_question_options, if: :option_based?

  validates :text, presence: { message:   QUESTION_TEXT_PRESENCE_MESSAGE }

  validates :section, presence: { message: PRESENCE_MESSAGE, on: :update }

  validates :question_format, presence: { message: PRESENCE_MESSAGE }

  validates :number, presence: { message: PRESENCE_MESSAGE },
                     uniqueness: { scope: :section_id,
                                   message: UNIQUENESS_MESSAGE }


  # =====================
  # = Nested Attributes =
  # =====================

  # TODO: evaluate if we need this
  accepts_nested_attributes_for :answers, reject_if: -> (a) { a[:text].blank? },
                                  allow_destroy: true

  accepts_nested_attributes_for :question_options, allow_destroy: true,
                                  reject_if: -> (a) { a[:text].blank? }

  accepts_nested_attributes_for :annotations, allow_destroy: true,
                                  reject_if: proc { |a| a[:text].blank? && a[:id].blank? }

  # =====================
  # = Delegated methods =
  # =====================

  delegate :option_based?, to: :question_format, :allow_nil => true

  # ===========================
  # = Public instance methods =
  # ===========================

  def deep_copy(**options)
    copy = self.dup
    copy.modifiable = options.fetch(:modifiable, self.modifiable)
    copy.section_id = options.fetch(:section_id, nil)
    copy.save!(validate: false)  if options.fetch(:save, false)
    options[:question_id] = copy.id
    self.question_options.each do |question_option|
      copy.question_options << question_option.deep_copy(options)
    end
    self.annotations.each do |annotation|
      copy.annotations << annotation.deep_copy(options)
    end
    self.themes.each { |theme| copy.themes << theme }
    copy
  end

  # TODO: consider moving this to a view helper instead and use the built in
  # scopes for guidance. May need to add a new one for 'thematic_guidance'.
  # This method doesn't even make reference to this class and its returning
  # a hash that is specific to a view guidance for org
  #
  # org - The Org to find guidance for
  #
  # Returns Hash
  def guidance_for_org(org)
    # pulls together guidance from various sources for question
    guidances = {}
    if theme_ids.any?
      GuidanceGroup.includes(guidances: :themes)
                   .where(org_id: org.id).each do |group|
        group.guidances.each do |g|
          g.themes.each do |theme|
            if theme_ids.include? theme.id
              guidances["#{group.name} " + _("guidance on") + " #{theme.title}"] = g
            end
          end
        end
      end
    end

    guidances
  end

  # get example answer belonging to the currents user for this question
  #
  # org_ids - The ids for the organisations
  #
  # Returns ActiveRecord::Relation
  def example_answers(org_ids)
    annotations.where(org_id: Array(org_ids),
                      type: Annotation.types[:example_answer])
               .order(:created_at)
  end

  alias get_example_answers example_answers

  deprecate :get_example_answers,
              deprecator: Cleanup::Deprecators::GetDeprecator.new

  # get guidance belonging to the current user's org for this question(need org
  # to distinguish customizations)
  #
  # org_id - The id for the organisation
  #
  # Returns Annotation
  def guidance_annotation(org_id)
    annotations.where(org_id: org_id, type: Annotation.types[:guidance]).first
  end

  alias get_guidance_annotation guidance_annotation

  deprecate :get_guidance_annotation,
              deprecator: Cleanup::Deprecators::GetDeprecator.new

  def annotations_per_org(org_id)
    example_answer = annotations.find_by(org_id: org_id,
                                         type: Annotation.types[:example_answer])
    guidance = annotations.find_by(org_id: org_id,
                                   type: Annotation.types[:guidance])
    unless example_answer.present?
      example_answer = annotations.build(type: :example_answer, text: "", org_id: org_id)
    end
    unless guidance.present?
      guidance = annotations.build(type: :guidance, text: "", org_id: org_id)
    end
    [example_answer, guidance]
  end

  private

  def ensure_has_question_options
    if question_options.empty?
      errors.add :base, OPTION_PRESENCE_MESSAGE
    end
  end

end