Newer
Older
dmpopidor / app / models / phase.rb
@Bodacious Bodacious on 25 Jul 2018 3 KB Add extra validations for all models
# == Schema Information
#
# Table name: phases
#
#  id          :integer          not null, primary key
#  description :text
#  modifiable  :boolean
#  number      :integer
#  slug        :string
#  title       :string
#  created_at  :datetime
#  updated_at  :datetime
#  template_id :integer
#
# Indexes
#
#  index_phases_on_template_id  (template_id)
#
# Foreign Keys
#
#  fk_rails_...  (template_id => templates.id)
#

# [+Project:+] DMPRoadmap
# [+Description:+] This model describes informmation about the phase of a plan, it's title, order of display and which template it belongs to.
#
# [+Created:+] 03/09/2014
# [+Copyright:+] Digital Curation Centre and University of California Curation Center
class Phase < ActiveRecord::Base
  include ValidationMessages
  include ValidationValues
  include ActsAsSortable

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

  # ================
  # = Associations =
  # ================
  belongs_to :template

  has_one :prefix_section, -> (phase) {
    modifiable.where("number < ?",
                      phase.sections.not_modifiable.minimum(:number))
  }, class_name: "Section"

  has_many :sections, dependent: :destroy

  has_many :template_sections, -> {
    not_modifiable
  }, class_name: "Section"


  has_many :suffix_sections, -> (phase) {
    modifiable.where(<<~SQL, phase_id: phase.id, modifiable: false)
      sections.number > (SELECT MAX(number) FROM sections
                           WHERE sections.modifiable = :modifiable
                           AND sections.phase_id = :phase_id)
    SQL
  }, class_name: "Section"


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

  validates :title, presence: { message: PRESENCE_MESSAGE }

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

  validates :template, presence: { message: PRESENCE_MESSAGE }

  validates :modifiable, inclusion: { in: BOOLEAN_VALUES,
                                      message: INCLUSION_MESSAGE }

  # ==========
  # = Scopes =
  # ==========

  scope :titles, -> (template_id) {
    Phase.where(template_id: template_id).select(:id, :title)
  }

  # TODO: Remove after implementing new template versioning logic
  # Callbacks
  after_save do |phase|
    # Updates the template.updated_at attribute whenever a phase has been created/updated
    phase.template.touch if template.present?
  end

  def deep_copy(**options)
    copy = self.dup
    copy.modifiable = options.fetch(:modifiable, self.modifiable)
    copy.template_id = options.fetch(:template_id, nil)
    copy.save!(validate:false)  if options.fetch(:save, false)
    options[:phase_id] = copy.id
    self.sections.each{ |section| copy.sections << section.deep_copy(options) }
    return copy
  end

  # TODO: Move this to Plan model as `num_answered_questions(phase=nil)`
  # Returns the number of answered question for the phase.
  def num_answered_questions(plan)
    return 0 if plan.nil?
    return sections.reduce(0) do |m, s|
      m + s.num_answered_questions(plan)
    end
  end

  # Returns the number of questions for a phase. Note, this method becomes useful
  # for when sections and their questions are eager loaded so that avoids SQL queries.
  def num_questions
    n = 0
    self.sections.each do |s|
      n+= s.questions.size()
    end
    n
  end
end