TracksApp/tracks

View on GitHub
app/models/project.rb

Summary

Maintainability
A
0 mins
Test Coverage
class Project < ApplicationRecord
  has_many :todos, -> { order(Arel.sql("todos.due IS NULL, todos.due ASC, todos.created_at ASC")) }, dependent: :delete_all
  has_many :notes, -> { order "created_at DESC" }, dependent: :delete_all
  has_many :recurring_todos

  belongs_to :default_context, :class_name => "Context", :foreign_key => "default_context_id"
  belongs_to :user

  scope :active,      -> { where state: 'active' }
  scope :hidden,      -> { where state: 'hidden' }
  scope :completed,   -> { where state: 'completed' }
  scope :uncompleted, -> { where("NOT(state = ?)", 'completed') }

  scope :with_name_or_description, lambda { |body| where("name " + Common.like_operator + " ? OR description " + Common.like_operator + " ?", body, body) }
  scope :with_namepart, lambda { |body| where("name " + Common.like_operator + " ?", body + '%') }

  before_create :set_last_reviewed_now

  validates :name, presence: true, length: { maximum: 255 }, uniqueness: { scope: :user_id }

  acts_as_list :scope => 'user_id = #{user_id} AND state = \'#{state}\'', :top_of_list => 0

  include AASM

  aasm :column => :state do
    state :active, :initial => true
    state :hidden
    state :completed, :enter => :set_completed_at_date, :exit => :clear_completed_at_date

    event :activate do
      transitions :to => :active,   :from => [:active, :hidden, :completed]
    end

    event :hide do
      transitions :to => :hidden,   :from => [:active, :completed]
    end

    event :complete do
      transitions :to => :completed, :from => [:active, :hidden]
    end
  end

  attr_accessor :cached_note_count

  def self.null_object
    NullProject.new
  end

  def set_last_reviewed_now
    self.last_reviewed = Time.zone.now
  end

  def set_completed_at_date
    self.completed_at = Time.zone.now
  end

  def clear_completed_at_date
    self.completed_at = nil
  end

  def note_count
    # TODO: test this for eager and not eager loading!!!
    return 0 if notes.size == 0
    cached_note_count || notes.count
  end

  alias_method :original_default_context, :default_context

  def default_context
    original_default_context.nil? ? Context.null_object : original_default_context
  end

  # would prefer to call this method state=(), but that causes an endless loop
  # as a result of acts_as_state_machine calling state=() to update the attribute
  def transition_to(candidate_state)
    case candidate_state.to_sym
      when aasm.current_state
        return
      when :hidden
        hide!
      when :active
        activate!
      when :completed
        complete!
    end
  end

  def needs_review?(user)
    return active? && (last_reviewed.nil? ||
                        (last_reviewed < Time.current - user.prefs.review_period.days))
  end

  def blocked?
    ## mutually exclusive for stalled and blocked
    # blocked is uncompleted project with deferred or pending todos, but no next actions
    return false if completed?
    return !todos.deferred_or_blocked.empty? && todos.active.empty?
  end

  def stalled?
    # Stalled projects are active projects with no active next actions
    return false if completed? || hidden?
    return !todos.deferred_or_blocked.exists? && !todos.active.exists?
  end

  def shortened_name(length = 40)
    name.truncate(length, :omission => "...").html_safe
  end

  def name=(value)
    if value
      self[:name] = value.gsub(/\s{2,}/, " ").strip
    else
      self[:name] = nil
    end
  end

  def age_in_days
    @age_in_days ||= (Time.current.to_date - created_at.to_date).to_i + 1
  end

  def running_time
    if completed_at.nil?
      return age_in_days
    else
      return (completed_at.to_date - created_at.to_date).to_i + 1
    end
  end

  def self.import(filename, params, user)
    count = 0
    CSV.foreach(filename, headers: true) do |row|
      unless find_by_name_and_user_id row[params[:name].to_i], user.id
        project = new
        project.name = row[params[:name].to_i]
        project.user = user
        project.description = row[params[:description].to_i] if row[params[:description].to_i].present?
        project.state = 'active'
        project.save!
        count += 1
      end
    end
    count
  end
end

class NullProject
  def hidden?
    false
  end

  def nil?
    true
  end

  def id
    nil
  end

  def name
    ""
  end

  def persisted?
    false
  end
end