From 93a914fdb39576c4c11e97fd566910e1d0ad45fd Mon Sep 17 00:00:00 2001 From: Henrik Bohre Date: Wed, 19 Aug 2009 22:15:38 +0200 Subject: [PATCH] #300: Disambiguation of predecessors by using project and context names --- app/controllers/todos_controller.rb | 10 ++--- app/helpers/todos_helper.rb | 24 ++++++------ app/models/todo.rb | 57 +++++++++++++++++++---------- public/stylesheets/standard.css | 1 + 4 files changed, 56 insertions(+), 36 deletions(-) diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 0721b35a..affd101b 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -236,7 +236,7 @@ class TodosController < ApplicationController @original_item_was_deferred = @todo.deferred? @original_item_due = @todo.due @original_item_due_id = get_due_id_for_calendar(@todo.due) - @original_item_predecessor_list = @todo.predecessors.collect{|t| t.description}.join(', ') + @original_item_predecessor_list = @todo.predecessors.map{|t| t.specification}.join(', ') if params['todo']['project_id'].blank? && !params['project_name'].nil? if params['project_name'] == 'None' @@ -604,7 +604,7 @@ class TodosController < ApplicationController get_todo_from_params # Begin matching todos in current project @items = current_user.todos.find(:all, - :select => :description, + :select => 'description, project_id, context_id, created_at', :conditions => [ '(todos.state = ? OR todos.state = ?) AND ' + 'NOT (id = ?) AND lower(description) LIKE ? AND project_id = ?', 'active', 'pending', @@ -616,7 +616,7 @@ class TodosController < ApplicationController ) if @items.empty? # Match todos in other projects @items = current_user.todos.find(:all, - :select => :description, + :select => 'description, project_id, context_id, created_at', :conditions => [ '(todos.state = ? OR todos.state = ?) AND ' + 'NOT (id = ?) AND lower(description) LIKE ?', 'active', 'pending', @@ -628,7 +628,7 @@ class TodosController < ApplicationController else # New todo - TODO: Filter on project @items = current_user.todos.find(:all, - :select => :description, + :select => 'description, project_id, context_id, created_at', :conditions => [ '(todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?', 'active', 'pending', '%' + params[:predecessor_list].downcase + '%' ], @@ -636,7 +636,7 @@ class TodosController < ApplicationController :limit => 10 ) end - render :inline => "<%= auto_complete_result(@items, :description) %>" + render :inline => "<%= auto_complete_result2(@items) %>" end private diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 9cf5c3eb..45bdcb3c 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -134,20 +134,10 @@ module TodosHelper if tag_list.empty? then "" else "#{tag_list}" end end - # TODO: Use DELIMITER def predecessor_list_text - @todo.predecessors.collect{|t| t.description}.join(', ') + @todo.predecessors.map{|t| t.specification}.join(', ') end - - def predecessor_list - predecessor_list = @todo.predecessors.collect{|t| - '' + - link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + - "" - }.join('') - '#{predecessor_list}' - end - + def deferred_due_date if @todo.deferred? && @todo.due "(action due on #{format_date(@todo.due)})" @@ -316,4 +306,14 @@ module TodosHelper class_str = todo.starred? ? "starred_todo" : "unstarred_todo" image_tag("blank.png", :title =>"Star action", :class => class_str) end + + def auto_complete_result2(entries, phrase = nil) + return unless entries + items = entries.map do |entry| + item = entry.specification() + content_tag("li", phrase ? highlight(h(item), phrase) : h(item)) + end + content_tag("ul", items.uniq) + end + end diff --git a/app/models/todo.rb b/app/models/todo.rb index 6dc75816..28a1f16f 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -75,52 +75,72 @@ class Todo < ActiveRecord::Base end def no_uncompleted_predecessors_or_deferral? - value = (show_from.blank? or Time.zone.now > show_from and uncompleted_predecessors.empty?) - logger.debug "=== no_uncompleted_predecessors_or_deferral #{value}" - return value + return (show_from.blank? or Time.zone.now > show_from and uncompleted_predecessors.empty?) end def no_uncompleted_predecessors? - value = self.uncompleted_predecessors.empty? - logger.debug "=== no_uncompleted_predecessor #{value}" - return value + return uncompleted_predecessors.empty? + end + + + def todo_from_string(specification) + # Split specification into parts: description + parts = specification.split(%r{\ \<|; |\>}) + return nil unless parts.length == 3 + todos = Todo.all(:joins => [:project, :context], + :include => [:context, :project], + :conditions => {:description => parts[0], + :contexts => {:name => parts[2]}}) + return nil if todos.empty? + # todos now contains all todos with matching description and context + # TODO: Is this possible to do with a single query? + todos.each do |todo| + project_name = todo.project.is_a?(NullProject) ? "(none)" : todo.project.name + return todo if project_name == parts[1] + end + return nil end - # TODO: Handle duplicate descriptions def validate if !show_from.blank? && show_from < user.date errors.add("show_from", "must be a date in the future") end unless @predecessor_array.nil? # Only validate predecessors if they changed - @predecessor_array.each do |description| - t = Todo.find_by_description(description) + @predecessor_array.each do |specification| + t = todo_from_string(specification) if t.nil? - errors.add("Depends on:", "Could not find action '#{description}'") + errors.add("Depends on:", "Could not find action '#{h(specification)}'") else - errors.add("Depends on:", "Adding '#{description}' would create a circular dependency") if is_successor?(t) + errors.add("Depends on:", "Adding '#{h(specification)}' would create a circular dependency") if is_successor?(t) end end end end + # Returns a string with description, project and context + def specification + project_name = project.is_a?(NullProject) ? "(none)" : project.name + return "#{description} <#{project_name}; #{context.title}>" + end + def save_predecessors unless @predecessor_array.nil? # Only save predecessors if they changed - current_array = predecessors.map(&:description) + current_array = predecessors.map{|p| p.specification} remove_array = current_array - @predecessor_array add_array = @predecessor_array - current_array # This is probably a bit naive code... - remove_array.each do |description| - t = Todo.find_by_description(description) - self.predecessors.delete(t) + remove_array.each do |specification| + t = todo_from_string(specification) + self.predecessors.delete(t) unless t.nil? end # ... as is this? - add_array.each do |description| - t = Todo.find_by_description(description) + add_array.each do |specification| + t = todo_from_string(specification) unless t.nil? self.predecessors << t unless self.predecessors.include?(t) else - logger.error "Could not find #{description}" # Unexpected since validation passed + logger.error "Could not find #{specification}" # Unexpected since validation passed end end end @@ -235,7 +255,6 @@ class Todo < ActiveRecord::Base end # TODO: DELIMITER - # TODO: Handle todos with the same description # TODO: Should possibly handle state changes also? def add_predecessor_list(predecessor_list) if predecessor_list.kind_of? String diff --git a/public/stylesheets/standard.css b/public/stylesheets/standard.css index 45617228..997f2f2d 100644 --- a/public/stylesheets/standard.css +++ b/public/stylesheets/standard.css @@ -1270,6 +1270,7 @@ div.auto_complete ul { list-style-type:none; } div.auto_complete ul li { + font-size: 1.0em; margin:0; padding:3px; list-style-type: none;