Class: Todo

Inherits:
ApplicationRecord show all
Includes:
AASM, IsTaggable
Defined in:
app/models/todo.rb

Constant Summary collapse

MAX_DESCRIPTION_LENGTH =
300
MAX_NOTES_LENGTH =
60_000
STARRED_TAG_NAME =
"starred".freeze
DEFAULT_INCLUDES =
[:project, :context, :tags, :taggings, :pending_successors, :uncompleted_predecessors, :recurring_todo].freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from IsTaggable

included

Constructor Details

#initialize(*args) ⇒ Todo

Returns a new instance of Todo.



118
119
120
121
122
# File 'app/models/todo.rb', line 118

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



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

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

.due_between(start_date, end_date) ⇒ Object



63
64
65
# File 'app/models/todo.rb', line 63

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

.import(filename, params, user) ⇒ Object



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'app/models/todo.rb', line 354

def self.import(filename, params, user)
  default_context = user.contexts.order('id').first
  return false if default_context.nil?

  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



284
285
286
287
288
# File 'app/models/todo.rb', line 284

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

#add_predecessor(t) ⇒ Object



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

def add_predecessor(t)
  return if t.nil?

  @predecessor_array = predecessors
  @predecessor_array << t
end

#add_predecessor_list(predecessor_list) ⇒ Object



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

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

  @predecessor_array = predecessor_list.split(",").inject([]) do |list, todo_id|
    predecessor = 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



347
348
349
350
351
352
# File 'app/models/todo.rb', line 347

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



291
292
293
294
295
# File 'app/models/todo.rb', line 291

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

#check_show_from_in_futureObject



110
111
112
113
114
115
116
# File 'app/models/todo.rb', line 110

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



314
315
316
317
318
319
320
321
322
# File 'app/models/todo.rb', line 314

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

#destroyObject



378
379
380
381
382
383
384
385
386
387
388
# File 'app/models/todo.rb', line 378

def destroy
  # activate successors if they only depend on this action
  pending_successors.each do |successor|
    successor.uncompleted_predecessors.delete(self)
    if successor.uncompleted_predecessors.empty?
      successor.activate!
    end
  end

  super
end

#from_recurring_todo?Boolean

Returns:

  • (Boolean)


260
261
262
# File 'app/models/todo.rb', line 260

def from_recurring_todo?
  return recurring_todo_id != nil
end

#guard_for_transition_from_deferred_to_pendingObject



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

def guard_for_transition_from_deferred_to_pending
  no_uncompleted_predecessors? && not_part_of_hidden_container?
end

#has_pending_successorsObject



211
212
213
# File 'app/models/todo.rb', line 211

def has_pending_successors
  return !pending_successors.empty?
end

#has_project?Boolean

Returns:

  • (Boolean)


342
343
344
# File 'app/models/todo.rb', line 342

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

#hidden?Boolean

Returns:

  • (Boolean)


215
216
217
# File 'app/models/todo.rb', line 215

def hidden?
  project.hidden? || context.hidden?
end

#is_successor?(todo) ⇒ Boolean

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

Returns:

  • (Boolean)


196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'app/models/todo.rb', line 196

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

#no_uncompleted_predecessors?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'app/models/todo.rb', line 129

def no_uncompleted_predecessors?
  return !uncompleted_predecessors?
end

#no_uncompleted_predecessors_or_deferral?Boolean

Returns:

  • (Boolean)


124
125
126
127
# File 'app/models/todo.rb', line 124

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

#not_part_of_hidden_container?Boolean

Returns:

  • (Boolean)


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

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

#original_context=Object



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

alias_method :original_context=, :context=

#original_projectObject



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

alias_method :original_project, :project

#original_project=Object



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

alias_method :original_project=, :project=

#predecessor_dependencies=(params) ⇒ Object

XML API fixups



302
303
304
305
306
307
308
309
310
311
# File 'app/models/todo.rb', line 302

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| add_predecessor(user.todos.find(dep.to_i)) if dep.present? }
end

#projectObject



325
326
327
# File 'app/models/todo.rb', line 325

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

#project=(value) ⇒ Object



330
331
332
333
334
335
336
337
338
339
340
# File 'app/models/todo.rb', line 330

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



297
298
299
# File 'app/models/todo.rb', line 297

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

#remove_predecessor(predecessor) ⇒ Object

remove predecessor and activate myself if it was the last predecessor



185
186
187
188
189
190
191
192
193
# File 'app/models/todo.rb', line 185

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

#removed_predecessorsObject



180
181
182
# File 'app/models/todo.rb', line 180

def removed_predecessors
  return @removed_predecessors
end

#save_predecessorsObject



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'app/models/todo.rb', line 151

def save_predecessors
  unless @predecessor_array.nil? # Only save predecessors if they changed
    current_array = 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
        predecessors.delete(todo)
      end
    end

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

#show_fromObject



223
224
225
# File 'app/models/todo.rb', line 223

def show_from
  self[:show_from]
end

#show_from=(date) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/todo.rb', line 227

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>



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

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

#starred=(starred) ⇒ Object



251
252
253
254
255
256
257
258
# File 'app/models/todo.rb', line 251

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)


243
244
245
# File 'app/models/todo.rb', line 243

def starred?
  return has_tag?(STARRED_TAG_NAME)
end

#toggle_completion!Object



219
220
221
# File 'app/models/todo.rb', line 219

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

#toggle_star!Object



247
248
249
# File 'app/models/todo.rb', line 247

def toggle_star!
  self.starred = !starred?
end

#touch_predecessorsObject



175
176
177
178
# File 'app/models/todo.rb', line 175

def touch_predecessors
  touch
  predecessors.each(&:touch_predecessors)
end

#uncompleted_predecessors?Boolean

Returns:

  • (Boolean)


133
134
135
# File 'app/models/todo.rb', line 133

def uncompleted_predecessors?
  return !uncompleted_predecessors.empty?
end