Class: Todo

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
AASM, IsTaggable
Defined in:
app/models/todo.rb

Constant Summary collapse

MAX_DESCRIPTION_LENGTH =
300
MAX_NOTES_LENGTH =
60000
STARRED_TAG_NAME =
"starred"
DEFAULT_INCLUDES =
[ :project, :context, :tags, :taggings, :pending_successors, :uncompleted_predecessors, :recurring_todo ]

Class Method Summary collapse

Instance Method Summary collapse

Methods included from IsTaggable

included

Constructor Details

#initialize(*args) ⇒ Todo

Returns a new instance of Todo



136
137
138
139
140
# File 'app/models/todo.rb', line 136

def initialize(*args)
  super(*args)
  @predecessor_array = nil # Used for deferred save of predecessors
  @removed_predecessors = nil
end

Class Method Details

.due_after(date) ⇒ Object



56
57
58
# File 'app/models/todo.rb', line 56

def self.due_after(date)
  where('todos.due > ?', date)
end

.due_between(start_date, end_date) ⇒ Object



60
61
62
# File 'app/models/todo.rb', line 60

def self.due_between(start_date, end_date)
  where('todos.due > ? AND todos.due <= ?', start_date, end_date)
end

.import(filename, params, user) ⇒ Object



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'app/models/todo.rb', line 397

def self.import(filename, params, user)
  default_context = user.contexts.order('id').first

  count = 0
  CSV.foreach(filename, headers: true) do |row|
    unless find_by_description_and_user_id row[params[:description].to_i], user.id
      todo = new
      todo.user = user
      todo.description = row[params[:description].to_i].truncate MAX_DESCRIPTION_LENGTH
      todo.context = Context.find_by_name_and_user_id(row[params[:context].to_i], user.id) || default_context
      todo.project = Project.find_by_name_and_user_id(row[params[:project].to_i], user.id) if row[params[:project].to_i].present?
      todo.state = row[params[:completed_at].to_i].present? ? 'completed' : 'active'
      todo.notes = row[params[:notes].to_i].truncate MAX_NOTES_LENGTH if row[params[:notes].to_i].present?
      todo.created_at = row[params[:created_at].to_i] if row[params[:created_at].to_i].present?
      todo.due = row[params[:due].to_i]
      todo.completed_at = row[params[:completed_at].to_i] if row[params[:completed_at].to_i].present?
      todo.save!
      count += 1
    end
  end
  count
end

Instance Method Details

#activate_pending_todosObject

activate todos that should be activated if the current todo is completed



319
320
321
322
323
# File 'app/models/todo.rb', line 319

def activate_pending_todos
  pending_todos = successors.select {|t| t.uncompleted_predecessors.empty?}
  pending_todos.each {|t| t.activate! }
  return pending_todos
end

#add_predecessor(t) ⇒ Object



311
312
313
314
315
316
# File 'app/models/todo.rb', line 311

def add_predecessor(t)
  return if t.nil?

  @predecessor_array = predecessors
  @predecessor_array << t
end

#add_predecessor_list(predecessor_list) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
# File 'app/models/todo.rb', line 299

def add_predecessor_list(predecessor_list)
  return unless predecessor_list.kind_of? String

  @predecessor_array=predecessor_list.split(",").inject([]) do |list, todo_id|
    predecessor = self.user.todos.find( todo_id.to_i ) if todo_id.present?
    list <<  predecessor unless predecessor.nil?
    list
  end

  return @predecessor_array
end

#add_tags=(params) ⇒ Object

used by the REST API. will also work, this is renamed to add_tags in TodosController::TodoCreateParamsHelper::initialize



382
383
384
385
386
387
# File 'app/models/todo.rb', line 382

def add_tags=(params)
  unless params[:tag].nil?
    tag_list = params[:tag].inject([]) { |list, value| list << value[:name] }
    tag_with tag_list.join(", ")
  end
end

#block_successorsObject

Return todos that should be blocked if the current todo is undone



326
327
328
329
330
# File 'app/models/todo.rb', line 326

def block_successors
  active_successors = successors.select {|t| t.active? or t.deferred?}
  active_successors.each {|t| t.block!}
  return active_successors
end

#check_circular_dependenciesObject



128
129
130
131
132
133
134
# File 'app/models/todo.rb', line 128

def check_circular_dependencies
  unless @predecessor_array.nil? # Only validate predecessors if they changed
    @predecessor_array.each do |todo|
      errors.add("Depends on:", "Adding '#{todo.specification}' would create a circular dependency") if is_successor?(todo)
    end
  end
end

#check_show_from_in_futureObject



120
121
122
123
124
125
126
# File 'app/models/todo.rb', line 120

def check_show_from_in_future
  if show_from_changed? # only check on change of show_from
    if show_from.present? && (show_from < user.date)
      errors.add("show_from", I18n.t('models.todo.error_date_must_be_future'))
    end
  end
end

#context=(value) ⇒ Object



349
350
351
352
353
354
355
356
357
# File 'app/models/todo.rb', line 349

def context=(value)
  if value.is_a? Context
    self.original_context=(value)
  else
    c = Context.where(:name => value[:name]).first
    c = Context.create(value) if c.nil?
    self.original_context=(c)
  end
end

#from_recurring_todo?Boolean

Returns:

  • (Boolean)


295
296
297
# File 'app/models/todo.rb', line 295

def from_recurring_todo?
  return self.recurring_todo_id != nil
end

#guard_for_transition_from_deferred_to_pendingObject



159
160
161
# File 'app/models/todo.rb', line 159

def guard_for_transition_from_deferred_to_pending
  no_uncompleted_predecessors? && not_part_of_hidden_container?
end

#has_pending_successorsObject



233
234
235
# File 'app/models/todo.rb', line 233

def has_pending_successors
  return !pending_successors.empty?
end

#has_project?Boolean

Returns:

  • (Boolean)


377
378
379
# File 'app/models/todo.rb', line 377

def has_project?
  return ! (project_id.nil? || project.is_a?(NullProject))
end

#hidden?Boolean

Returns:

  • (Boolean)


237
238
239
# File 'app/models/todo.rb', line 237

def hidden?
  return self.project_hidden? || ( self.context.hidden? && (self.active? || self.deferred?))
end

#is_successor?(todo) ⇒ Boolean

Returns true if t is equal to self or a successor of self

Returns:

  • (Boolean)


218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'app/models/todo.rb', line 218

def is_successor?(todo)
  if self == todo
    return true
  elsif self.successors.empty?
    return false
  else
    self.successors.each do |item|
      if item.is_successor?(todo)
        return true
      end
    end
  end
  return false
end

#no_uncompleted_predecessors?Boolean

Returns:

  • (Boolean)


147
148
149
# File 'app/models/todo.rb', line 147

def no_uncompleted_predecessors?
  return !uncompleted_predecessors?
end

#no_uncompleted_predecessors_or_deferral?Boolean

Returns:

  • (Boolean)


142
143
144
145
# File 'app/models/todo.rb', line 142

def no_uncompleted_predecessors_or_deferral?
  no_deferral = show_from.blank? or Time.zone.now > show_from
  return (no_deferral && no_uncompleted_predecessors?)
end

#not_part_of_hidden_container?Boolean

Returns:

  • (Boolean)


163
164
165
# File 'app/models/todo.rb', line 163

def not_part_of_hidden_container?
  !( (self.project && self.project.hidden?) || self.context.hidden? )
end

#original_context=Object



348
# File 'app/models/todo.rb', line 348

alias_method :original_context=, :context=

#original_projectObject



359
# File 'app/models/todo.rb', line 359

alias_method :original_project, :project

#original_project=Object



364
# File 'app/models/todo.rb', line 364

alias_method :original_project=, :project=

#predecessor_dependencies=(params) ⇒ Object

XML API fixups



337
338
339
340
341
342
343
344
345
346
# File 'app/models/todo.rb', line 337

def predecessor_dependencies=(params)
  deps = params[:predecessor]
  return if deps.nil?

  # for multiple dependencies, value will be an array of id's, but for a single dependency,
  # value will be a string. In that case convert to array
  deps = [deps] unless deps.class == Array

  deps.each { |dep| self.add_predecessor(self.user.todos.find(dep.to_i)) if dep.present? }
end

#projectObject



360
361
362
# File 'app/models/todo.rb', line 360

def project
  original_project.nil? ? Project.null_object : original_project
end

#project=(value) ⇒ Object



365
366
367
368
369
370
371
372
373
374
375
# File 'app/models/todo.rb', line 365

def project=(value)
  if value.is_a? Project
    self.original_project=(value)
  elsif !(value.nil? || value.is_a?(NullProject))
    p = Project.where(:name => value[:name]).first
    p = Project.create(value) if p.nil?
    self.original_project=(p)
  else
    self.original_project=value
  end
end

#raw_notes=(value) ⇒ Object



332
333
334
# File 'app/models/todo.rb', line 332

def raw_notes=(value)
  self[:notes] = value
end

#remove_predecessor(predecessor) ⇒ Object

remove predecessor and activate myself if it was the last predecessor



207
208
209
210
211
212
213
214
215
# File 'app/models/todo.rb', line 207

def remove_predecessor(predecessor)
  self.predecessors.delete(predecessor)
  if self.predecessors.empty?
    self.reload  # reload predecessors
    self.not_part_of_hidden_container? ? self.activate! : self.hide!
  else
    save!
  end
end

#removed_predecessorsObject



202
203
204
# File 'app/models/todo.rb', line 202

def removed_predecessors
  return @removed_predecessors
end

#render_noteObject



389
390
391
392
393
394
395
# File 'app/models/todo.rb', line 389

def render_note
  unless self.notes.nil?
    self.rendered_notes = Tracks::Utils.render_text(self.notes)
  else
    self.rendered_notes = nil
  end
end

#save_predecessorsObject



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'app/models/todo.rb', line 173

def save_predecessors
  unless @predecessor_array.nil?  # Only save predecessors if they changed
    current_array = self.predecessors
    remove_array = current_array - @predecessor_array
    add_array = @predecessor_array - current_array

    @removed_predecessors = []
    remove_array.each do |todo|
      unless todo.nil?
        @removed_predecessors << todo
        self.predecessors.delete(todo)
      end
    end

    add_array.each do |todo|
      unless todo.nil?
        self.predecessors << todo unless self.predecessors.include?(todo)
      else
        logger.error "Could not find #{todo.description}" # Unexpected since validation passed
      end
    end
  end
end

#should_be_blocked?Boolean

Returns:

  • (Boolean)


155
156
157
# File 'app/models/todo.rb', line 155

def should_be_blocked?
  return !( uncompleted_predecessors.empty? || state == 'project_hidden' )
end

#show_fromObject



258
259
260
# File 'app/models/todo.rb', line 258

def show_from
  self[:show_from]
end

#show_from=(date) ⇒ Object



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'app/models/todo.rb', line 262

def show_from=(date)
  if deferred? && date.blank?
    activate
  else
    # parse Date objects into the proper timezone
    date = date.in_time_zone.beginning_of_day if (date.is_a? Date)

    # show_from needs to be set before state_change because of "bug" in aasm.
    # If show_from is not set, the todo will not validate and thus aasm will not save
    # (see http://stackoverflow.com/questions/682920/persisting-the-state-column-on-transition-using-rubyist-aasm-acts-as-state-machi)
    self[:show_from] = date

    defer if active? && date.present? && show_from > user.date
  end
end

#specificationObject

Returns a string with description <context, project>



168
169
170
171
# File 'app/models/todo.rb', line 168

def specification
  project_name = self.project.is_a?(NullProject) ? "(none)" : self.project.name
  return "\'#{self.description}\' <\'#{self.context.title}\'; \'#{project_name}\'>"
end

#starred=(starred) ⇒ Object



286
287
288
289
290
291
292
293
# File 'app/models/todo.rb', line 286

def starred=(starred)
  if starred
    _add_tags STARRED_TAG_NAME unless starred?
  else
    _remove_tags STARRED_TAG_NAME
  end
  starred
end

#starred?Boolean

Returns:

  • (Boolean)


278
279
280
# File 'app/models/todo.rb', line 278

def starred?
  return has_tag?(STARRED_TAG_NAME)
end

#toggle_completion!Object



254
255
256
# File 'app/models/todo.rb', line 254

def toggle_completion!
  return completed? ? activate! : complete!
end

#toggle_star!Object



282
283
284
# File 'app/models/todo.rb', line 282

def toggle_star!
  self.starred= !starred?
end

#touch_predecessorsObject



197
198
199
200
# File 'app/models/todo.rb', line 197

def touch_predecessors
  self.touch
  predecessors.each { |p| p.touch_predecessors }
end

#uncompleted_predecessors?Boolean

Returns:

  • (Boolean)


151
152
153
# File 'app/models/todo.rb', line 151

def uncompleted_predecessors?
  return !uncompleted_predecessors.empty?
end

#update_state_from_projectObject



241
242
243
244
245
246
247
248
249
250
251
252
# File 'app/models/todo.rb', line 241

def update_state_from_project
  if self.project_hidden? && (!self.project.hidden?)
    if self.uncompleted_predecessors.empty?
      self.activate!
    else
      self.block!
    end
  elsif self.active? && self.project.hidden?
    self.hide!
  end
  self.save!
end