app/models/project.rb
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