diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 0d54b75f..8a329f56 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -1,268 +1,268 @@
-# The filters added to this controller will be run for all controllers in the
-# application. Likewise will all the methods added be available for all
-# controllers.
-
-require_dependency "login_system"
-require_dependency "tracks/source_view"
-require "redcloth"
-
-require 'date'
-require 'time'
-
-# Commented the following line because of #744. It prevented rake db:migrate to
-# run because this tag went looking for the taggings table that did not exist
-# when you feshly create a new database Old comment: We need this in development
-# mode, or you get 'method missing' errors
-#
-# Tag
-
-class CannotAccessContext < RuntimeError; end
-
-class ApplicationController < ActionController::Base
-
- protect_from_forgery :secret => SALT
-
- helper :application
- include LoginSystem
- helper_method :current_user, :prefs
-
- layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
-
- before_filter :set_session_expiration
- before_filter :set_time_zone
- prepend_before_filter :login_required
- prepend_before_filter :enable_mobile_content_negotiation
- after_filter :set_charset
-
-
-
- include ActionView::Helpers::TextHelper
- include ActionView::Helpers::SanitizeHelper
- extend ActionView::Helpers::SanitizeHelper::ClassMethods
- helper_method :format_date, :markdown
-
- # By default, sets the charset to UTF-8 if it isn't already set
- def set_charset
- headers["Content-Type"] ||= "text/html; charset=UTF-8"
- end
-
- def set_session_expiration
- # http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
- unless session == nil
- return if @controller_name == 'feed' or session['noexpiry'] == "on"
- # If the method is called by the feed controller (which we don't have
- # under session control) or if we checked the box to keep logged in on
- # login don't set the session expiry time.
- if session
- # Get expiry time (allow ten seconds window for the case where we have
- # none)
- expiry_time = session['expiry_time'] || Time.now + 10
- if expiry_time < Time.now
- # Too late, matey... bang goes your session!
- reset_session
- else
- # Okay, you get another hour
- session['expiry_time'] = Time.now + (60*60)
- end
- end
- end
- end
-
- def render_failure message, status = 404
- render :text => message, :status => status
- end
-
- # def rescue_action(exception)
- # log_error(exception) if logger
- # respond_to do |format|
- # format.html do
- # notify :warning, "An error occurred on the server."
- # render :action => "index"
- # end
- # format.js { render :action => 'error' }
- # format.xml { render :text => 'An error occurred on the server.' + $! }
- # end
- # end
-
- # Returns a count of next actions in the given context or project The result
- # is count and a string descriptor, correctly pluralised if there are no
- # actions or multiple actions
- #
- def count_undone_todos_phrase(todos_parent, string="actions")
- count = count_undone_todos(todos_parent)
- if count == 1
- word = string.singularize
- else
- word = string.pluralize
- end
- return count.to_s + " " + word
- end
-
- def count_undone_todos(todos_parent)
- if todos_parent.nil?
- count = 0
- elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
- count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
- else
- count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
- end
- count || 0
- end
-
- # Convert a date object to the format specified in the user's preferences in
- # config/settings.yml
- #
- def format_date(date)
- if date
- date_format = prefs.date_format
- formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}")
- else
- formatted_date = ''
- end
- formatted_date
- end
-
- # Uses RedCloth to transform text using either Textile or Markdown Need to
- # require redcloth above RedCloth 3.0 or greater is needed to use Markdown,
- # otherwise it only handles Textile
- #
- def markdown(text)
- RedCloth.new(text).to_html
- end
-
- def build_default_project_context_name_map(projects)
- Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
- end
-
- # Here's the concept behind this "mobile content negotiation" hack: In
- # addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
- # 'mobile' version designed to be suitablef or use from a phone or PDA. It
- # makes some sense that tne pages of that mobile version are simply alternate
- # representations of the same Todo resources. The implementation goal was to
- # treat mobile as another format and be able to use respond_to to render both
- # versions. Unfortunately, I ran into a lot of trouble simply registering a
- # new mime type 'text/html' with format :m because :html already is linked to
- # that mime type and the new registration was forcing all html requests to be
- # rendered in the mobile view. The before_filter and after_filter hackery
- # below accomplishs that implementation goal by using a 'fake' mime type
- # during the processing and then setting it to 'text/html' in an
- # 'after_filter' -LKM 2007-04-01
- def mobile?
- return params[:format] == 'm'
- end
-
- def enable_mobile_content_negotiation
- if mobile?
- request.format = :m
- end
- end
-
- def create_todo_from_recurring_todo(rt, date=nil)
- # create todo and initialize with data from recurring_todo rt
- todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
-
- # set dates
- todo.recurring_todo_id = rt.id
- todo.due = rt.get_due_date(date)
-
- show_from_date = rt.get_show_from_date(date)
- if show_from_date.nil?
- todo.show_from=nil
- else
- # make sure that show_from is not in the past
- todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
- end
-
- saved = todo.save
- if saved
- todo.tag_with(rt.tag_list, current_user)
- todo.tags.reload
- end
-
- # increate number of occurences created from recurring todo
- rt.inc_occurences
-
- # mark recurring todo complete if there are no next actions left
- checkdate = todo.due.nil? ? todo.show_from : todo.due
- rt.toggle_completion! unless rt.has_next_todo(checkdate)
-
- return saved ? todo : nil
- end
-
- protected
-
- def admin_login_required
- unless User.find_by_id_and_is_admin(session['user_id'], true)
- render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401
- return false
- end
- end
-
- def redirect_back_or_home
- respond_to do |format|
- format.html { redirect_back_or_default home_url }
- format.m { redirect_back_or_default mobile_url }
- end
- end
-
- def boolean_param(param_name)
- return false if param_name.blank?
- s = params[param_name]
- return false if s.blank? || s == false || s =~ /^false$/i
- return true if s == true || s =~ /^true$/i
- raise ArgumentError.new("invalid value for Boolean: \"#{s}\"")
- end
-
- def self.openid_enabled?
- Tracks::Config.openid_enabled?
- end
-
- def openid_enabled?
- self.class.openid_enabled?
- end
-
- private
-
- def parse_date_per_user_prefs( s )
- prefs.parse_date(s)
- end
-
- def init_data_for_sidebar
- @completed_projects = current_user.projects.completed
- @hidden_projects = current_user.projects.hidden
- @active_projects = current_user.projects.active
-
- @active_contexts = current_user.contexts.active
- @hidden_contexts = current_user.contexts.hidden
-
- init_not_done_counts
- if prefs.show_hidden_projects_in_sidebar
- init_project_hidden_todo_counts(['project'])
- end
- end
-
- def init_not_done_counts(parents = ['project','context'])
- parents.each do |parent|
- eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)")
- end
- end
-
- def init_project_hidden_todo_counts(parents = ['project','context'])
- parents.each do |parent|
- eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || current_user.todos.count(:conditions => ['state = ? or state = ?', 'project_hidden', 'active'], :group => :#{parent}_id)")
- end
- end
-
- # Set the contents of the flash message from a controller Usage: notify
- # :warning, "This is the message" Sets the flash of type 'warning' to "This is
- # the message"
- def notify(type, message)
- flash[type] = message
- logger.error("ERROR: #{message}") if type == :error
- end
-
- def set_time_zone
- Time.zone = current_user.prefs.time_zone if logged_in?
- end
-
-end
+# The filters added to this controller will be run for all controllers in the
+# application. Likewise will all the methods added be available for all
+# controllers.
+
+require_dependency "login_system"
+require_dependency "tracks/source_view"
+require "redcloth"
+
+require 'date'
+require 'time'
+
+# Commented the following line because of #744. It prevented rake db:migrate to
+# run because this tag went looking for the taggings table that did not exist
+# when you feshly create a new database Old comment: We need this in development
+# mode, or you get 'method missing' errors
+#
+# Tag
+
+class CannotAccessContext < RuntimeError; end
+
+class ApplicationController < ActionController::Base
+
+ protect_from_forgery :secret => SALT
+
+ helper :application
+ include LoginSystem
+ helper_method :current_user, :prefs
+
+ layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
+
+ before_filter :set_session_expiration
+ before_filter :set_time_zone
+ prepend_before_filter :login_required
+ prepend_before_filter :enable_mobile_content_negotiation
+ after_filter :set_charset
+
+
+
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::SanitizeHelper
+ extend ActionView::Helpers::SanitizeHelper::ClassMethods
+ helper_method :format_date, :markdown
+
+ # By default, sets the charset to UTF-8 if it isn't already set
+ def set_charset
+ headers["Content-Type"] ||= "text/html; charset=UTF-8"
+ end
+
+ def set_session_expiration
+ # http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
+ unless session == nil
+ return if @controller_name == 'feed' or session['noexpiry'] == "on"
+ # If the method is called by the feed controller (which we don't have
+ # under session control) or if we checked the box to keep logged in on
+ # login don't set the session expiry time.
+ if session
+ # Get expiry time (allow ten seconds window for the case where we have
+ # none)
+ expiry_time = session['expiry_time'] || Time.now + 10
+ if expiry_time < Time.now
+ # Too late, matey... bang goes your session!
+ reset_session
+ else
+ # Okay, you get another hour
+ session['expiry_time'] = Time.now + (60*60)
+ end
+ end
+ end
+ end
+
+ def render_failure message, status = 404
+ render :text => message, :status => status
+ end
+
+ # def rescue_action(exception)
+ # log_error(exception) if logger
+ # respond_to do |format|
+ # format.html do
+ # notify :warning, "An error occurred on the server."
+ # render :action => "index"
+ # end
+ # format.js { render :action => 'error' }
+ # format.xml { render :text => 'An error occurred on the server.' + $! }
+ # end
+ # end
+
+ # Returns a count of next actions in the given context or project The result
+ # is count and a string descriptor, correctly pluralised if there are no
+ # actions or multiple actions
+ #
+ def count_undone_todos_phrase(todos_parent, string="actions")
+ count = count_undone_todos(todos_parent)
+ if count == 1
+ word = string.singularize
+ else
+ word = string.pluralize
+ end
+ return count.to_s + " " + word
+ end
+
+ def count_undone_todos(todos_parent)
+ if todos_parent.nil?
+ count = 0
+ elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
+ count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
+ else
+ count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
+ end
+ count || 0
+ end
+
+ # Convert a date object to the format specified in the user's preferences in
+ # config/settings.yml
+ #
+ def format_date(date)
+ if date
+ date_format = prefs.date_format
+ formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}")
+ else
+ formatted_date = ''
+ end
+ formatted_date
+ end
+
+ # Uses RedCloth to transform text using either Textile or Markdown Need to
+ # require redcloth above RedCloth 3.0 or greater is needed to use Markdown,
+ # otherwise it only handles Textile
+ #
+ def markdown(text)
+ RedCloth.new(text).to_html
+ end
+
+ def build_default_project_context_name_map(projects)
+ Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
+ end
+
+ # Here's the concept behind this "mobile content negotiation" hack: In
+ # addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
+ # 'mobile' version designed to be suitablef or use from a phone or PDA. It
+ # makes some sense that tne pages of that mobile version are simply alternate
+ # representations of the same Todo resources. The implementation goal was to
+ # treat mobile as another format and be able to use respond_to to render both
+ # versions. Unfortunately, I ran into a lot of trouble simply registering a
+ # new mime type 'text/html' with format :m because :html already is linked to
+ # that mime type and the new registration was forcing all html requests to be
+ # rendered in the mobile view. The before_filter and after_filter hackery
+ # below accomplishs that implementation goal by using a 'fake' mime type
+ # during the processing and then setting it to 'text/html' in an
+ # 'after_filter' -LKM 2007-04-01
+ def mobile?
+ return params[:format] == 'm'
+ end
+
+ def enable_mobile_content_negotiation
+ if mobile?
+ request.format = :m
+ end
+ end
+
+ def create_todo_from_recurring_todo(rt, date=nil)
+ # create todo and initialize with data from recurring_todo rt
+ todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
+
+ # set dates
+ todo.recurring_todo_id = rt.id
+ todo.due = rt.get_due_date(date)
+
+ show_from_date = rt.get_show_from_date(date)
+ if show_from_date.nil?
+ todo.show_from=nil
+ else
+ # make sure that show_from is not in the past
+ todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
+ end
+
+ saved = todo.save
+ if saved
+ todo.tag_with(rt.tag_list)
+ todo.tags.reload
+ end
+
+ # increate number of occurences created from recurring todo
+ rt.inc_occurences
+
+ # mark recurring todo complete if there are no next actions left
+ checkdate = todo.due.nil? ? todo.show_from : todo.due
+ rt.toggle_completion! unless rt.has_next_todo(checkdate)
+
+ return saved ? todo : nil
+ end
+
+ protected
+
+ def admin_login_required
+ unless User.find_by_id_and_is_admin(session['user_id'], true)
+ render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401
+ return false
+ end
+ end
+
+ def redirect_back_or_home
+ respond_to do |format|
+ format.html { redirect_back_or_default home_url }
+ format.m { redirect_back_or_default mobile_url }
+ end
+ end
+
+ def boolean_param(param_name)
+ return false if param_name.blank?
+ s = params[param_name]
+ return false if s.blank? || s == false || s =~ /^false$/i
+ return true if s == true || s =~ /^true$/i
+ raise ArgumentError.new("invalid value for Boolean: \"#{s}\"")
+ end
+
+ def self.openid_enabled?
+ Tracks::Config.openid_enabled?
+ end
+
+ def openid_enabled?
+ self.class.openid_enabled?
+ end
+
+ private
+
+ def parse_date_per_user_prefs( s )
+ prefs.parse_date(s)
+ end
+
+ def init_data_for_sidebar
+ @completed_projects = current_user.projects.completed
+ @hidden_projects = current_user.projects.hidden
+ @active_projects = current_user.projects.active
+
+ @active_contexts = current_user.contexts.active
+ @hidden_contexts = current_user.contexts.hidden
+
+ init_not_done_counts
+ if prefs.show_hidden_projects_in_sidebar
+ init_project_hidden_todo_counts(['project'])
+ end
+ end
+
+ def init_not_done_counts(parents = ['project','context'])
+ parents.each do |parent|
+ eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)")
+ end
+ end
+
+ def init_project_hidden_todo_counts(parents = ['project','context'])
+ parents.each do |parent|
+ eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || current_user.todos.count(:conditions => ['state = ? or state = ?', 'project_hidden', 'active'], :group => :#{parent}_id)")
+ end
+ end
+
+ # Set the contents of the flash message from a controller Usage: notify
+ # :warning, "This is the message" Sets the flash of type 'warning' to "This is
+ # the message"
+ def notify(type, message)
+ flash[type] = message
+ logger.error("ERROR: #{message}") if type == :error
+ end
+
+ def set_time_zone
+ Time.zone = current_user.prefs.time_zone if logged_in?
+ end
+
+end
diff --git a/app/controllers/contexts_controller.rb b/app/controllers/contexts_controller.rb
index 348151aa..2412ba95 100644
--- a/app/controllers/contexts_controller.rb
+++ b/app/controllers/contexts_controller.rb
@@ -23,6 +23,7 @@ class ContextsController < ApplicationController
end
def show
+ @contexts = current_user.contexts(true)
if (@context.nil?)
respond_to do |format|
format.html { render :text => 'Context not found', :status => 404 }
diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb
index 668e6acb..aaed6a33 100644
--- a/app/controllers/recurring_todos_controller.rb
+++ b/app/controllers/recurring_todos_controller.rb
@@ -1,268 +1,268 @@
-class RecurringTodosController < ApplicationController
-
- helper :todos, :recurring_todos
-
- append_before_filter :init, :only => [:index, :new, :edit]
- append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update]
-
- def index
- find_and_inactivate
-
- @recurring_todos = current_user.recurring_todos.active
- @completed_recurring_todos = current_user.recurring_todos.completed
- @no_recurring_todos = @recurring_todos.size == 0
- @no_completed_recurring_todos = @completed_recurring_todos.size == 0
- @count = @recurring_todos.size
-
- @page_title = "TRACKS::Recurring Actions"
- end
-
- def new
- end
-
- def show
- end
-
- def edit
- respond_to do |format|
- format.js
- end
- end
-
- def update
- @recurring_todo.tag_with(params[:tag_list], current_user) if params[:tag_list]
- @original_item_context_id = @recurring_todo.context_id
- @original_item_project_id = @recurring_todo.project_id
-
- # we needed to rename the recurring_period selector in the edit form because
- # the form for a new recurring todo and the edit form are on the same page.
- # Same goes for start_from and end_date
- params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period']
- params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date'])
- params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from'])
-
- # update project
- if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil?
- if params['project_name'] == 'None'
- project = Project.null_object
- else
- project = current_user.projects.find_by_name(params['project_name'].strip)
- unless project
- project = current_user.projects.build
- project.name = params['project_name'].strip
- project.save
- @new_project_created = true
- end
- end
- params["recurring_todo"]["project_id"] = project.id
- end
-
- # update context
- if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank?
- context = current_user.contexts.find_by_name(params['context_name'].strip)
- unless context
- context = current_user.contexts.build
- context.name = params['context_name'].strip
- context.save
- @new_context_created = true
- end
- params["recurring_todo"]["context_id"] = context.id
- end
-
- params["recurring_todo"]["weekly_return_monday"]=' ' if params["recurring_todo"]["weekly_return_monday"].nil?
- params["recurring_todo"]["weekly_return_tuesday"]=' ' if params["recurring_todo"]["weekly_return_tuesday"].nil?
- params["recurring_todo"]["weekly_return_wednesday"]=' ' if params["recurring_todo"]["weekly_return_wednesday"].nil?
- params["recurring_todo"]["weekly_return_thursday"]=' ' if params["recurring_todo"]["weekly_return_thursday"].nil?
- params["recurring_todo"]["weekly_return_friday"]=' ' if params["recurring_todo"]["weekly_return_friday"].nil?
- params["recurring_todo"]["weekly_return_saturday"]=' ' if params["recurring_todo"]["weekly_return_saturday"].nil?
- params["recurring_todo"]["weekly_return_sunday"]=' ' if params["recurring_todo"]["weekly_return_sunday"].nil?
-
- @saved = @recurring_todo.update_attributes params["recurring_todo"]
-
- respond_to do |format|
- format.js
- end
- end
-
- def create
- p = RecurringTodoCreateParamsHelper.new(params)
- p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
- p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from'])
-
- @recurring_todo = current_user.recurring_todos.build(p.selector_attributes)
- @recurring_todo.update_attributes(p.attributes)
-
- if p.project_specified_by_name?
- project = current_user.projects.find_or_create_by_name(p.project_name)
- @new_project_created = project.new_record_before_save?
- @recurring_todo.project_id = project.id
- end
-
- if p.context_specified_by_name?
- context = current_user.contexts.find_or_create_by_name(p.context_name)
- @new_context_created = context.new_record_before_save?
- @recurring_todo.context_id = context.id
- end
-
- @recurring_saved = @recurring_todo.save
- unless (@recurring_saved == false) || p.tag_list.blank?
- @recurring_todo.tag_with(p.tag_list, current_user)
- @recurring_todo.tags.reload
- end
-
- if @recurring_saved
- @message = "The recurring todo was saved"
- @todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false
- if @todo_saved
- @message += " / created a new todo"
- else
- @message += " / did not create todo"
- end
- @count = current_user.recurring_todos.active.count
- else
- @message = "Error saving recurring todo"
- end
-
- respond_to do |format|
- format.js
- end
- end
-
- def destroy
-
- # remove all references to this recurring todo
- @todos = @recurring_todo.todos
- @number_of_todos = @todos.size
- @todos.each do |t|
- t.recurring_todo_id = nil
- t.save
- end
-
- # delete the recurring todo
- @saved = @recurring_todo.destroy
- @remaining = current_user.recurring_todos.count
-
- respond_to do |format|
-
- format.html do
- if @saved
- notify :notice, "Successfully deleted recurring action", 2.0
- redirect_to :action => 'index'
- else
- notify :error, "Failed to delete the recurring action", 2.0
- redirect_to :action => 'index'
- end
- end
-
- format.js do
- render
- end
- end
- end
-
- def toggle_check
- @saved = @recurring_todo.toggle_completion!
-
- @count = current_user.recurring_todos.active.count
- @remaining = @count
-
- if @recurring_todo.active?
- @remaining = current_user.recurring_todos.completed.count
-
- # from completed back to active -> check if there is an active todo
- # current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]})
- @active_todos = @recurring_todo.todos.active.count
- # create todo if there is no active todo belonging to the activated
- # recurring_todo
- @new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0
- end
-
- respond_to do |format|
- format.js
- end
- end
-
- def toggle_star
- @recurring_todo.toggle_star!
- @saved = @recurring_todo.save!
- respond_to do |format|
- format.js
- end
- end
-
- class RecurringTodoCreateParamsHelper
-
- def initialize(params)
- @params = params['request'] || params
- @attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo']
-
- # make sure all selectors (recurring_period, recurrence_selector,
- # daily_selector, monthly_selector and yearly_selector) are first in hash
- # so that they are processed first by the model
- @selector_attributes = {
- 'recurring_period' => @attributes['recurring_period'],
- 'daily_selector' => @attributes['daily_selector'],
- 'monthly_selector' => @attributes['monthly_selector'],
- 'yearly_selector' => @attributes['yearly_selector']
- }
- end
-
- def attributes
- @attributes
- end
-
- def selector_attributes
- return @selector_attributes
- end
-
- def project_name
- @params['project_name'].strip unless @params['project_name'].nil?
- end
-
- def context_name
- @params['context_name'].strip unless @params['context_name'].nil?
- end
-
- def tag_list
- @params['tag_list']
- end
-
- def project_specified_by_name?
- return false unless @attributes['project_id'].blank?
- return false if project_name.blank?
- return false if project_name == 'None'
- true
- end
-
- def context_specified_by_name?
- return false unless @attributes['context_id'].blank?
- return false if context_name.blank?
- true
- end
-
- end
-
- private
-
- def init
- @days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]]
- @months_of_year = [
- ['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6],
- ['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]]
- @xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]]
- @projects = current_user.projects.find(:all, :include => [:default_context])
- @contexts = current_user.contexts.find(:all)
- @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
- end
-
- def get_recurring_todo_from_param
- @recurring_todo = current_user.recurring_todos.find(params[:id])
- end
-
- def find_and_inactivate
- # find active recurring todos without active todos and inactivate them
- recurring_todos = current_user.recurring_todos.active
- recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
- end
-
-end
+class RecurringTodosController < ApplicationController
+
+ helper :todos, :recurring_todos
+
+ append_before_filter :init, :only => [:index, :new, :edit]
+ append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update]
+
+ def index
+ find_and_inactivate
+
+ @recurring_todos = current_user.recurring_todos.active
+ @completed_recurring_todos = current_user.recurring_todos.completed
+ @no_recurring_todos = @recurring_todos.size == 0
+ @no_completed_recurring_todos = @completed_recurring_todos.size == 0
+ @count = @recurring_todos.size
+
+ @page_title = "TRACKS::Recurring Actions"
+ end
+
+ def new
+ end
+
+ def show
+ end
+
+ def edit
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def update
+ @recurring_todo.tag_with(params[:tag_list]) if params[:tag_list]
+ @original_item_context_id = @recurring_todo.context_id
+ @original_item_project_id = @recurring_todo.project_id
+
+ # we needed to rename the recurring_period selector in the edit form because
+ # the form for a new recurring todo and the edit form are on the same page.
+ # Same goes for start_from and end_date
+ params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period']
+ params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date'])
+ params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from'])
+
+ # update project
+ if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil?
+ if params['project_name'] == 'None'
+ project = Project.null_object
+ else
+ project = current_user.projects.find_by_name(params['project_name'].strip)
+ unless project
+ project = current_user.projects.build
+ project.name = params['project_name'].strip
+ project.save
+ @new_project_created = true
+ end
+ end
+ params["recurring_todo"]["project_id"] = project.id
+ end
+
+ # update context
+ if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank?
+ context = current_user.contexts.find_by_name(params['context_name'].strip)
+ unless context
+ context = current_user.contexts.build
+ context.name = params['context_name'].strip
+ context.save
+ @new_context_created = true
+ end
+ params["recurring_todo"]["context_id"] = context.id
+ end
+
+ params["recurring_todo"]["weekly_return_monday"]=' ' if params["recurring_todo"]["weekly_return_monday"].nil?
+ params["recurring_todo"]["weekly_return_tuesday"]=' ' if params["recurring_todo"]["weekly_return_tuesday"].nil?
+ params["recurring_todo"]["weekly_return_wednesday"]=' ' if params["recurring_todo"]["weekly_return_wednesday"].nil?
+ params["recurring_todo"]["weekly_return_thursday"]=' ' if params["recurring_todo"]["weekly_return_thursday"].nil?
+ params["recurring_todo"]["weekly_return_friday"]=' ' if params["recurring_todo"]["weekly_return_friday"].nil?
+ params["recurring_todo"]["weekly_return_saturday"]=' ' if params["recurring_todo"]["weekly_return_saturday"].nil?
+ params["recurring_todo"]["weekly_return_sunday"]=' ' if params["recurring_todo"]["weekly_return_sunday"].nil?
+
+ @saved = @recurring_todo.update_attributes params["recurring_todo"]
+
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def create
+ p = RecurringTodoCreateParamsHelper.new(params)
+ p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
+ p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from'])
+
+ @recurring_todo = current_user.recurring_todos.build(p.selector_attributes)
+ @recurring_todo.update_attributes(p.attributes)
+
+ if p.project_specified_by_name?
+ project = current_user.projects.find_or_create_by_name(p.project_name)
+ @new_project_created = project.new_record_before_save?
+ @recurring_todo.project_id = project.id
+ end
+
+ if p.context_specified_by_name?
+ context = current_user.contexts.find_or_create_by_name(p.context_name)
+ @new_context_created = context.new_record_before_save?
+ @recurring_todo.context_id = context.id
+ end
+
+ @recurring_saved = @recurring_todo.save
+ unless (@recurring_saved == false) || p.tag_list.blank?
+ @recurring_todo.tag_with(p.tag_list)
+ @recurring_todo.tags.reload
+ end
+
+ if @recurring_saved
+ @message = "The recurring todo was saved"
+ @todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false
+ if @todo_saved
+ @message += " / created a new todo"
+ else
+ @message += " / did not create todo"
+ end
+ @count = current_user.recurring_todos.active.count
+ else
+ @message = "Error saving recurring todo"
+ end
+
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def destroy
+
+ # remove all references to this recurring todo
+ @todos = @recurring_todo.todos
+ @number_of_todos = @todos.size
+ @todos.each do |t|
+ t.recurring_todo_id = nil
+ t.save
+ end
+
+ # delete the recurring todo
+ @saved = @recurring_todo.destroy
+ @remaining = current_user.recurring_todos.count
+
+ respond_to do |format|
+
+ format.html do
+ if @saved
+ notify :notice, "Successfully deleted recurring action", 2.0
+ redirect_to :action => 'index'
+ else
+ notify :error, "Failed to delete the recurring action", 2.0
+ redirect_to :action => 'index'
+ end
+ end
+
+ format.js do
+ render
+ end
+ end
+ end
+
+ def toggle_check
+ @saved = @recurring_todo.toggle_completion!
+
+ @count = current_user.recurring_todos.active.count
+ @remaining = @count
+
+ if @recurring_todo.active?
+ @remaining = current_user.recurring_todos.completed.count
+
+ # from completed back to active -> check if there is an active todo
+ # current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]})
+ @active_todos = @recurring_todo.todos.active.count
+ # create todo if there is no active todo belonging to the activated
+ # recurring_todo
+ @new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0
+ end
+
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def toggle_star
+ @recurring_todo.toggle_star!
+ @saved = @recurring_todo.save!
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ class RecurringTodoCreateParamsHelper
+
+ def initialize(params)
+ @params = params['request'] || params
+ @attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo']
+
+ # make sure all selectors (recurring_period, recurrence_selector,
+ # daily_selector, monthly_selector and yearly_selector) are first in hash
+ # so that they are processed first by the model
+ @selector_attributes = {
+ 'recurring_period' => @attributes['recurring_period'],
+ 'daily_selector' => @attributes['daily_selector'],
+ 'monthly_selector' => @attributes['monthly_selector'],
+ 'yearly_selector' => @attributes['yearly_selector']
+ }
+ end
+
+ def attributes
+ @attributes
+ end
+
+ def selector_attributes
+ return @selector_attributes
+ end
+
+ def project_name
+ @params['project_name'].strip unless @params['project_name'].nil?
+ end
+
+ def context_name
+ @params['context_name'].strip unless @params['context_name'].nil?
+ end
+
+ def tag_list
+ @params['tag_list']
+ end
+
+ def project_specified_by_name?
+ return false unless @attributes['project_id'].blank?
+ return false if project_name.blank?
+ return false if project_name == 'None'
+ true
+ end
+
+ def context_specified_by_name?
+ return false unless @attributes['context_id'].blank?
+ return false if context_name.blank?
+ true
+ end
+
+ end
+
+ private
+
+ def init
+ @days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]]
+ @months_of_year = [
+ ['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6],
+ ['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]]
+ @xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]]
+ @projects = current_user.projects.find(:all, :include => [:default_context])
+ @contexts = current_user.contexts.find(:all)
+ @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
+ end
+
+ def get_recurring_todo_from_param
+ @recurring_todo = current_user.recurring_todos.find(params[:id])
+ end
+
+ def find_and_inactivate
+ # find active recurring todos without active todos and inactivate them
+ recurring_todos = current_user.recurring_todos.active
+ recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
+ end
+
+end
diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb
index 8ccf710a..ff5b767f 100644
--- a/app/controllers/todos_controller.rb
+++ b/app/controllers/todos_controller.rb
@@ -1,932 +1,932 @@
-class TodosController < ApplicationController
-
- helper :todos
-
- skip_before_filter :login_required, :only => [:index, :calendar]
- prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar]
- append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create, :calendar ]
- append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ]
-
- session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
-
- def index
- current_user.deferred_todos.find_and_activate_ready
- @projects = current_user.projects.find(:all, :include => [:default_context])
- @contexts = current_user.contexts.find(:all)
-
- @contexts_to_show = current_user.contexts.active
-
- respond_to do |format|
- format.html &render_todos_html
- format.m &render_todos_mobile
- format.xml { render :xml => @todos.to_xml( :except => :user_id ) }
- format.rss &render_rss_feed
- format.atom &render_atom_feed
- format.text &render_text_feed
- format.ics &render_ical_feed
- end
- end
-
- def new
- @projects = current_user.projects.active
- @contexts = current_user.contexts.find(:all)
- respond_to do |format|
- format.m {
- @new_mobile = true
- @return_path=cookies[:mobile_url]
- @mobile_from_context = current_user.contexts.find_by_id(params[:from_context]) if params[:from_context]
- @mobile_from_project = current_user.projects.find_by_id(params[:from_project]) if params[:from_project]
- if params[:from_project] && !params[:from_context]
- # we have a project but not a context -> use the default context
- @mobile_from_context = @mobile_from_project.default_context
- end
- render :action => "new"
- }
- end
- end
-
- def create
- @source_view = params['_source_view'] || 'todo'
- p = TodoCreateParamsHelper.new(params, prefs)
- p.parse_dates() unless mobile?
-
- @todo = current_user.todos.build(p.attributes)
-
- if p.project_specified_by_name?
- project = current_user.projects.find_or_create_by_name(p.project_name)
- @new_project_created = project.new_record_before_save?
- @todo.project_id = project.id
- end
-
- if p.context_specified_by_name?
- context = current_user.contexts.find_or_create_by_name(p.context_name)
- @new_context_created = context.new_record_before_save?
- @not_done_todos = [@todo] if @new_context_created
- @todo.context_id = context.id
- end
-
- @saved = @todo.save
- unless (@saved == false) || p.tag_list.blank?
- @todo.tag_with(p.tag_list, current_user)
- @todo.tags.reload
- end
-
- respond_to do |format|
- format.html { redirect_to :action => "index" }
- format.m do
- @return_path=cookies[:mobile_url]
- # todo: use function for this fixed path
- @return_path='/m' if @return_path.nil?
- if @saved
- redirect_to @return_path
- else
- @projects = current_user.projects.find(:all)
- @contexts = current_user.contexts.find(:all)
- render :action => "new"
- end
- end
- format.js do
- determine_down_count if @saved
- @contexts = current_user.contexts.find(:all) if @new_context_created
- @projects = current_user.projects.find(:all) if @new_project_created
- @initial_context_name = params['default_context_name']
- @initial_project_name = params['default_project_name']
- render :action => 'create'
- end
- format.xml do
- if @saved
- head :created, :location => todo_url(@todo)
- else
- render :xml => @todo.errors.to_xml, :status => 422
- end
- end
- end
- end
-
- def edit
- @projects = current_user.projects.find(:all)
- @contexts = current_user.contexts.find(:all)
- @source_view = params['_source_view'] || 'todo'
- @tag_name = params['_tag_name']
- respond_to do |format|
- format.js
- end
- end
-
- def show
- respond_to do |format|
- format.m do
- @projects = current_user.projects.active
- @contexts = current_user.contexts.find(:all)
- @edit_mobile = true
- @return_path=cookies[:mobile_url]
- render :action => 'show'
- end
- format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
- end
- end
-
- # Toggles the 'done' status of the action
- #
- def toggle_check
- @source_view = params['_source_view'] || 'todo'
- @original_item_due = @todo.due
- @saved = @todo.toggle_completion!
-
- # check if this todo has a related recurring_todo. If so, create next todo
- @new_recurring_todo = check_for_next_todo(@todo) if @saved
-
- respond_to do |format|
- format.js do
- if @saved
- determine_remaining_in_context_count(@todo.context_id)
- determine_down_count
- determine_completed_count if @todo.completed?
- if source_view_is :calendar
- @original_item_due_id = get_due_id_for_calendar(@original_item_due)
- @old_due_empty = is_old_due_empty(@original_item_due_id)
- end
- end
- render
- end
- format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
- format.html do
- if @saved
- # TODO: I think this will work, but can't figure out how to test it
- notify :notice, "The action '#{@todo.description}' was marked as #{@todo.completed? ? 'complete' : 'incomplete' }"
- redirect_to :action => "index"
- else
- notify :notice, "The action '#{@todo.description}' was NOT marked as #{@todo.completed? ? 'complete' : 'incomplete' } due to an error on the server.", "index"
- redirect_to :action => "index"
- end
- end
- end
- end
-
- def toggle_star
- @todo.toggle_star!
- @saved = @todo.save!
- respond_to do |format|
- format.js
- format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
- end
- end
-
- def update
- @source_view = params['_source_view'] || 'todo'
- init_data_for_sidebar unless mobile?
- @todo.tag_with(params[:tag_list], current_user) if params[:tag_list]
- @original_item_context_id = @todo.context_id
- @original_item_project_id = @todo.project_id
- @original_item_was_deferred = @todo.deferred?
- @original_item_due = @todo.due
- @original_item_due_id = get_due_id_for_calendar(@todo.due)
-
- if params['todo']['project_id'].blank? && !params['project_name'].nil?
- if params['project_name'] == 'None'
- project = Project.null_object
- else
- project = current_user.projects.find_by_name(params['project_name'].strip)
- unless project
- project = current_user.projects.build
- project.name = params['project_name'].strip
- project.save
- @new_project_created = true
- end
- end
- params["todo"]["project_id"] = project.id
- end
-
- if params['todo']['context_id'].blank? && !params['context_name'].blank?
- context = current_user.contexts.find_by_name(params['context_name'].strip)
- unless context
- context = current_user.contexts.build
- context.name = params['context_name'].strip
- context.save
- @new_context_created = true
- @not_done_todos = [@todo]
- end
- params["todo"]["context_id"] = context.id
- end
-
- if params["todo"].has_key?("due")
- params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"])
- else
- params["todo"]["due"] = ""
- end
-
- if params['todo']['show_from']
- params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from'])
- end
-
- if params['done'] == '1' && !@todo.completed?
- @todo.complete!
- end
- # strange. if checkbox is not checked, there is no 'done' in params.
- # Therefore I've used the negation
- if !(params['done'] == '1') && @todo.completed?
- @todo.activate!
- end
-
- @saved = @todo.update_attributes params["todo"]
- @context_changed = @original_item_context_id != @todo.context_id
- @todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
-
- if source_view_is :calendar
- @due_date_changed = @original_item_due != @todo.due
- if @due_date_changed
- @old_due_empty = is_old_due_empty(@original_item_due_id)
- if @todo.due.nil?
- # do not act further on date change when date is changed to nil
- @due_date_changed = false
- else
- @new_due_id = get_due_id_for_calendar(@todo.due)
- end
- end
- end
-
- if @context_changed
- determine_remaining_in_context_count(@original_item_context_id)
- else
- determine_remaining_in_context_count(@todo.context_id)
- end
-
- @project_changed = @original_item_project_id != @todo.project_id
- if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = current_user.projects.find(@original_item_project_id).not_done_todo_count; end
- determine_down_count
- respond_to do |format|
- format.js
- format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
- format.m do
- if @saved
- if cookies[:mobile_url]
- cookies[:mobile_url] = {:value => nil, :secure => TRACKS_COOKIES_SECURE}
- redirect_to cookies[:mobile_url]
- else
- redirect_to formatted_todos_path(:m)
- end
- else
- render :action => "edit", :format => :m
- end
- end
- end
- end
-
- def destroy
- @todo = get_todo_from_params
- @original_item_due = @todo.due
- @context_id = @todo.context_id
- @project_id = @todo.project_id
-
- @saved = @todo.destroy
-
- # check if this todo has a related recurring_todo. If so, create next todo
- @new_recurring_todo = check_for_next_todo(@todo) if @saved
-
- respond_to do |format|
-
- format.html do
- if @saved
- notify :notice, "Successfully deleted next action", 2.0
- redirect_to :action => 'index'
- else
- notify :error, "Failed to delete the action", 2.0
- redirect_to :action => 'index'
- end
- end
-
- format.js do
- if @saved
- determine_down_count
- if source_view_is_one_of(:todo, :deferred)
- determine_remaining_in_context_count(@context_id)
- elsif source_view_is :calendar
- @original_item_due_id = get_due_id_for_calendar(@original_item_due)
- @old_due_empty = is_old_due_empty(@original_item_due_id)
- end
- end
- render
- end
-
- format.xml { render :text => '200 OK. Action deleted.', :status => 200 }
-
- end
- end
-
- def completed
- @page_title = "TRACKS::Completed tasks"
- @done = current_user.completed_todos
- @done_today = @done.completed_within Time.zone.now - 1.day
- @done_this_week = @done.completed_within Time.zone.now - 1.week
- @done_this_month = @done.completed_within Time.zone.now - 4.week
- @count = @done_today.size + @done_this_week.size + @done_this_month.size
- end
-
- def completed_archive
- @page_title = "TRACKS::Archived completed tasks"
- @done = current_user.completed_todos
- @count = @done.size
- @done_archive = @done.completed_more_than Time.zone.now - 28.days
- end
-
- def list_deferred
- @source_view = 'deferred'
- @page_title = "TRACKS::Tickler"
-
- @projects = current_user.projects.find(:all, :include => [ :todos, :default_context ])
- @contexts_to_show = @contexts = current_user.contexts.find(:all, :include => [ :todos ])
-
- current_user.deferred_todos.find_and_activate_ready
- @not_done_todos = current_user.deferred_todos
- @count = @not_done_todos.size
- @down_count = @count
- @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json unless mobile?
-
- respond_to do |format|
- format.html
- format.m { render :action => 'mobile_list_deferred' }
- end
- end
-
- # Check for any due tickler items, activate them Called by
- # periodically_call_remote
- def check_deferred
- @due_tickles = current_user.deferred_todos.find_and_activate_ready
- respond_to do |format|
- format.html { redirect_to home_path }
- format.js
- end
- end
-
- def filter_to_context
- context = current_user.contexts.find(params['context']['id'])
- redirect_to formatted_context_todos_path(context, :m)
- end
-
- def filter_to_project
- project = current_user.projects.find(params['project']['id'])
- redirect_to formatted_project_todos_path(project, :m)
- end
-
- # /todos/tag/[tag_name] shows all the actions tagged with tag_name
- def tag
- @source_view = params['_source_view'] || 'tag'
- @tag_name = params[:name]
- @page_title = "TRACKS::Tagged with \'#{@tag_name}\'"
-
- # mobile tags are routed with :name ending on .m. So we need to chomp it
- @tag_name = @tag_name.chomp('.m') if mobile?
-
- @tag = Tag.find_by_name(@tag_name)
- @tag = Tag.new(:name => @tag_name) if @tag.nil?
- tag_collection = @tag.todos
-
- @not_done_todos = tag_collection.find(:all,
- :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'],
- :order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC')
- @hidden_todos = current_user.todos.find(:all,
- :include => [:taggings, :tags, :context],
- :conditions => ['tags.name = ? AND (todos.state = ? OR (contexts.hide = ? AND todos.state = ?))', @tag_name, 'project_hidden', true, 'active'],
- :order => 'todos.completed_at DESC, todos.created_at DESC')
- @deferred = tag_collection.find(:all,
- :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'deferred'],
- :order => 'show_from ASC, todos.created_at DESC')
-
- # If you've set no_completed to zero, the completed items box isn't shown on
- # the tag page
- max_completed = current_user.prefs.show_number_completed
- @done = tag_collection.find(:all,
- :limit => max_completed,
- :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'completed'],
- :order => 'todos.completed_at DESC')
-
- @projects = current_user.projects
- @contexts = current_user.contexts
- @contexts_to_show = @contexts.reject {|x| x.hide? }
-
- # Set count badge to number of items with this tag
- @not_done_todos.empty? ? @count = 0 : @count = @not_done_todos.size
- @down_count = @count
-
- respond_to do |format|
- format.html {
- @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
- }
- format.m {
- cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
- render :action => "mobile_tag"
- }
- end
- end
-
- def defer
- @source_view = params['_source_view'] || 'todo'
- numdays = params['days'].to_i
- @todo = Todo.find(params[:id])
- @todo.show_from = (@todo.show_from || @todo.user.date) + numdays.days
- @saved = @todo.save
-
- determine_down_count
- determine_remaining_in_context_count(@todo.context_id)
- respond_to do |format|
- format.html { redirect_to :back }
- format.js {render :action => 'update'}
- end
- end
-
- def calendar
- @source_view = params['_source_view'] || 'calendar'
- @page_title = "TRACKS::Calendar"
-
- due_today_date = Time.zone.now
- due_this_week_date = Time.zone.now.end_of_week
- due_next_week_date = due_this_week_date + 7.days
- due_this_month_date = Time.zone.now.end_of_month
-
- @due_today = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due <= ?', due_today_date],
- :order => "due")
- @due_this_week = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date],
- :order => "due")
- @due_next_week = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date],
- :order => "due")
- @due_this_month = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date],
- :order => "due")
- @due_after_this_month = current_user.todos.not_completed.find(:all,
- :include => [:taggings, :tags],
- :conditions => ['todos.due > ?', due_this_month_date],
- :order => "due")
-
- @count = current_user.todos.not_completed.are_due.count
-
- respond_to do |format|
- format.html
- format.ics {
- @due_all = current_user.todos.not_completed.are_due.find(:all, :order => "due")
- render :action => 'calendar', :layout => false, :content_type => Mime::ICS
- }
- end
- end
-
- private
-
- def get_todo_from_params
- @todo = current_user.todos.find(params['id'])
- end
-
- def init
- @source_view = params['_source_view'] || 'todo'
- init_data_for_sidebar unless mobile?
- init_todos
- end
-
- def with_feed_query_scope(&block)
- unless TodosController.is_feed_request(request)
- Todo.send(:with_scope, :find => {:conditions => ['todos.state = ?', 'active']}) do
- yield
- return
- end
- end
- condition_builder = FindConditionBuilder.new
-
- if params.key?('done')
- condition_builder.add 'todos.state = ?', 'completed'
- else
- condition_builder.add 'todos.state = ?', 'active'
- end
-
- @title = "Tracks - Next Actions"
- @description = "Filter: "
-
- if params.key?('due')
- due_within = params['due'].to_i
- due_within_when = Time.zone.now + due_within.days
- condition_builder.add('todos.due <= ?', due_within_when)
- due_within_date_s = due_within_when.strftime("%Y-%m-%d")
- @title << " due today" if (due_within == 0)
- @title << " due within a week" if (due_within == 6)
- @description << " with a due date #{due_within_date_s} or earlier"
- end
-
- if params.key?('done')
- done_in_last = params['done'].to_i
- condition_builder.add('todos.completed_at >= ?', Time.zone.now - done_in_last.days)
- @title << " actions completed"
- @description << " in the last #{done_in_last.to_s} days"
- end
-
- if params.key?('tag')
- tag = Tag.find_by_name(params['tag'])
- if tag.nil?
- tag = Tag.new(:name => params['tag'])
- end
- condition_builder.add('taggings.tag_id = ?', tag.id)
- end
-
- Todo.send :with_scope, :find => {:conditions => condition_builder.to_conditions} do
- yield
- end
-
- end
-
- def with_parent_resource_scope(&block)
- @feed_title = "Actions "
- if (params[:context_id])
- @context = current_user.contexts.find_by_params(params)
- @feed_title = @feed_title + "in context '#{@context.name}'"
- Todo.send :with_scope, :find => {:conditions => ['todos.context_id = ?', @context.id]} do
- yield
- end
- elsif (params[:project_id])
- @project = current_user.projects.find_by_params(params)
- @feed_title = @feed_title + "in project '#{@project.name}'"
- @project_feed = true
- Todo.send :with_scope, :find => {:conditions => ['todos.project_id = ?', @project.id]} do
- yield
- end
- else
- yield
- end
- end
-
- def with_limit_scope(&block)
- if params.key?('limit')
- Todo.send :with_scope, :find => { :limit => params['limit'] } do
- yield
- end
- if TodosController.is_feed_request(request) && @description
- if params.key?('limit')
- @description << "Lists the last #{params['limit']} incomplete next actions"
- else
- @description << "Lists incomplete next actions"
- end
- end
- else
- yield
- end
- end
-
- def init_todos
- with_feed_query_scope do
- with_parent_resource_scope do # @context or @project may get defined here
- with_limit_scope do
-
- if mobile?
- init_todos_for_mobile_view
- else
-
- # Note: these next two finds were previously using
- # current_users.todos.find but that broke with_scope for :limit
-
- # Exclude hidden projects from count on home page
- @todos = Todo.find(:all, :conditions => ['todos.user_id = ?', current_user.id], :include => [ :project, :context, :tags ])
-
- # Exclude hidden projects from the home page
- @not_done_todos = Todo.find(:all,
- :conditions => ['todos.user_id = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)',
- current_user.id, false, 'active'],
- :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
- :include => [ :project, :context, :tags ])
- end
-
- end
- end
- end
- end
-
- def init_todos_for_mobile_view
- # Note: these next two finds were previously using current_users.todos.find
- # but that broke with_scope for :limit
-
- # Exclude hidden projects from the home page
- @not_done_todos = Todo.find(:all,
- :conditions => ['todos.user_id = ? AND todos.state = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)',
- current_user.id, 'active', false, 'active'],
- :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
- :include => [ :project, :context, :tags ])
- end
-
- def determine_down_count
- source_view do |from|
- from.todo do
- @down_count = Todo.count(
- :all,
- :conditions => ['todos.user_id = ? and todos.state = ? and contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', current_user.id, 'active', false, 'active'],
- :include => [ :project, :context ])
- # #@down_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos,
- # contexts WHERE todos.context_id = contexts.id and todos.user_id = ?
- # and todos.state = ? and contexts.hide = ?', current_user.id, 'active',
- # false])
- end
- from.context do
- @down_count = current_user.contexts.find(@todo.context_id).not_done_todo_count
- end
- from.project do
- unless @todo.project_id == nil
- @down_count = current_user.projects.find(@todo.project_id).not_done_todo_count(:include_project_hidden_todos => true)
- @deferred_count = current_user.projects.find(@todo.project_id).deferred_todo_count
- end
- end
- from.deferred do
- @down_count = current_user.todos.count_in_state(:deferred)
- end
- from.tag do
- @tag_name = params['_tag_name']
- @tag = Tag.find_by_name(@tag_name)
- if @tag.nil?
- @tag = Tag.new(:name => @tag_name)
- end
- tag_collection = @tag.todos
- @not_done_todos = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'])
- @not_done_todos.empty? ? @down_count = 0 : @down_count = @not_done_todos.size
- end
- end
- end
-
- def determine_remaining_in_context_count(context_id = @todo.context_id)
- source_view do |from|
- from.deferred { @remaining_in_context = current_user.contexts.find(context_id).deferred_todo_count }
- from.tag {
- tag = Tag.find_by_name(params['_tag_name'])
- if tag.nil?
- tag = Tag.new(:name => params['tag'])
- end
- @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count({:tag => tag.id})
- }
- end
- @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count if @remaining_in_context.nil?
- end
-
- def determine_completed_count
- source_view do |from|
- from.todo do
- @completed_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos, contexts WHERE todos.context_id = contexts.id and todos.user_id = ? and todos.state = ? and contexts.hide = ?', current_user.id, 'completed', false])
- end
- from.context do
- @completed_count = current_user.contexts.find(@todo.context_id).done_todo_count
- end
- from.project do
- unless @todo.project_id == nil
- @completed_count = current_user.projects.find(@todo.project_id).done_todo_count
- end
- end
- end
- end
-
- def render_todos_html
- lambda do
- @page_title = "TRACKS::List tasks"
-
- # If you've set no_completed to zero, the completed items box isn't shown
- # on the home page
- max_completed = current_user.prefs.show_number_completed
- @done = current_user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
-
- # Set count badge to number of not-done, not hidden context items
- @count = 0
- @todos.each do |x|
- if x.active?
- if x.project.nil?
- @count += 1 if !x.context.hide?
- else
- @count += 1 if x.project.active? && !x.context.hide?
- end
- end
- end
-
- @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
-
- render
- end
- end
-
- def render_todos_mobile
- lambda do
- @page_title = "All actions"
- @home = true
- cookies[:mobile_url]= { :value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
- determine_down_count
-
- render :action => 'index'
- end
- end
-
- def render_rss_feed
- lambda do
- render_rss_feed_for @todos, :feed => todo_feed_options,
- :item => {
- :title => :description,
- :link => lambda { |t| @project_feed.nil? ? context_url(t.context) : project_url(t.project) },
- :guid => lambda { |t| todo_url(t) },
- :description => todo_feed_content
- }
- end
- end
-
- def todo_feed_options
- options = Todo.feed_options(current_user)
- options[:title] = @feed_title
- return options
- end
-
- def todo_feed_content
- lambda do |i|
- item_notes = sanitize(markdown( i.notes )) if i.notes?
- due = "
Due: #{format_date(i.due)}
\n" if i.due?
- done = "Completed: #{format_date(i.completed_at)}
\n" if i.completed?
- context_link = "#{ i.context.name }"
- if i.project_id?
- project_link = "#{ i.project.name }"
- else
- project_link = "none"
- end
- "#{done||''}#{due||''}#{item_notes||''}\nProject: #{project_link}
\nContext: #{context_link}
"
- end
- end
-
- def render_atom_feed
- lambda do
- render_atom_feed_for @todos, :feed => todo_feed_options,
- :item => {
- :title => :description,
- :link => lambda { |t| context_url(t.context) },
- :description => todo_feed_content,
- :author => lambda { |p| nil }
- }
- end
- end
-
- def render_text_feed
- lambda do
- render :action => 'index', :layout => false, :content_type => Mime::TEXT
- end
- end
-
- def render_ical_feed
- lambda do
- render :action => 'index', :layout => false, :content_type => Mime::ICS
- end
- end
-
- def self.is_feed_request(req)
- ['rss','atom','txt','ics'].include?(req.parameters[:format])
- end
-
- def check_for_next_todo(todo)
- # check if this todo has a related recurring_todo. If so, create next todo
- new_recurring_todo = nil
- recurring_todo = nil
- if todo.from_recurring_todo?
- recurring_todo = todo.recurring_todo
-
- # check if there are active todos belonging to this recurring todo. only
- # add new one if all active todos are completed
- if recurring_todo.todos.active.count == 0
-
- # check for next todo either from the due date or the show_from date
- date_to_check = todo.due.nil? ? todo.show_from : todo.due
-
- # if both due and show_from are nil, check for a next todo from now
- date_to_check = Time.zone.now if date_to_check.nil?
-
- if recurring_todo.active? && recurring_todo.has_next_todo(date_to_check)
-
- # shift the reference date to yesterday if date_to_check is furher in
- # the past. This is to make sure we do not get older todos for overdue
- # todos. I.e. checking a daily todo that is overdue with 5 days will
- # create a new todo which is overdue by 4 days if we don't shift the
- # date. Discard the time part in the compare. We pick yesterday so
- # that new todos due for today will be created instead of new todos
- # for tomorrow.
- date = date_to_check.at_midnight >= Time.zone.now.at_midnight ? date_to_check : Time.zone.now-1.day
-
- new_recurring_todo = create_todo_from_recurring_todo(recurring_todo, date)
- end
- end
- end
- return new_recurring_todo
- end
-
- def get_due_id_for_calendar(due)
- return "" if due.nil?
- due_today_date = Time.zone.now
- due_this_week_date = Time.zone.now.end_of_week
- due_next_week_date = due_this_week_date + 7.days
- due_this_month_date = Time.zone.now.end_of_month
- if due <= due_today_date
- new_due_id = "due_today"
- elsif due <= due_this_week_date
- new_due_id = "due_this_week"
- elsif due <= due_next_week_date
- new_due_id = "due_next_week"
- elsif due <= due_this_month_date
- new_due_id = "due_this_month"
- else
- new_due_id = "due_after_this_month"
- end
- return new_due_id
- end
-
- def is_old_due_empty(id)
- due_today_date = Time.zone.now
- due_this_week_date = Time.zone.now.end_of_week
- due_next_week_date = due_this_week_date + 7.days
- due_this_month_date = Time.zone.now.end_of_month
- case id
- when "due_today"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due <= ?', due_today_date])
- when "due_this_week"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date])
- when "due_next_week"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date])
- when "due_this_month"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date])
- when "due_after_this_month"
- return 0 == current_user.todos.not_completed.count(:all,
- :conditions => ['todos.due > ?', due_this_month_date])
- else
- raise Exception.new, "unknown due id for calendar: '#{id}'"
- end
- end
-
- class FindConditionBuilder
-
- def initialize
- @queries = Array.new
- @params = Array.new
- end
-
- def add(query, param)
- @queries << query
- @params << param
- end
-
- def to_conditions
- [@queries.join(' AND ')] + @params
- end
- end
-
- class TodoCreateParamsHelper
-
- def initialize(params, prefs)
- @params = params['request'] || params
- @prefs = prefs
- @attributes = params['request'] && params['request']['todo'] || params['todo']
- end
-
- def attributes
- @attributes
- end
-
- def show_from
- @attributes['show_from']
- end
-
- def due
- @attributes['due']
- end
-
- def project_name
- @params['project_name'].strip unless @params['project_name'].nil?
- end
-
- def context_name
- @params['context_name'].strip unless @params['context_name'].nil?
- end
-
- def tag_list
- @params['tag_list']
- end
-
- def parse_dates()
- @attributes['show_from'] = @prefs.parse_date(show_from)
- @attributes['due'] = @prefs.parse_date(due)
- @attributes['due'] ||= ''
- end
-
- def project_specified_by_name?
- return false unless @attributes['project_id'].blank?
- return false if project_name.blank?
- return false if project_name == 'None'
- true
- end
-
- def context_specified_by_name?
- return false unless @attributes['context_id'].blank?
- return false if context_name.blank?
- true
- end
-
- end
-end
+class TodosController < ApplicationController
+
+ helper :todos
+
+ skip_before_filter :login_required, :only => [:index, :calendar]
+ prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar]
+ append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create, :calendar ]
+ append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ]
+
+ session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
+
+ def index
+ current_user.deferred_todos.find_and_activate_ready
+ @projects = current_user.projects.find(:all, :include => [:default_context])
+ @contexts = current_user.contexts.find(:all)
+
+ @contexts_to_show = current_user.contexts.active
+
+ respond_to do |format|
+ format.html &render_todos_html
+ format.m &render_todos_mobile
+ format.xml { render :xml => @todos.to_xml( :except => :user_id ) }
+ format.rss &render_rss_feed
+ format.atom &render_atom_feed
+ format.text &render_text_feed
+ format.ics &render_ical_feed
+ end
+ end
+
+ def new
+ @projects = current_user.projects.active
+ @contexts = current_user.contexts.find(:all)
+ respond_to do |format|
+ format.m {
+ @new_mobile = true
+ @return_path=cookies[:mobile_url]
+ @mobile_from_context = current_user.contexts.find_by_id(params[:from_context]) if params[:from_context]
+ @mobile_from_project = current_user.projects.find_by_id(params[:from_project]) if params[:from_project]
+ if params[:from_project] && !params[:from_context]
+ # we have a project but not a context -> use the default context
+ @mobile_from_context = @mobile_from_project.default_context
+ end
+ render :action => "new"
+ }
+ end
+ end
+
+ def create
+ @source_view = params['_source_view'] || 'todo'
+ p = TodoCreateParamsHelper.new(params, prefs)
+ p.parse_dates() unless mobile?
+
+ @todo = current_user.todos.build(p.attributes)
+
+ if p.project_specified_by_name?
+ project = current_user.projects.find_or_create_by_name(p.project_name)
+ @new_project_created = project.new_record_before_save?
+ @todo.project_id = project.id
+ end
+
+ if p.context_specified_by_name?
+ context = current_user.contexts.find_or_create_by_name(p.context_name)
+ @new_context_created = context.new_record_before_save?
+ @not_done_todos = [@todo] if @new_context_created
+ @todo.context_id = context.id
+ end
+
+ @saved = @todo.save
+ unless (@saved == false) || p.tag_list.blank?
+ @todo.tag_with(p.tag_list)
+ @todo.tags.reload
+ end
+
+ respond_to do |format|
+ format.html { redirect_to :action => "index" }
+ format.m do
+ @return_path=cookies[:mobile_url]
+ # todo: use function for this fixed path
+ @return_path='/m' if @return_path.nil?
+ if @saved
+ redirect_to @return_path
+ else
+ @projects = current_user.projects.find(:all)
+ @contexts = current_user.contexts.find(:all)
+ render :action => "new"
+ end
+ end
+ format.js do
+ determine_down_count if @saved
+ @contexts = current_user.contexts.find(:all) if @new_context_created
+ @projects = current_user.projects.find(:all) if @new_project_created
+ @initial_context_name = params['default_context_name']
+ @initial_project_name = params['default_project_name']
+ render :action => 'create'
+ end
+ format.xml do
+ if @saved
+ head :created, :location => todo_url(@todo)
+ else
+ render :xml => @todo.errors.to_xml, :status => 422
+ end
+ end
+ end
+ end
+
+ def edit
+ @projects = current_user.projects.find(:all)
+ @contexts = current_user.contexts.find(:all)
+ @source_view = params['_source_view'] || 'todo'
+ @tag_name = params['_tag_name']
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def show
+ respond_to do |format|
+ format.m do
+ @projects = current_user.projects.active
+ @contexts = current_user.contexts.find(:all)
+ @edit_mobile = true
+ @return_path=cookies[:mobile_url]
+ render :action => 'show'
+ end
+ format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
+ end
+ end
+
+ # Toggles the 'done' status of the action
+ #
+ def toggle_check
+ @source_view = params['_source_view'] || 'todo'
+ @original_item_due = @todo.due
+ @saved = @todo.toggle_completion!
+
+ # check if this todo has a related recurring_todo. If so, create next todo
+ @new_recurring_todo = check_for_next_todo(@todo) if @saved
+
+ respond_to do |format|
+ format.js do
+ if @saved
+ determine_remaining_in_context_count(@todo.context_id)
+ determine_down_count
+ determine_completed_count if @todo.completed?
+ if source_view_is :calendar
+ @original_item_due_id = get_due_id_for_calendar(@original_item_due)
+ @old_due_empty = is_old_due_empty(@original_item_due_id)
+ end
+ end
+ render
+ end
+ format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
+ format.html do
+ if @saved
+ # TODO: I think this will work, but can't figure out how to test it
+ notify :notice, "The action '#{@todo.description}' was marked as #{@todo.completed? ? 'complete' : 'incomplete' }"
+ redirect_to :action => "index"
+ else
+ notify :notice, "The action '#{@todo.description}' was NOT marked as #{@todo.completed? ? 'complete' : 'incomplete' } due to an error on the server.", "index"
+ redirect_to :action => "index"
+ end
+ end
+ end
+ end
+
+ def toggle_star
+ @todo.toggle_star!
+ @saved = @todo.save!
+ respond_to do |format|
+ format.js
+ format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
+ end
+ end
+
+ def update
+ @source_view = params['_source_view'] || 'todo'
+ init_data_for_sidebar unless mobile?
+ @todo.tag_with(params[:tag_list]) if params[:tag_list]
+ @original_item_context_id = @todo.context_id
+ @original_item_project_id = @todo.project_id
+ @original_item_was_deferred = @todo.deferred?
+ @original_item_due = @todo.due
+ @original_item_due_id = get_due_id_for_calendar(@todo.due)
+
+ if params['todo']['project_id'].blank? && !params['project_name'].nil?
+ if params['project_name'] == 'None'
+ project = Project.null_object
+ else
+ project = current_user.projects.find_by_name(params['project_name'].strip)
+ unless project
+ project = current_user.projects.build
+ project.name = params['project_name'].strip
+ project.save
+ @new_project_created = true
+ end
+ end
+ params["todo"]["project_id"] = project.id
+ end
+
+ if params['todo']['context_id'].blank? && !params['context_name'].blank?
+ context = current_user.contexts.find_by_name(params['context_name'].strip)
+ unless context
+ context = current_user.contexts.build
+ context.name = params['context_name'].strip
+ context.save
+ @new_context_created = true
+ @not_done_todos = [@todo]
+ end
+ params["todo"]["context_id"] = context.id
+ end
+
+ if params["todo"].has_key?("due")
+ params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"])
+ else
+ params["todo"]["due"] = ""
+ end
+
+ if params['todo']['show_from']
+ params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from'])
+ end
+
+ if params['done'] == '1' && !@todo.completed?
+ @todo.complete!
+ end
+ # strange. if checkbox is not checked, there is no 'done' in params.
+ # Therefore I've used the negation
+ if !(params['done'] == '1') && @todo.completed?
+ @todo.activate!
+ end
+
+ @saved = @todo.update_attributes params["todo"]
+ @context_changed = @original_item_context_id != @todo.context_id
+ @todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
+
+ if source_view_is :calendar
+ @due_date_changed = @original_item_due != @todo.due
+ if @due_date_changed
+ @old_due_empty = is_old_due_empty(@original_item_due_id)
+ if @todo.due.nil?
+ # do not act further on date change when date is changed to nil
+ @due_date_changed = false
+ else
+ @new_due_id = get_due_id_for_calendar(@todo.due)
+ end
+ end
+ end
+
+ if @context_changed
+ determine_remaining_in_context_count(@original_item_context_id)
+ else
+ determine_remaining_in_context_count(@todo.context_id)
+ end
+
+ @project_changed = @original_item_project_id != @todo.project_id
+ if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = current_user.projects.find(@original_item_project_id).not_done_todo_count; end
+ determine_down_count
+ respond_to do |format|
+ format.js
+ format.xml { render :xml => @todo.to_xml( :except => :user_id ) }
+ format.m do
+ if @saved
+ if cookies[:mobile_url]
+ cookies[:mobile_url] = {:value => nil, :secure => TRACKS_COOKIES_SECURE}
+ redirect_to cookies[:mobile_url]
+ else
+ redirect_to formatted_todos_path(:m)
+ end
+ else
+ render :action => "edit", :format => :m
+ end
+ end
+ end
+ end
+
+ def destroy
+ @todo = get_todo_from_params
+ @original_item_due = @todo.due
+ @context_id = @todo.context_id
+ @project_id = @todo.project_id
+
+ @saved = @todo.destroy
+
+ # check if this todo has a related recurring_todo. If so, create next todo
+ @new_recurring_todo = check_for_next_todo(@todo) if @saved
+
+ respond_to do |format|
+
+ format.html do
+ if @saved
+ notify :notice, "Successfully deleted next action", 2.0
+ redirect_to :action => 'index'
+ else
+ notify :error, "Failed to delete the action", 2.0
+ redirect_to :action => 'index'
+ end
+ end
+
+ format.js do
+ if @saved
+ determine_down_count
+ if source_view_is_one_of(:todo, :deferred)
+ determine_remaining_in_context_count(@context_id)
+ elsif source_view_is :calendar
+ @original_item_due_id = get_due_id_for_calendar(@original_item_due)
+ @old_due_empty = is_old_due_empty(@original_item_due_id)
+ end
+ end
+ render
+ end
+
+ format.xml { render :text => '200 OK. Action deleted.', :status => 200 }
+
+ end
+ end
+
+ def completed
+ @page_title = "TRACKS::Completed tasks"
+ @done = current_user.completed_todos
+ @done_today = @done.completed_within Time.zone.now - 1.day
+ @done_this_week = @done.completed_within Time.zone.now - 1.week
+ @done_this_month = @done.completed_within Time.zone.now - 4.week
+ @count = @done_today.size + @done_this_week.size + @done_this_month.size
+ end
+
+ def completed_archive
+ @page_title = "TRACKS::Archived completed tasks"
+ @done = current_user.completed_todos
+ @count = @done.size
+ @done_archive = @done.completed_more_than Time.zone.now - 28.days
+ end
+
+ def list_deferred
+ @source_view = 'deferred'
+ @page_title = "TRACKS::Tickler"
+
+ @projects = current_user.projects.find(:all, :include => [ :todos, :default_context ])
+ @contexts_to_show = @contexts = current_user.contexts.find(:all, :include => [ :todos ])
+
+ current_user.deferred_todos.find_and_activate_ready
+ @not_done_todos = current_user.deferred_todos
+ @count = @not_done_todos.size
+ @down_count = @count
+ @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json unless mobile?
+
+ respond_to do |format|
+ format.html
+ format.m { render :action => 'mobile_list_deferred' }
+ end
+ end
+
+ # Check for any due tickler items, activate them Called by
+ # periodically_call_remote
+ def check_deferred
+ @due_tickles = current_user.deferred_todos.find_and_activate_ready
+ respond_to do |format|
+ format.html { redirect_to home_path }
+ format.js
+ end
+ end
+
+ def filter_to_context
+ context = current_user.contexts.find(params['context']['id'])
+ redirect_to formatted_context_todos_path(context, :m)
+ end
+
+ def filter_to_project
+ project = current_user.projects.find(params['project']['id'])
+ redirect_to formatted_project_todos_path(project, :m)
+ end
+
+ # /todos/tag/[tag_name] shows all the actions tagged with tag_name
+ def tag
+ @source_view = params['_source_view'] || 'tag'
+ @tag_name = params[:name]
+ @page_title = "TRACKS::Tagged with \'#{@tag_name}\'"
+
+ # mobile tags are routed with :name ending on .m. So we need to chomp it
+ @tag_name = @tag_name.chomp('.m') if mobile?
+
+ @tag = Tag.find_by_name(@tag_name)
+ @tag = Tag.new(:name => @tag_name) if @tag.nil?
+ tag_collection = @tag.todos
+
+ @not_done_todos = tag_collection.find(:all,
+ :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'],
+ :order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC')
+ @hidden_todos = current_user.todos.find(:all,
+ :include => [:taggings, :tags, :context],
+ :conditions => ['tags.name = ? AND (todos.state = ? OR (contexts.hide = ? AND todos.state = ?))', @tag_name, 'project_hidden', true, 'active'],
+ :order => 'todos.completed_at DESC, todos.created_at DESC')
+ @deferred = tag_collection.find(:all,
+ :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'deferred'],
+ :order => 'show_from ASC, todos.created_at DESC')
+
+ # If you've set no_completed to zero, the completed items box isn't shown on
+ # the tag page
+ max_completed = current_user.prefs.show_number_completed
+ @done = tag_collection.find(:all,
+ :limit => max_completed,
+ :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'completed'],
+ :order => 'todos.completed_at DESC')
+
+ @projects = current_user.projects
+ @contexts = current_user.contexts
+ @contexts_to_show = @contexts.reject {|x| x.hide? }
+
+ # Set count badge to number of items with this tag
+ @not_done_todos.empty? ? @count = 0 : @count = @not_done_todos.size
+ @down_count = @count
+
+ respond_to do |format|
+ format.html {
+ @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
+ }
+ format.m {
+ cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
+ render :action => "mobile_tag"
+ }
+ end
+ end
+
+ def defer
+ @source_view = params['_source_view'] || 'todo'
+ numdays = params['days'].to_i
+ @todo = Todo.find(params[:id])
+ @todo.show_from = (@todo.show_from || @todo.user.date) + numdays.days
+ @saved = @todo.save
+
+ determine_down_count
+ determine_remaining_in_context_count(@todo.context_id)
+ respond_to do |format|
+ format.html { redirect_to :back }
+ format.js {render :action => 'update'}
+ end
+ end
+
+ def calendar
+ @source_view = params['_source_view'] || 'calendar'
+ @page_title = "TRACKS::Calendar"
+
+ due_today_date = Time.zone.now
+ due_this_week_date = Time.zone.now.end_of_week
+ due_next_week_date = due_this_week_date + 7.days
+ due_this_month_date = Time.zone.now.end_of_month
+
+ @due_today = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due <= ?', due_today_date],
+ :order => "due")
+ @due_this_week = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date],
+ :order => "due")
+ @due_next_week = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date],
+ :order => "due")
+ @due_this_month = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date],
+ :order => "due")
+ @due_after_this_month = current_user.todos.not_completed.find(:all,
+ :include => [:taggings, :tags],
+ :conditions => ['todos.due > ?', due_this_month_date],
+ :order => "due")
+
+ @count = current_user.todos.not_completed.are_due.count
+
+ respond_to do |format|
+ format.html
+ format.ics {
+ @due_all = current_user.todos.not_completed.are_due.find(:all, :order => "due")
+ render :action => 'calendar', :layout => false, :content_type => Mime::ICS
+ }
+ end
+ end
+
+ private
+
+ def get_todo_from_params
+ @todo = current_user.todos.find(params['id'])
+ end
+
+ def init
+ @source_view = params['_source_view'] || 'todo'
+ init_data_for_sidebar unless mobile?
+ init_todos
+ end
+
+ def with_feed_query_scope(&block)
+ unless TodosController.is_feed_request(request)
+ Todo.send(:with_scope, :find => {:conditions => ['todos.state = ?', 'active']}) do
+ yield
+ return
+ end
+ end
+ condition_builder = FindConditionBuilder.new
+
+ if params.key?('done')
+ condition_builder.add 'todos.state = ?', 'completed'
+ else
+ condition_builder.add 'todos.state = ?', 'active'
+ end
+
+ @title = "Tracks - Next Actions"
+ @description = "Filter: "
+
+ if params.key?('due')
+ due_within = params['due'].to_i
+ due_within_when = Time.zone.now + due_within.days
+ condition_builder.add('todos.due <= ?', due_within_when)
+ due_within_date_s = due_within_when.strftime("%Y-%m-%d")
+ @title << " due today" if (due_within == 0)
+ @title << " due within a week" if (due_within == 6)
+ @description << " with a due date #{due_within_date_s} or earlier"
+ end
+
+ if params.key?('done')
+ done_in_last = params['done'].to_i
+ condition_builder.add('todos.completed_at >= ?', Time.zone.now - done_in_last.days)
+ @title << " actions completed"
+ @description << " in the last #{done_in_last.to_s} days"
+ end
+
+ if params.key?('tag')
+ tag = Tag.find_by_name(params['tag'])
+ if tag.nil?
+ tag = Tag.new(:name => params['tag'])
+ end
+ condition_builder.add('taggings.tag_id = ?', tag.id)
+ end
+
+ Todo.send :with_scope, :find => {:conditions => condition_builder.to_conditions} do
+ yield
+ end
+
+ end
+
+ def with_parent_resource_scope(&block)
+ @feed_title = "Actions "
+ if (params[:context_id])
+ @context = current_user.contexts.find_by_params(params)
+ @feed_title = @feed_title + "in context '#{@context.name}'"
+ Todo.send :with_scope, :find => {:conditions => ['todos.context_id = ?', @context.id]} do
+ yield
+ end
+ elsif (params[:project_id])
+ @project = current_user.projects.find_by_params(params)
+ @feed_title = @feed_title + "in project '#{@project.name}'"
+ @project_feed = true
+ Todo.send :with_scope, :find => {:conditions => ['todos.project_id = ?', @project.id]} do
+ yield
+ end
+ else
+ yield
+ end
+ end
+
+ def with_limit_scope(&block)
+ if params.key?('limit')
+ Todo.send :with_scope, :find => { :limit => params['limit'] } do
+ yield
+ end
+ if TodosController.is_feed_request(request) && @description
+ if params.key?('limit')
+ @description << "Lists the last #{params['limit']} incomplete next actions"
+ else
+ @description << "Lists incomplete next actions"
+ end
+ end
+ else
+ yield
+ end
+ end
+
+ def init_todos
+ with_feed_query_scope do
+ with_parent_resource_scope do # @context or @project may get defined here
+ with_limit_scope do
+
+ if mobile?
+ init_todos_for_mobile_view
+ else
+
+ # Note: these next two finds were previously using
+ # current_users.todos.find but that broke with_scope for :limit
+
+ # Exclude hidden projects from count on home page
+ @todos = Todo.find(:all, :conditions => ['todos.user_id = ?', current_user.id], :include => [ :project, :context, :tags ])
+
+ # Exclude hidden projects from the home page
+ @not_done_todos = Todo.find(:all,
+ :conditions => ['todos.user_id = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)',
+ current_user.id, false, 'active'],
+ :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
+ :include => [ :project, :context, :tags ])
+ end
+
+ end
+ end
+ end
+ end
+
+ def init_todos_for_mobile_view
+ # Note: these next two finds were previously using current_users.todos.find
+ # but that broke with_scope for :limit
+
+ # Exclude hidden projects from the home page
+ @not_done_todos = Todo.find(:all,
+ :conditions => ['todos.user_id = ? AND todos.state = ? AND contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)',
+ current_user.id, 'active', false, 'active'],
+ :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
+ :include => [ :project, :context, :tags ])
+ end
+
+ def determine_down_count
+ source_view do |from|
+ from.todo do
+ @down_count = Todo.count(
+ :all,
+ :conditions => ['todos.user_id = ? and todos.state = ? and contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', current_user.id, 'active', false, 'active'],
+ :include => [ :project, :context ])
+ # #@down_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos,
+ # contexts WHERE todos.context_id = contexts.id and todos.user_id = ?
+ # and todos.state = ? and contexts.hide = ?', current_user.id, 'active',
+ # false])
+ end
+ from.context do
+ @down_count = current_user.contexts.find(@todo.context_id).not_done_todo_count
+ end
+ from.project do
+ unless @todo.project_id == nil
+ @down_count = current_user.projects.find(@todo.project_id).not_done_todo_count(:include_project_hidden_todos => true)
+ @deferred_count = current_user.projects.find(@todo.project_id).deferred_todo_count
+ end
+ end
+ from.deferred do
+ @down_count = current_user.todos.count_in_state(:deferred)
+ end
+ from.tag do
+ @tag_name = params['_tag_name']
+ @tag = Tag.find_by_name(@tag_name)
+ if @tag.nil?
+ @tag = Tag.new(:name => @tag_name)
+ end
+ tag_collection = @tag.todos
+ @not_done_todos = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'])
+ @not_done_todos.empty? ? @down_count = 0 : @down_count = @not_done_todos.size
+ end
+ end
+ end
+
+ def determine_remaining_in_context_count(context_id = @todo.context_id)
+ source_view do |from|
+ from.deferred { @remaining_in_context = current_user.contexts.find(context_id).deferred_todo_count }
+ from.tag {
+ tag = Tag.find_by_name(params['_tag_name'])
+ if tag.nil?
+ tag = Tag.new(:name => params['tag'])
+ end
+ @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count({:tag => tag.id})
+ }
+ end
+ @remaining_in_context = current_user.contexts.find(context_id).not_done_todo_count if @remaining_in_context.nil?
+ end
+
+ def determine_completed_count
+ source_view do |from|
+ from.todo do
+ @completed_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos, contexts WHERE todos.context_id = contexts.id and todos.user_id = ? and todos.state = ? and contexts.hide = ?', current_user.id, 'completed', false])
+ end
+ from.context do
+ @completed_count = current_user.contexts.find(@todo.context_id).done_todo_count
+ end
+ from.project do
+ unless @todo.project_id == nil
+ @completed_count = current_user.projects.find(@todo.project_id).done_todo_count
+ end
+ end
+ end
+ end
+
+ def render_todos_html
+ lambda do
+ @page_title = "TRACKS::List tasks"
+
+ # If you've set no_completed to zero, the completed items box isn't shown
+ # on the home page
+ max_completed = current_user.prefs.show_number_completed
+ @done = current_user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
+
+ # Set count badge to number of not-done, not hidden context items
+ @count = 0
+ @todos.each do |x|
+ if x.active?
+ if x.project.nil?
+ @count += 1 if !x.context.hide?
+ else
+ @count += 1 if x.project.active? && !x.context.hide?
+ end
+ end
+ end
+
+ @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
+
+ render
+ end
+ end
+
+ def render_todos_mobile
+ lambda do
+ @page_title = "All actions"
+ @home = true
+ cookies[:mobile_url]= { :value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
+ determine_down_count
+
+ render :action => 'index'
+ end
+ end
+
+ def render_rss_feed
+ lambda do
+ render_rss_feed_for @todos, :feed => todo_feed_options,
+ :item => {
+ :title => :description,
+ :link => lambda { |t| @project_feed.nil? ? context_url(t.context) : project_url(t.project) },
+ :guid => lambda { |t| todo_url(t) },
+ :description => todo_feed_content
+ }
+ end
+ end
+
+ def todo_feed_options
+ options = Todo.feed_options(current_user)
+ options[:title] = @feed_title
+ return options
+ end
+
+ def todo_feed_content
+ lambda do |i|
+ item_notes = sanitize(markdown( i.notes )) if i.notes?
+ due = "Due: #{format_date(i.due)}
\n" if i.due?
+ done = "Completed: #{format_date(i.completed_at)}
\n" if i.completed?
+ context_link = "#{ i.context.name }"
+ if i.project_id?
+ project_link = "#{ i.project.name }"
+ else
+ project_link = "none"
+ end
+ "#{done||''}#{due||''}#{item_notes||''}\nProject: #{project_link}
\nContext: #{context_link}
"
+ end
+ end
+
+ def render_atom_feed
+ lambda do
+ render_atom_feed_for @todos, :feed => todo_feed_options,
+ :item => {
+ :title => :description,
+ :link => lambda { |t| context_url(t.context) },
+ :description => todo_feed_content,
+ :author => lambda { |p| nil }
+ }
+ end
+ end
+
+ def render_text_feed
+ lambda do
+ render :action => 'index', :layout => false, :content_type => Mime::TEXT
+ end
+ end
+
+ def render_ical_feed
+ lambda do
+ render :action => 'index', :layout => false, :content_type => Mime::ICS
+ end
+ end
+
+ def self.is_feed_request(req)
+ ['rss','atom','txt','ics'].include?(req.parameters[:format])
+ end
+
+ def check_for_next_todo(todo)
+ # check if this todo has a related recurring_todo. If so, create next todo
+ new_recurring_todo = nil
+ recurring_todo = nil
+ if todo.from_recurring_todo?
+ recurring_todo = todo.recurring_todo
+
+ # check if there are active todos belonging to this recurring todo. only
+ # add new one if all active todos are completed
+ if recurring_todo.todos.active.count == 0
+
+ # check for next todo either from the due date or the show_from date
+ date_to_check = todo.due.nil? ? todo.show_from : todo.due
+
+ # if both due and show_from are nil, check for a next todo from now
+ date_to_check = Time.zone.now if date_to_check.nil?
+
+ if recurring_todo.active? && recurring_todo.has_next_todo(date_to_check)
+
+ # shift the reference date to yesterday if date_to_check is furher in
+ # the past. This is to make sure we do not get older todos for overdue
+ # todos. I.e. checking a daily todo that is overdue with 5 days will
+ # create a new todo which is overdue by 4 days if we don't shift the
+ # date. Discard the time part in the compare. We pick yesterday so
+ # that new todos due for today will be created instead of new todos
+ # for tomorrow.
+ date = date_to_check.at_midnight >= Time.zone.now.at_midnight ? date_to_check : Time.zone.now-1.day
+
+ new_recurring_todo = create_todo_from_recurring_todo(recurring_todo, date)
+ end
+ end
+ end
+ return new_recurring_todo
+ end
+
+ def get_due_id_for_calendar(due)
+ return "" if due.nil?
+ due_today_date = Time.zone.now
+ due_this_week_date = Time.zone.now.end_of_week
+ due_next_week_date = due_this_week_date + 7.days
+ due_this_month_date = Time.zone.now.end_of_month
+ if due <= due_today_date
+ new_due_id = "due_today"
+ elsif due <= due_this_week_date
+ new_due_id = "due_this_week"
+ elsif due <= due_next_week_date
+ new_due_id = "due_next_week"
+ elsif due <= due_this_month_date
+ new_due_id = "due_this_month"
+ else
+ new_due_id = "due_after_this_month"
+ end
+ return new_due_id
+ end
+
+ def is_old_due_empty(id)
+ due_today_date = Time.zone.now
+ due_this_week_date = Time.zone.now.end_of_week
+ due_next_week_date = due_this_week_date + 7.days
+ due_this_month_date = Time.zone.now.end_of_month
+ case id
+ when "due_today"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due <= ?', due_today_date])
+ when "due_this_week"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date])
+ when "due_next_week"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date])
+ when "due_this_month"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date])
+ when "due_after_this_month"
+ return 0 == current_user.todos.not_completed.count(:all,
+ :conditions => ['todos.due > ?', due_this_month_date])
+ else
+ raise Exception.new, "unknown due id for calendar: '#{id}'"
+ end
+ end
+
+ class FindConditionBuilder
+
+ def initialize
+ @queries = Array.new
+ @params = Array.new
+ end
+
+ def add(query, param)
+ @queries << query
+ @params << param
+ end
+
+ def to_conditions
+ [@queries.join(' AND ')] + @params
+ end
+ end
+
+ class TodoCreateParamsHelper
+
+ def initialize(params, prefs)
+ @params = params['request'] || params
+ @prefs = prefs
+ @attributes = params['request'] && params['request']['todo'] || params['todo']
+ end
+
+ def attributes
+ @attributes
+ end
+
+ def show_from
+ @attributes['show_from']
+ end
+
+ def due
+ @attributes['due']
+ end
+
+ def project_name
+ @params['project_name'].strip unless @params['project_name'].nil?
+ end
+
+ def context_name
+ @params['context_name'].strip unless @params['context_name'].nil?
+ end
+
+ def tag_list
+ @params['tag_list']
+ end
+
+ def parse_dates()
+ @attributes['show_from'] = @prefs.parse_date(show_from)
+ @attributes['due'] = @prefs.parse_date(due)
+ @attributes['due'] ||= ''
+ end
+
+ def project_specified_by_name?
+ return false unless @attributes['project_id'].blank?
+ return false if project_name.blank?
+ return false if project_name == 'None'
+ true
+ end
+
+ def context_specified_by_name?
+ return false unless @attributes['context_id'].blank?
+ return false if context_name.blank?
+ true
+ end
+
+ end
+end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 0b38d202..1bf4eb13 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -1,287 +1,287 @@
-module TodosHelper
-
- # #require 'users_controller' Counts the number of incomplete items in the
- # specified context
- #
- def count_items(context)
- count = Todo.find_all("done=0 AND context_id=#{context.id}").length
- end
-
- def form_remote_tag_edit_todo( &block )
- form_tag(
- todo_path(@todo), {
- :method => :put,
- :id => dom_id(@todo, 'form'),
- :class => dom_id(@todo, 'form') + " inline-form edit_todo_form" },
- &block )
- apply_behavior 'form.edit_todo_form', make_remote_form(
- :method => :put,
- :before => "this.down('button.positive').startWaiting()",
- :loaded => "this.down('button.positive').stopWaiting()",
- :condition => "!(this.down('button.positive').isWaiting())"),
- :prevent_default => true
- end
-
- def set_behavior_for_delete_icon
- parameters = "_source_view=#{@source_view}"
- parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
- apply_behavior '.item-container a.delete_icon:click', :prevent_default => true do |page|
- page.confirming "'Are you sure that you want to ' + this.title + '?'" do
- page << "itemContainer = this.up('.item-container'); itemContainer.startWaiting();"
- page << remote_to_href(:method => 'delete', :with => "'#{parameters}'", :complete => "itemContainer.stopWaiting();")
- end
- end
- end
-
- def remote_delete_icon
- str = link_to( image_tag_for_delete,
- todo_path(@todo), :id => "delete_icon_"+@todo.id.to_s,
- :class => "icon delete_icon", :title => "delete the action '#{@todo.description}'")
- set_behavior_for_delete_icon
- str
- end
-
- def set_behavior_for_star_icon
- apply_behavior '.item-container a.star_item:click',
- remote_to_href(:method => 'put', :with => "{ _source_view : '#{@source_view}' }"),
- :prevent_default => true
- end
-
- def remote_star_icon
- str = link_to( image_tag_for_star(@todo),
- toggle_star_todo_path(@todo),
- :class => "icon star_item", :title => "star the action '#{@todo.description}'")
- set_behavior_for_star_icon
- str
- end
-
- def set_behavior_for_edit_icon
- parameters = "_source_view=#{@source_view}"
- parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
- apply_behavior '.item-container a.edit_icon:click', :prevent_default => true do |page|
- page << "Effect.Pulsate(this);"
- page << remote_to_href(:method => 'get', :with => "'#{parameters}'")
- end
- end
-
- def remote_edit_icon
- if !@todo.completed?
- str = link_to( image_tag_for_edit(@todo),
- edit_todo_path(@todo),
- :class => "icon edit_icon")
- set_behavior_for_edit_icon
- else
- str = '' + image_tag("blank.png") + " "
- end
- str
- end
-
- def set_behavior_for_toggle_checkbox
- parameters = "_source_view=#{@source_view}"
- parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
- apply_behavior '.item-container input.item-checkbox:click',
- remote_function(:url => javascript_variable('this.value'), :method => 'put',
- :with => "'#{parameters}'")
- end
-
- def remote_toggle_checkbox
- str = check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox')
- set_behavior_for_toggle_checkbox
- str
- end
-
- def date_span
- if @todo.completed?
- "#{format_date( @todo.completed_at )}"
- elsif @todo.deferred?
- show_date( @todo.show_from )
- else
- due_date( @todo.due )
- end
- end
-
- def tag_list_text
- @todo.tags.collect{|t| t.name}.join(', ')
- end
-
- def tag_list
- tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
- tag_list = tags_except_starred.collect{|t| "" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + ""}.join('')
- "#{tag_list}"
- end
-
- def tag_list_mobile
- tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
- # removed the link. TODO: add link to mobile view of tagged actions
- tag_list = tags_except_starred.collect{|t|
- "" +
- link_to(t.name, {:action => "tag", :controller => "todos", :id => t.name+".m"}) +
- ""}.join('')
- if tag_list.empty? then "" else "#{tag_list}" end
- end
-
- def deferred_due_date
- if @todo.deferred? && @todo.due
- "(action due on #{format_date(@todo.due)})"
- end
- end
-
- def project_and_context_links(parent_container_type, opts = {})
- str = ''
- if @todo.completed?
- str += @todo.context.name unless opts[:suppress_context]
- should_suppress_project = opts[:suppress_project] || @todo.project.nil?
- str += ", " unless str.blank? || should_suppress_project
- str += @todo.project.name unless should_suppress_project
- str = "(#{str})" unless str.blank?
- else
- if (['project', 'tag', 'stats', 'search'].include?(parent_container_type))
- str << item_link_to_context( @todo )
- end
- if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && @todo.project_id
- str << item_link_to_project( @todo )
- end
- end
- return str
- end
-
- # Uses the 'staleness_starts' value from settings.yml (in days) to colour the
- # background of the action appropriately according to the age of the creation
- # date:
- # * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts
- # * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts
- # * l3: created more than 3 x staleness_starts
- #
- def staleness_class(item)
- if item.due || item.completed?
- return ""
- elsif item.created_at < user_time - (prefs.staleness_starts * 3).days
- return " stale_l3"
- elsif item.created_at < user_time - (prefs.staleness_starts * 2).days
- return " stale_l2"
- elsif item.created_at < user_time - (prefs.staleness_starts).days
- return " stale_l1"
- else
- return ""
- end
- end
-
- # Check show_from date in comparison to today's date Flag up date
- # appropriately with a 'traffic light' colour code
- #
- def show_date(d)
- if d == nil
- return ""
- end
-
- days = days_from_today(d)
-
- case days
- # overdue or due very soon! sound the alarm!
- when -1000..-1
- "Scheduled to show " + (days * -1).to_s + " days ago "
- when 0
- "Show Today "
- when 1
- "Show Tomorrow "
- # due 2-7 days away
- when 2..7
- if prefs.due_style == Preference.due_styles[:due_on]
- "Show on " + d.strftime("%A") + " "
- else
- "Show in " + days.to_s + " days "
- end
- # more than a week away - relax
- else
- "Show in " + days.to_s + " days "
- end
- end
-
- def calendar_setup( input_field )
- str = "Calendar.setup({ ifFormat:\"#{prefs.date_format}\""
- str << ",firstDay:#{prefs.week_starts},showOthers:true,range:[2004, 2010]"
- str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n"
- javascript_tag str
- end
-
- def item_container_id (todo)
- if source_view_is :project
- return "p#{todo.project_id}" if todo.active?
- return "tickler" if todo.deferred?
- end
- return "c#{todo.context_id}"
- end
-
- def should_show_new_item
-
- if @todo.project.nil? == false
- # do not show new actions that were added to hidden or completed projects
- # on home page and context page
- return false if source_view_is(:todo) && (@todo.project.hidden? || @todo.project.completed?)
- return false if source_view_is(:context) && (@todo.project.hidden? || @todo.project.completed?)
- end
-
- return true if source_view_is(:deferred) && @todo.deferred?
- return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden?
- return true if source_view_is(:project) && @todo.deferred?
- return true if !source_view_is(:deferred) && @todo.active?
- return false
- end
-
- def parent_container_type
- return 'tickler' if source_view_is :deferred
- return 'project' if source_view_is :project
- return 'stats' if source_view_is :stats
- return 'context'
- end
-
- def empty_container_msg_div_id
- return "tickler-empty-nd" if source_view_is(:project) && @todo.deferred?
- return "p#{@todo.project_id}empty-nd" if source_view_is :project
- return "c#{@todo.context_id}empty-nd"
- end
-
- def project_names_for_autocomplete
- array_or_string_for_javascript( ['None'] + current_user.projects.active.collect{|p| escape_javascript(p.name) } )
- end
-
- def context_names_for_autocomplete
- # #return array_or_string_for_javascript(['Create a new context']) if
- # @contexts.empty?
- array_or_string_for_javascript( current_user.contexts.collect{|c| escape_javascript(c.name) } )
- end
-
- def format_ical_notes(notes)
- split_notes = notes.split(/\n/)
- joined_notes = split_notes.join("\\n")
- end
-
- def formatted_pagination(total)
- s = will_paginate(@todos)
- (s.gsub /(<\/[^<]+>)/, '\1 ').chomp(' ')
- end
-
- def date_field_tag(name, id, value = nil, options = {})
- text_field_tag name, value, {"size" => 12, "id" => id, "class" => "Date", "onfocus" => "Calendar.setup", "autocomplete" => "off"}.update(options.stringify_keys)
- end
-
- private
-
- def image_tag_for_delete
- image_tag("blank.png", :title =>"Delete action", :class=>"delete_item")
- end
-
- def image_tag_for_edit(todo)
- image_tag("blank.png", :title =>"Edit action", :class=>"edit_item", :id=> dom_id(todo, 'edit_icon'))
- end
-
- def image_tag_for_star(todo)
- class_str = todo.starred? ? "starred_todo" : "unstarred_todo"
- image_tag("blank.png", :title =>"Star action", :class => class_str)
- end
-
- def defer_link(days)
- link_to_remote image_tag("defer_#{days}.png", :alt => "Defer #{pluralize(days, 'day')}"), :url => {:controller => 'todos', :action => 'defer', :id => @todo.id, :days => days, :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")}
- end
-
-end
+module TodosHelper
+
+ # #require 'users_controller' Counts the number of incomplete items in the
+ # specified context
+ #
+ def count_items(context)
+ count = Todo.find_all("done=0 AND context_id=#{context.id}").length
+ end
+
+ def form_remote_tag_edit_todo( &block )
+ form_tag(
+ todo_path(@todo), {
+ :method => :put,
+ :id => dom_id(@todo, 'form'),
+ :class => dom_id(@todo, 'form') + " inline-form edit_todo_form" },
+ &block )
+ apply_behavior 'form.edit_todo_form', make_remote_form(
+ :method => :put,
+ :before => "this.down('button.positive').startWaiting()",
+ :loaded => "this.down('button.positive').stopWaiting()",
+ :condition => "!(this.down('button.positive').isWaiting())"),
+ :prevent_default => true
+ end
+
+ def set_behavior_for_delete_icon
+ parameters = "_source_view=#{@source_view}"
+ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
+ apply_behavior '.item-container a.delete_icon:click', :prevent_default => true do |page|
+ page.confirming "'Are you sure that you want to ' + this.title + '?'" do
+ page << "itemContainer = this.up('.item-container'); itemContainer.startWaiting();"
+ page << remote_to_href(:method => 'delete', :with => "'#{parameters}'", :complete => "itemContainer.stopWaiting();")
+ end
+ end
+ end
+
+ def remote_delete_icon
+ str = link_to( image_tag_for_delete,
+ todo_path(@todo), :id => "delete_icon_"+@todo.id.to_s,
+ :class => "icon delete_icon", :title => "delete the action '#{@todo.description}'")
+ set_behavior_for_delete_icon
+ str
+ end
+
+ def set_behavior_for_star_icon
+ apply_behavior '.item-container a.star_item:click',
+ remote_to_href(:method => 'put', :with => "{ _source_view : '#{@source_view}' }"),
+ :prevent_default => true
+ end
+
+ def remote_star_icon
+ str = link_to( image_tag_for_star(@todo),
+ toggle_star_todo_path(@todo),
+ :class => "icon star_item", :title => "star the action '#{@todo.description}'")
+ set_behavior_for_star_icon
+ str
+ end
+
+ def set_behavior_for_edit_icon
+ parameters = "_source_view=#{@source_view}"
+ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
+ apply_behavior '.item-container a.edit_icon:click', :prevent_default => true do |page|
+ page << "Effect.Pulsate(this);"
+ page << remote_to_href(:method => 'get', :with => "'#{parameters}'")
+ end
+ end
+
+ def remote_edit_icon
+ if !@todo.completed?
+ str = link_to( image_tag_for_edit(@todo),
+ edit_todo_path(@todo),
+ :class => "icon edit_icon")
+ set_behavior_for_edit_icon
+ else
+ str = '' + image_tag("blank.png") + " "
+ end
+ str
+ end
+
+ def set_behavior_for_toggle_checkbox
+ parameters = "_source_view=#{@source_view}"
+ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
+ apply_behavior '.item-container input.item-checkbox:click',
+ remote_function(:url => javascript_variable('this.value'), :method => 'put',
+ :with => "'#{parameters}'")
+ end
+
+ def remote_toggle_checkbox
+ str = check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox')
+ set_behavior_for_toggle_checkbox
+ str
+ end
+
+ def date_span
+ if @todo.completed?
+ "#{format_date( @todo.completed_at )}"
+ elsif @todo.deferred?
+ show_date( @todo.show_from )
+ else
+ due_date( @todo.due )
+ end
+ end
+
+ def tag_list_text
+ @todo.tags.collect{|t| t.name}.join(', ')
+ end
+
+ def tag_list
+ tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
+ tag_list = tags_except_starred.collect{|t| "" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + ""}.join('')
+ "#{tag_list}"
+ end
+
+ def tag_list_mobile
+ tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
+ # removed the link. TODO: add link to mobile view of tagged actions
+ tag_list = tags_except_starred.collect{|t|
+ "" +
+ link_to(t.name, {:action => "tag", :controller => "todos", :id => t.name+".m"}) +
+ ""}.join('')
+ if tag_list.empty? then "" else "#{tag_list}" end
+ end
+
+ def deferred_due_date
+ if @todo.deferred? && @todo.due
+ "(action due on #{format_date(@todo.due)})"
+ end
+ end
+
+ def project_and_context_links(parent_container_type, opts = {})
+ str = ''
+ if @todo.completed?
+ str += @todo.context.name unless opts[:suppress_context]
+ should_suppress_project = opts[:suppress_project] || @todo.project.nil?
+ str += ", " unless str.blank? || should_suppress_project
+ str += @todo.project.name unless should_suppress_project
+ str = "(#{str})" unless str.blank?
+ else
+ if (['project', 'tag', 'stats', 'search'].include?(parent_container_type))
+ str << item_link_to_context( @todo )
+ end
+ if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && @todo.project_id
+ str << item_link_to_project( @todo )
+ end
+ end
+ return str
+ end
+
+ # Uses the 'staleness_starts' value from settings.yml (in days) to colour the
+ # background of the action appropriately according to the age of the creation
+ # date:
+ # * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts
+ # * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts
+ # * l3: created more than 3 x staleness_starts
+ #
+ def staleness_class(item)
+ if item.due || item.completed?
+ return ""
+ elsif item.created_at < user_time - (prefs.staleness_starts * 3).days
+ return " stale_l3"
+ elsif item.created_at < user_time - (prefs.staleness_starts * 2).days
+ return " stale_l2"
+ elsif item.created_at < user_time - (prefs.staleness_starts).days
+ return " stale_l1"
+ else
+ return ""
+ end
+ end
+
+ # Check show_from date in comparison to today's date Flag up date
+ # appropriately with a 'traffic light' colour code
+ #
+ def show_date(d)
+ if d == nil
+ return ""
+ end
+
+ days = days_from_today(d)
+
+ case days
+ # overdue or due very soon! sound the alarm!
+ when -1000..-1
+ "Scheduled to show " + (days * -1).to_s + " days ago "
+ when 0
+ "Show Today "
+ when 1
+ "Show Tomorrow "
+ # due 2-7 days away
+ when 2..7
+ if prefs.due_style == Preference.due_styles[:due_on]
+ "Show on " + d.strftime("%A") + " "
+ else
+ "Show in " + days.to_s + " days "
+ end
+ # more than a week away - relax
+ else
+ "Show in " + days.to_s + " days "
+ end
+ end
+
+ def calendar_setup( input_field )
+ str = "Calendar.setup({ ifFormat:\"#{prefs.date_format}\""
+ str << ",firstDay:#{prefs.week_starts},showOthers:true,range:[2004, 2010]"
+ str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n"
+ javascript_tag str
+ end
+
+ def item_container_id (todo)
+ if source_view_is :project
+ return "p#{todo.project_id}" if todo.active?
+ return "tickler" if todo.deferred?
+ end
+ return "c#{todo.context_id}"
+ end
+
+ def should_show_new_item
+
+ if @todo.project.nil? == false
+ # do not show new actions that were added to hidden or completed projects
+ # on home page and context page
+ return false if source_view_is(:todo) && (@todo.project.hidden? || @todo.project.completed?)
+ return false if source_view_is(:context) && (@todo.project.hidden? || @todo.project.completed?)
+ end
+
+ return true if source_view_is(:deferred) && @todo.deferred?
+ return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden?
+ return true if source_view_is(:project) && @todo.deferred?
+ return true if !source_view_is(:deferred) && @todo.active?
+ return false
+ end
+
+ def parent_container_type
+ return 'tickler' if source_view_is :deferred
+ return 'project' if source_view_is :project
+ return 'stats' if source_view_is :stats
+ return 'context'
+ end
+
+ def empty_container_msg_div_id
+ return "tickler-empty-nd" if source_view_is(:project) && @todo.deferred?
+ return "p#{@todo.project_id}empty-nd" if source_view_is :project
+ return "c#{@todo.context_id}empty-nd"
+ end
+
+ def project_names_for_autocomplete
+ array_or_string_for_javascript( ['None'] + current_user.projects.active.collect{|p| escape_javascript(p.name) } )
+ end
+
+ def context_names_for_autocomplete
+ # #return array_or_string_for_javascript(['Create a new context']) if
+ # @contexts.empty?
+ array_or_string_for_javascript( current_user.contexts.collect{|c| escape_javascript(c.name) } )
+ end
+
+ def format_ical_notes(notes)
+ split_notes = notes.split(/\n/)
+ joined_notes = split_notes.join("\\n")
+ end
+
+ def formatted_pagination(total)
+ s = will_paginate(@todos)
+ (s.gsub(/(<\/[^<]+>)/, '\1 ')).chomp(' ')
+ end
+
+ def date_field_tag(name, id, value = nil, options = {})
+ text_field_tag name, value, {"size" => 12, "id" => id, "class" => "Date", "onfocus" => "Calendar.setup", "autocomplete" => "off"}.update(options.stringify_keys)
+ end
+
+ private
+
+ def image_tag_for_delete
+ image_tag("blank.png", :title =>"Delete action", :class=>"delete_item")
+ end
+
+ def image_tag_for_edit(todo)
+ image_tag("blank.png", :title =>"Edit action", :class=>"edit_item", :id=> dom_id(todo, 'edit_icon'))
+ end
+
+ def image_tag_for_star(todo)
+ class_str = todo.starred? ? "starred_todo" : "unstarred_todo"
+ image_tag("blank.png", :title =>"Star action", :class => class_str)
+ end
+
+ def defer_link(days)
+ link_to_remote image_tag("defer_#{days}.png", :alt => "Defer #{pluralize(days, 'day')}"), :url => {:controller => 'todos', :action => 'defer', :id => @todo.id, :days => days, :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")}
+ end
+
+end
diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb
index 7d24b7eb..f8f22526 100644
--- a/app/models/recurring_todo.rb
+++ b/app/models/recurring_todo.rb
@@ -362,14 +362,14 @@ class RecurringTodo < ActiveRecord::Base
end
def starred?
- tags.any? {|tag| tag.name == Todo::STARRED_TAG_NAME}
+ tags.any? {|tag| tag.name == Todo::STARRED_TAG_NAME }
end
def get_due_date(previous)
case self.target
when 'due_date'
return get_next_date(previous)
- when 'show_from'
+ when 'show_from_date'
# so leave due date empty
return nil
else
@@ -623,10 +623,10 @@ class RecurringTodo < ActiveRecord::Base
def toggle_star!
if starred?
- delete_tags Todo::STARRED_TAG_NAME
+ _remove_tags Todo::STARRED_TAG_NAME
tags.reload
else
- add_tag Todo::STARRED_TAG_NAME
+ _add_tags(Todo::STARRED_TAG_NAME)
tags.reload
end
starred?
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 81684c13..28f72662 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -1,11 +1,48 @@
-class Tag < ActiveRecord::Base
- has_many_polymorphs :taggables,
- :from => [:todos, :recurring_todos],
- :through => :taggings,
- :dependent => :destroy
- def on(taggable, user)
- tagging = taggings.create :taggable => taggable, :user => user
+# The Tag model. This model is automatically generated and added to your app if
+# you run the tagging generator included with has_many_polymorphs.
+
+class Tag < ActiveRecord::Base
+
+ DELIMITER = "," # Controls how to split and join tagnames from strings. You may need to change the validates_format_of parameters if you change this.
+ JOIN_DELIMITER = ", "
+
+ # If database speed becomes an issue, you could remove these validations and
+ # rescue the ActiveRecord database constraint errors instead.
+ validates_presence_of :name
+ validates_uniqueness_of :name, :case_sensitive => false
+
+ # Change this validation if you need more complex tag names.
+ # validates_format_of :name, :with => /^[a-zA-Z0-9\_\-]+$/, :message => "can not contain special characters"
+
+ # Set up the polymorphic relationship.
+ has_many_polymorphs :taggables,
+ :from => [:todos, :recurring_todos],
+ :through => :taggings,
+ :dependent => :destroy,
+ :skip_duplicates => false,
+ :parent_extend => proc {
+ # Defined on the taggable models, not on Tag itself. Return the tagnames
+ # associated with this record as a string.
+ def to_s
+ self.map(&:name).sort.join(Tag::JOIN_DELIMITER)
+ end
+ }
+
+ # Callback to strip extra spaces from the tagname before saving it. If you
+ # allow tags to be renamed later, you might want to use the
+ # before_save callback instead.
+ def before_create
+ self.name = name.downcase.strip.squeeze(" ")
end
-end
\ No newline at end of file
+ def on(taggable, user)
+ taggings.create :taggable => taggable, :user => user
+ end
+
+ # Tag::Error class. Raised by ActiveRecord::Base::TaggingExtensions if
+ # something goes wrong.
+ class Error < StandardError
+ end
+
+end
diff --git a/app/models/tagging.rb b/app/models/tagging.rb
index 16c1d5da..de6c3743 100644
--- a/app/models/tagging.rb
+++ b/app/models/tagging.rb
@@ -1,11 +1,17 @@
-class Tagging < ActiveRecord::Base
+
+# The Tagging join model. This model is automatically generated and added to your app if you run the tagging generator included with has_many_polymorphs.
+
+class Tagging < ActiveRecord::Base
+
belongs_to :tag
belongs_to :taggable, :polymorphic => true
- belongs_to :user
-
- # def before_destroy
- # # disallow orphaned tags
- # # TODO: this doesn't seem to be working
- # tag.destroy if tag.taggings.count < 2
- # end
+ # belongs_to :user
+
+ # If you also need to use acts_as_list, you will have to manage the tagging positions manually by creating decorated join records when you associate Tags with taggables.
+ # acts_as_list :scope => :taggable
+
+ # This callback makes sure that an orphaned Tag is deleted if it no longer tags anything.
+ def after_destroy
+ tag.destroy_without_callbacks if tag and tag.taggings.count == 0
+ end
end
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 5aed02be..97a1b984 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -1,176 +1,176 @@
-class Todo < ActiveRecord::Base
-
- belongs_to :context
- belongs_to :project
- belongs_to :user
- belongs_to :recurring_todo
-
- named_scope :active, :conditions => { :state => 'active' }
- named_scope :not_completed, :conditions => ['NOT state = ? ', 'completed']
- named_scope :are_due, :conditions => ['NOT todos.due IS NULL']
-
- STARRED_TAG_NAME = "starred"
-
- acts_as_state_machine :initial => :active, :column => 'state'
-
- # when entering active state, also remove completed_at date. Looks like :exit
- # of state completed is not run, see #679
- state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil }
- state :project_hidden
- state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
- state :deferred
-
- event :defer do
- transitions :to => :deferred, :from => [:active]
- end
-
- event :complete do
- transitions :to => :completed, :from => [:active, :project_hidden, :deferred]
- end
-
- event :activate do
- transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
- end
-
- event :hide do
- transitions :to => :project_hidden, :from => [:active, :deferred]
- end
-
- event :unhide do
- transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? }
- transitions :to => :active, :from => [:project_hidden]
- end
-
- attr_protected :user
-
- # Description field can't be empty, and must be < 100 bytes Notes must be <
- # 60,000 bytes (65,000 actually, but I'm being cautious)
- validates_presence_of :description
- validates_length_of :description, :maximum => 100
- validates_length_of :notes, :maximum => 60000, :allow_nil => true
- validates_presence_of :show_from, :if => :deferred?
- validates_presence_of :context
-
- def validate
- if !show_from.blank? && show_from < user.date
- errors.add("show_from", "must be a date in the future")
- end
- end
-
- def toggle_completion!
- saved = false
- if completed?
- saved = activate!
- else
- saved = complete!
- end
- return saved
- end
-
- def show_from
- self[:show_from]
- end
-
- def show_from=(date)
- # parse Date objects into the proper timezone
- date = user.at_midnight(date) if (date.is_a? Date)
- activate! if deferred? && date.blank?
- defer! if active? && !date.blank? && date > user.date
- self[:show_from] = date
- end
-
- alias_method :original_project, :project
-
- def project
- original_project.nil? ? Project.null_object : original_project
- end
-
- alias_method :original_set_initial_state, :set_initial_state
-
- def set_initial_state
- if show_from && (show_from > user.date)
- write_attribute self.class.state_column, 'deferred'
- else
- original_set_initial_state
- end
- end
-
- alias_method :original_run_initial_state_actions, :run_initial_state_actions
-
- def run_initial_state_actions
- # only run the initial state actions if the standard initial state hasn't
- # been changed
- if self.class.initial_state.to_sym == current_state
- original_run_initial_state_actions
- end
- end
-
- def self.feed_options(user)
- {
- :title => 'Tracks Actions',
- :description => "Actions for #{user.display_name}"
- }
- end
-
- def starred?
- tags.any? {|tag| tag.name == STARRED_TAG_NAME}
- end
-
- def toggle_star!
- if starred?
- delete_tags STARRED_TAG_NAME
- tags.reload
- else
- add_tag STARRED_TAG_NAME
- tags.reload
- end
- starred?
- end
-
- def from_recurring_todo?
- return self.recurring_todo_id != nil
- end
-
- # Rich Todo API
-
- def self.from_rich_message(user, default_context_id, description, notes)
- fields = description.match /([^>@]*)@?([^>]*)>?(.*)/
- description = fields[1].strip
- context = fields[2].strip
- project = fields[3].strip
-
- context = nil if context == ""
- project = nil if project == ""
-
- context_id = default_context_id
- unless(context.nil?)
- found_context = user.active_contexts.find_by_namepart(context)
- found_context = user.contexts.find_by_namepart(context) if found_context.nil?
- context_id = found_context.id unless found_context.nil?
- end
-
- unless user.contexts.exists? context_id
- raise(CannotAccessContext, "Cannot access a context that does not belong to this user.")
- end
-
- project_id = nil
- unless(project.blank?)
- if(project[0..3].downcase == "new:")
- found_project = user.projects.build
- found_project.name = project[4..255+4].strip
- found_project.save!
- else
- found_project = user.active_projects.find_by_namepart(project)
- found_project = user.projects.find_by_namepart(project) if found_project.nil?
- end
- project_id = found_project.id unless found_project.nil?
- end
-
- todo = user.todos.build
- todo.description = description
- todo.notes = notes
- todo.context_id = context_id
- todo.project_id = project_id unless project_id.nil?
- return todo
- end
-end
+class Todo < ActiveRecord::Base
+
+ belongs_to :context
+ belongs_to :project
+ belongs_to :user
+ belongs_to :recurring_todo
+
+ named_scope :active, :conditions => { :state => 'active' }
+ named_scope :not_completed, :conditions => ['NOT state = ? ', 'completed']
+ named_scope :are_due, :conditions => ['NOT todos.due IS NULL']
+
+ STARRED_TAG_NAME = "starred"
+
+ acts_as_state_machine :initial => :active, :column => 'state'
+
+ # when entering active state, also remove completed_at date. Looks like :exit
+ # of state completed is not run, see #679
+ state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil }
+ state :project_hidden
+ state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
+ state :deferred
+
+ event :defer do
+ transitions :to => :deferred, :from => [:active]
+ end
+
+ event :complete do
+ transitions :to => :completed, :from => [:active, :project_hidden, :deferred]
+ end
+
+ event :activate do
+ transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
+ end
+
+ event :hide do
+ transitions :to => :project_hidden, :from => [:active, :deferred]
+ end
+
+ event :unhide do
+ transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? }
+ transitions :to => :active, :from => [:project_hidden]
+ end
+
+ attr_protected :user
+
+ # Description field can't be empty, and must be < 100 bytes Notes must be <
+ # 60,000 bytes (65,000 actually, but I'm being cautious)
+ validates_presence_of :description
+ validates_length_of :description, :maximum => 100
+ validates_length_of :notes, :maximum => 60000, :allow_nil => true
+ validates_presence_of :show_from, :if => :deferred?
+ validates_presence_of :context
+
+ def validate
+ if !show_from.blank? && show_from < user.date
+ errors.add("show_from", "must be a date in the future")
+ end
+ end
+
+ def toggle_completion!
+ saved = false
+ if completed?
+ saved = activate!
+ else
+ saved = complete!
+ end
+ return saved
+ end
+
+ def show_from
+ self[:show_from]
+ end
+
+ def show_from=(date)
+ # parse Date objects into the proper timezone
+ date = user.at_midnight(date) if (date.is_a? Date)
+ activate! if deferred? && date.blank?
+ defer! if active? && !date.blank? && date > user.date
+ self[:show_from] = date
+ end
+
+ alias_method :original_project, :project
+
+ def project
+ original_project.nil? ? Project.null_object : original_project
+ end
+
+ alias_method :original_set_initial_state, :set_initial_state
+
+ def set_initial_state
+ if show_from && (show_from > user.date)
+ write_attribute self.class.state_column, 'deferred'
+ else
+ original_set_initial_state
+ end
+ end
+
+ alias_method :original_run_initial_state_actions, :run_initial_state_actions
+
+ def run_initial_state_actions
+ # only run the initial state actions if the standard initial state hasn't
+ # been changed
+ if self.class.initial_state.to_sym == current_state
+ original_run_initial_state_actions
+ end
+ end
+
+ def self.feed_options(user)
+ {
+ :title => 'Tracks Actions',
+ :description => "Actions for #{user.display_name}"
+ }
+ end
+
+ def starred?
+ tags.any? {|tag| tag.name == STARRED_TAG_NAME}
+ end
+
+ def toggle_star!
+ if starred?
+ _remove_tags STARRED_TAG_NAME
+ tags.reload
+ else
+ _add_tags(STARRED_TAG_NAME)
+ tags.reload
+ end
+ starred?
+ end
+
+ def from_recurring_todo?
+ return self.recurring_todo_id != nil
+ end
+
+ # Rich Todo API
+
+ def self.from_rich_message(user, default_context_id, description, notes)
+ fields = description.match(/([^>@]*)@?([^>]*)>?(.*)/)
+ description = fields[1].strip
+ context = fields[2].strip
+ project = fields[3].strip
+
+ context = nil if context == ""
+ project = nil if project == ""
+
+ context_id = default_context_id
+ unless(context.nil?)
+ found_context = user.active_contexts.find_by_namepart(context)
+ found_context = user.contexts.find_by_namepart(context) if found_context.nil?
+ context_id = found_context.id unless found_context.nil?
+ end
+
+ unless user.contexts.exists? context_id
+ raise(CannotAccessContext, "Cannot access a context that does not belong to this user.")
+ end
+
+ project_id = nil
+ unless(project.blank?)
+ if(project[0..3].downcase == "new:")
+ found_project = user.projects.build
+ found_project.name = project[4..255+4].strip
+ found_project.save!
+ else
+ found_project = user.active_projects.find_by_namepart(project)
+ found_project = user.projects.find_by_namepart(project) if found_project.nil?
+ end
+ project_id = found_project.id unless found_project.nil?
+ end
+
+ todo = user.todos.build
+ todo.description = description
+ todo.notes = notes
+ todo.context_id = context_id
+ todo.project_id = project_id unless project_id.nil?
+ return todo
+ end
+end
diff --git a/app/views/shared/_add_new_item_form.rhtml b/app/views/shared/_add_new_item_form.rhtml
index 3c3b6f8a..3e3f749f 100644
--- a/app/views/shared/_add_new_item_form.rhtml
+++ b/app/views/shared/_add_new_item_form.rhtml
@@ -24,7 +24,21 @@
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' },
:before => "$('todo_new_action_submit').startWaiting()",
:complete => "$('todo_new_action_submit').stopWaiting()",
- :condition => "!$('todo_new_action_submit').isWaiting()") do -%>
+ :condition => "!$('todo_new_action_submit').isWaiting() && askIfNewContextProvided()") do -%>
+
+
<%= error_messages_for("item", :object_name => 'action') %>
diff --git a/app/views/todos/_mobile_todo.rhtml b/app/views/todos/_mobile_todo.rhtml
index 88147afa..e2561b69 100644
--- a/app/views/todos/_mobile_todo.rhtml
+++ b/app/views/todos/_mobile_todo.rhtml
@@ -14,12 +14,12 @@ end -%>
<% end -%>
<%= date_span -%> <%= link_to mobile_todo.description, formatted_todo_path(mobile_todo, :m) -%>
<% if parent_container_type == 'context' or parent_container_type == 'tag' -%>
-<%= " (" +
+<%= " (" +
link_to(mobile_todo.project.name, formatted_project_path(mobile_todo.project, :m)) +
")" unless mobile_todo.project.nil? -%>
<% end
if parent_container_type == 'project' or parent_container_type == 'tag' -%>
-<%= " (" +
+<%= " (" +
link_to(mobile_todo.context.name, formatted_context_path(mobile_todo.context, :m)) +
")" -%>
<% end -%>
diff --git a/config/environment.rb.tmpl b/config/environment.rb.tmpl
index b9fbcf86..fa48939e 100644
--- a/config/environment.rb.tmpl
+++ b/config/environment.rb.tmpl
@@ -1,109 +1,111 @@
-# Be sure to restart your webserver when you modify this file.
-# Uncomment below to force Rails into production mode
-
-# (Use only when you can't set environment variables through your web/app server)
-# ENV['RAILS_ENV'] = 'production'
-
-# Bootstrap the Rails environment, frameworks, and default configuration
-require File.join(File.dirname(__FILE__), 'boot')
-
-# This is the 'salt' to add to the password before it is encrypted
-# You need to change this to something unique for yourself
-SALT = "change-me"
-
-class Rails::Configuration
- attr_accessor :action_web_service
-end
-
-# Leave this alone or set it to one or more of ['database', 'ldap', 'open_id'].
-# If you choose ldap, see the additional configuration options further down.
-AUTHENTICATION_SCHEMES = ['database']
-
-Rails::Initializer.run do |config|
- # Skip frameworks you're not going to use
- # config.frameworks -= [ :action_web_service, :action_mailer ]
- config.frameworks += [ :action_web_service]
- config.action_web_service = Rails::OrderedOptions.new
- config.load_paths += %W( #{RAILS_ROOT}/app/apis )
-
- config.action_controller.use_accept_header = true
-
- # Add additional load paths for your own custom dirs
- # config.load_paths += %W( #{RAILS_ROOT}/app/services )
-
- # Force all environments to use the same logger level
- # (by default production uses :info, the others :debug)
- # config.log_level = :debug
-
- # Use the database for sessions instead of the file system
- # (create the session table with 'rake create_sessions_table')
- config.action_controller.session_store = :active_record_store
-
- config.action_controller.session = {
- :session_key => '_tracks_session_id',
- :secret => SALT * (30.0 / SALT.length).ceil #must be at least 30 characters
- }
-
- # Enable page/fragment caching by setting a file-based store
- # (remember to create the caching directory and make it readable to the application)
- # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
-
- # Activate observers that should always be running
- # config.active_record.observers = :cacher, :garbage_collector
-
- # Make Active Record use UTC-base instead of local time
- config.active_record.default_timezone = :utc
-
- # You''ll probably want to change this to the time zone of the computer where Tracks is running
- # run rake time:zones:local have Rails suggest time zone names on your system
- config.time_zone = 'UTC'
-
- # Use Active Record's schema dumper instead of SQL when creating the test database
- # (enables use of different database adapters for development and test environments)
- config.active_record.schema_format = :ruby
-
- # See Rails::Configuration for more options
-end
-
-# Add new inflection rules using the following format
-# (all these examples are active by default):
-# Inflector.inflections do |inflect|
-# inflect.plural /^(ox)$/i, '\1en'
-# inflect.singular /^(ox)en/i, '\1'
-# inflect.irregular 'person', 'people'
-# inflect.uncountable %w( fish sheep )
-# end
-
-# Include your application configuration below
-
-
-require 'name_part_finder'
-require 'tracks/todo_list'
-require 'tracks/config'
-require 'activerecord_base_tag_extensions' # Needed for tagging-specific extensions
-require 'digest/sha1' #Needed to support 'rake db:fixtures:load' on some ruby installs: http://dev.rousette.org.uk/ticket/557
-require 'prototype_helper_extensions'
-
-if (AUTHENTICATION_SCHEMES.include? 'ldap')
- require 'net/ldap' #requires ruby-net-ldap gem be installed
- require 'simple_ldap_authenticator'
- SimpleLdapAuthenticator.ldap_library = 'net/ldap'
- SimpleLdapAuthenticator.servers = %w'localhost'
- SimpleLdapAuthenticator.use_ssl = false
- SimpleLdapAuthenticator.login_format = 'cn=%s,dc=example,dc=com'
-end
-if (AUTHENTICATION_SCHEMES.include? 'open_id')
- #requires ruby-openid gem to be installed
-end
-
-# setting this to true will make the cookies only available over HTTPS
-TRACKS_COOKIES_SECURE = false
-
-tracks_version='1.7RC'
-
-# comment out next two lines if you do not want (or can not) the date of the
-# last git commit in the footer
-# info=`git log --pretty=format:"%ai" -1`
-# tracks_version=tracks_version + ' ('+info+')'
-
-TRACKS_VERSION=tracks_version
+# Be sure to restart your webserver when you modify this file.
+# Uncomment below to force Rails into production mode
+
+# (Use only when you can't set environment variables through your web/app server)
+# ENV['RAILS_ENV'] = 'production'
+
+# Bootstrap the Rails environment, frameworks, and default configuration
+require File.join(File.dirname(__FILE__), 'boot')
+
+# This is the 'salt' to add to the password before it is encrypted
+# You need to change this to something unique for yourself
+SALT = "change-me"
+
+class Rails::Configuration
+ attr_accessor :action_web_service
+end
+
+# Leave this alone or set it to one or more of ['database', 'ldap', 'open_id'].
+# If you choose ldap, see the additional configuration options further down.
+AUTHENTICATION_SCHEMES = ['database']
+
+Rails::Initializer.run do |config|
+ # Skip frameworks you're not going to use
+ # config.frameworks -= [ :action_web_service, :action_mailer ]
+ config.frameworks += [ :action_web_service]
+ config.action_web_service = Rails::OrderedOptions.new
+ config.load_paths += %W( #{RAILS_ROOT}/app/apis )
+
+ config.gem "highline"
+
+ config.action_controller.use_accept_header = true
+
+ # Add additional load paths for your own custom dirs
+ # config.load_paths += %W( #{RAILS_ROOT}/app/services )
+
+ # Force all environments to use the same logger level
+ # (by default production uses :info, the others :debug)
+ # config.log_level = :debug
+
+ # Use the database for sessions instead of the file system
+ # (create the session table with 'rake create_sessions_table')
+ config.action_controller.session_store = :active_record_store
+
+ config.action_controller.session = {
+ :session_key => '_tracks_session_id',
+ :secret => SALT * (30.0 / SALT.length).ceil #must be at least 30 characters
+ }
+
+ # Enable page/fragment caching by setting a file-based store
+ # (remember to create the caching directory and make it readable to the application)
+ # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
+
+ # Activate observers that should always be running
+ # config.active_record.observers = :cacher, :garbage_collector
+
+ # Make Active Record use UTC-base instead of local time
+ config.active_record.default_timezone = :utc
+
+ # You''ll probably want to change this to the time zone of the computer where Tracks is running
+ # run rake time:zones:local have Rails suggest time zone names on your system
+ config.time_zone = 'UTC'
+
+ # Use Active Record's schema dumper instead of SQL when creating the test database
+ # (enables use of different database adapters for development and test environments)
+ config.active_record.schema_format = :ruby
+
+ # See Rails::Configuration for more options
+end
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
+
+# Include your application configuration below
+
+
+require 'name_part_finder'
+require 'tracks/todo_list'
+require 'tracks/config'
+require 'tagging_extensions' # Needed for tagging-specific extensions
+require 'digest/sha1' #Needed to support 'rake db:fixtures:load' on some ruby installs: http://dev.rousette.org.uk/ticket/557
+require 'prototype_helper_extensions'
+
+if (AUTHENTICATION_SCHEMES.include? 'ldap')
+ require 'net/ldap' #requires ruby-net-ldap gem be installed
+ require 'simple_ldap_authenticator'
+ SimpleLdapAuthenticator.ldap_library = 'net/ldap'
+ SimpleLdapAuthenticator.servers = %w'localhost'
+ SimpleLdapAuthenticator.use_ssl = false
+ SimpleLdapAuthenticator.login_format = 'cn=%s,dc=example,dc=com'
+end
+if (AUTHENTICATION_SCHEMES.include? 'open_id')
+ #requires ruby-openid gem to be installed
+end
+
+# setting this to true will make the cookies only available over HTTPS
+TRACKS_COOKIES_SECURE = false
+
+tracks_version='1.7RC'
+
+# comment out next two lines if you do not want (or can not) the date of the
+# last git commit in the footer
+# info=`git log --pretty=format:"%ai" -1`
+# tracks_version=tracks_version + ' ('+info+')'
+
+TRACKS_VERSION=tracks_version
diff --git a/lib/activerecord_base_tag_extensions.rb b/lib/activerecord_base_tag_extensions.rb
deleted file mode 100644
index 5361f73e..00000000
--- a/lib/activerecord_base_tag_extensions.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-class ActiveRecord::Base
- # These methods will work for any model instances
-
- # Tag with deletes all the current tags before adding the new ones
- # This makes the edit form more intiuitive:
- # Whatever is in the tags text field is what gets set as the tags for that action
- # If you submit an empty tags text field, all the tags are removed.
- def tag_with(tags, user)
- Tag.transaction do
- Tagging.delete_all("taggable_id = #{self.id} and taggable_type = '#{self.class}' and user_id = #{user.id}")
- tags.downcase.split(",").each do |tag|
- Tag.find_or_create_by_name(tag.strip).on(self, user)
- end
- end
- end
-
- def tag_list
- tags.map(&:name).join(', ')
- end
-
- def delete_tags tag_string
- split = tag_string.downcase.split(",")
- tags.delete tags.select{|t| split.include? t.name.strip}
- end
-
- def add_tag tag_name
- Tag.find_or_create_by_name(tag_name.strip).on(self,user)
- end
-
-end
\ No newline at end of file
diff --git a/lib/tagging_extensions.rb b/lib/tagging_extensions.rb
new file mode 100644
index 00000000..ab47bf1f
--- /dev/null
+++ b/lib/tagging_extensions.rb
@@ -0,0 +1,148 @@
+
+class ActiveRecord::Base #:nodoc:
+
+ # These extensions make models taggable. This file is automatically generated and required by your app if you run the tagging generator included with has_many_polymorphs.
+ module TaggingExtensions
+
+ # Add tags to self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
+ #
+ # We need to avoid name conflicts with the built-in ActiveRecord association methods, thus the underscores.
+ def _add_tags incoming
+ taggable?(true)
+ tag_cast_to_string(incoming).each do |tag_name|
+ begin
+ tag = Tag.find_or_create_by_name(tag_name)
+ raise Tag::Error, "tag could not be saved: #{tag_name}" if tag.new_record?
+ tag.taggables << self
+ rescue ActiveRecord::StatementInvalid => e
+ raise unless e.to_s =~ /duplicate/i
+ end
+ end
+ end
+
+ # Removes tags from self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
+ def _remove_tags outgoing
+ taggable?(true)
+ outgoing = tag_cast_to_string(outgoing)
+
+ tags.delete(*(tags.select do |tag|
+ outgoing.include? tag.name
+ end))
+ end
+
+ # Returns the tags on self as a string.
+ def tag_list
+ # Redefined later to avoid an RDoc parse error.
+ end
+
+ # Replace the existing tags on self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
+ def tag_with list
+ #:stopdoc:
+ taggable?(true)
+ list = tag_cast_to_string(list)
+
+ # Transactions may not be ideal for you here; be aware.
+ Tag.transaction do
+ current = tags.map(&:name)
+ _add_tags(list - current)
+ _remove_tags(current - list)
+ end
+
+ self
+ #:startdoc:
+ end
+
+ # Returns the tags on self as a string.
+ def tag_list #:nodoc:
+ #:stopdoc:
+ taggable?(true)
+ tags.reload
+ tags.to_s
+ #:startdoc:
+ end
+
+ private
+
+ def tag_cast_to_string obj #:nodoc:
+ case obj
+ when Array
+ obj.map! do |item|
+ case item
+ # removed next line: its prevents adding numbers as tags
+ # when /^\d+$/, Fixnum then Tag.find(item).name # This will be slow if you use ids a lot.
+ when Tag then item.name
+ when String then item
+ else
+ raise "Invalid type"
+ end
+ end
+ when String
+ obj = obj.split(Tag::DELIMITER).map do |tag_name|
+ tag_name.strip.squeeze(" ")
+ end
+ else
+ raise "Invalid object of class #{obj.class} as tagging method parameter"
+ end.flatten.compact.map(&:downcase).uniq
+ end
+
+ # Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model.
+ def taggable?(should_raise = false) #:nodoc:
+ unless flag = respond_to?(:tags)
+ raise "#{self.class} is not a taggable model" if should_raise
+ end
+ flag
+ end
+
+ end
+
+ module TaggingFinders
+ #
+ # Find all the objects tagged with the supplied list of tags
+ #
+ # Usage : Model.tagged_with("ruby")
+ # Model.tagged_with("hello", "world")
+ # Model.tagged_with("hello", "world", :limit => 10)
+ #
+ def tagged_with(*tag_list)
+ options = tag_list.last.is_a?(Hash) ? tag_list.pop : {}
+ tag_list = parse_tags(tag_list)
+
+ scope = scope(:find)
+ options[:select] ||= "#{table_name}.*"
+ options[:from] ||= "#{table_name}, tags, taggings"
+
+ sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
+ sql << "FROM #{(scope && scope[:from]) || options[:from]} "
+
+ add_joins!(sql, options, scope)
+
+ sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
+ sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "
+ sql << "AND taggings.tag_id = tags.id "
+
+ tag_list_condition = tag_list.map {|t| "'#{t}'"}.join(", ")
+
+ sql << "AND (tags.name IN (#{sanitize_sql(tag_list_condition)})) "
+ sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions]
+ sql << "GROUP BY #{table_name}.id "
+ sql << "HAVING COUNT(taggings.tag_id) = #{tag_list.size}"
+
+ add_order!(sql, options[:order], scope)
+ add_limit!(sql, options, scope)
+ add_lock!(sql, options, scope)
+
+ find_by_sql(sql)
+ end
+
+ def parse_tags(tags)
+ return [] if tags.blank?
+ tags = Array(tags).first
+ tags = tags.respond_to?(:flatten) ? tags.flatten : tags.split(Tag::DELIMITER)
+ tags.map { |tag| tag.strip.squeeze(" ") }.flatten.compact.map(&:downcase).uniq
+ end
+
+ end
+
+ include TaggingExtensions
+ extend TaggingFinders
+end
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
index fbaa1dc5..7b2ad866 100644
--- a/test/functional/projects_controller_test.rb
+++ b/test/functional/projects_controller_test.rb
@@ -1,259 +1,259 @@
-require File.dirname(__FILE__) + '/../test_helper'
-require File.dirname(__FILE__) + '/todo_container_controller_test_base'
-require 'projects_controller'
-
-# Re-raise errors caught by the controller.
-class ProjectsController; def rescue_action(e) raise e end; end
-
-class ProjectsControllerTest < TodoContainerControllerTestBase
- fixtures :users, :todos, :preferences, :projects, :contexts
-
- def setup
- perform_setup(Project, ProjectsController)
- end
-
- def test_projects_list
- login_as :admin_user
- get :index
- end
-
- def test_show_exposes_deferred_todos
- p = projects(:timemachine)
- login_as :admin_user
- get :show, :id => p.to_param
- assert_not_nil assigns['deferred']
- assert_equal 1, assigns['deferred'].size
-
- t = p.not_done_todos[0]
- t.show_from = 1.days.from_now.utc
- t.save!
-
- get :show, :id => p.to_param
- assert_equal 2, assigns['deferred'].size
- end
-
- def test_show_exposes_next_project_in_same_state
- login_as :admin_user
- get :show, :id => projects(:timemachine).to_param
- assert_equal(projects(:moremoney), assigns['next_project'])
- end
-
- def test_show_exposes_previous_project_in_same_state
- login_as :admin_user
- get :show, :id => projects(:moremoney).to_param
- assert_equal(projects(:timemachine), assigns['previous_project'])
- end
-
- def test_create_project_via_ajax_increments_number_of_projects
- assert_ajax_create_increments_count 'My New Project'
- end
-
- def test_create_project_with_ajax_success_rjs
- ajax_create 'My New Project'
- assert_rjs :insert_html, :bottom, "list-active-projects"
- assert_rjs :sortable, 'list-active-projects', { :tag => 'div', :handle => 'handle', :complete => visual_effect(:highlight, 'list-active-projects'), :url => order_projects_path }
- # not yet sure how to write the following properly...
- assert_rjs :call, "Form.reset", "project-form"
- assert_rjs :call, "Form.focusFirstElement", "project-form"
- end
-
- def test_create_project_and_go_to_project_page
- num_projects = Project.count
- xhr :post, :create, { :project => {:name => 'Immediate Project Planning Required'}, :go_to_project => 1}
- assert_js_redirected_to %r{/?projects/\d+}
- assert_equal num_projects + 1, Project.count
- end
-
- def test_create_with_comma_in_name_does_not_increment_number_of_projects
- assert_ajax_create_does_not_increment_count 'foo,bar'
- end
-
- def test_create_with_comma_in_name_fails_with_rjs
- ajax_create 'foo,bar'
- assert_rjs :show, 'status'
- # Not working with Rails 2.0 upgrade
- # assert_rjs :update, 'status', "1 error prohibited this record from being saved
There were problems with the following fields:
Name cannot contain the comma (',') character
"
- end
-
- def test_todo_state_is_project_hidden_after_hiding_project
- p = projects(:timemachine)
- todos = p.todos.find_in_state(:all, :active)
- login_as(:admin_user)
- xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"}
- todos.each do |t|
- assert_equal :project_hidden, t.reload().current_state
- end
- assert p.reload().hidden?
- end
-
- def test_not_done_counts_after_hiding_and_unhiding_project
- p = projects(:timemachine)
- todos = p.todos.find_in_state(:all, :active)
- login_as(:admin_user)
- xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"}
- xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"active"}
- todos.each do |t|
- assert_equal :active, t.reload().current_state
- end
- assert p.reload().active?
- end
-
- def test_rss_feed_content
- login_as(:admin_user)
- get :index, { :format => "rss" }
- assert_equal 'application/rss+xml', @response.content_type
- #puts @response.body
-
- assert_xml_select 'rss[version="2.0"]' do
- assert_select 'channel' do
- assert_select '>title', 'Tracks Projects'
- assert_select '>description', "Lists all the projects for #{users(:admin_user).display_name}"
- assert_select 'language', 'en-us'
- assert_select 'ttl', '40'
- end
- assert_select 'item', 3 do
- assert_select 'title', /.+/
- assert_select 'description' do
- assert_select_encoded do
- assert_select 'p', /^\d+ actions\. Project is (active|hidden|completed)\.$/
- end
- end
- %w(guid link).each do |node|
- assert_select node, /http:\/\/test.host\/projects\/.+/
- end
- assert_select 'pubDate', projects(:timemachine).updated_at.to_s(:rfc822)
- end
- end
- end
-
- def test_rss_feed_not_accessible_to_anonymous_user_without_token
- login_as nil
- get :index, { :format => "rss" }
- assert_response 401
- end
-
- def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token
- login_as nil
- get :index, { :format => "rss", :token => 'foo' }
- assert_response 401
- end
-
- def test_rss_feed_accessible_to_anonymous_user_with_valid_token
- login_as nil
- get :index, { :format => "rss", :token => users(:admin_user).token }
- assert_response :ok
- end
-
- def test_atom_feed_content
- login_as :admin_user
- get :index, { :format => "atom" }
- assert_equal 'application/atom+xml', @response.content_type
- #puts @response.body
-
- assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
- assert_select '>title', 'Tracks Projects'
- assert_select '>subtitle', "Lists all the projects for #{users(:admin_user).display_name}"
- assert_select 'entry', 3 do
- assert_select 'title', /.+/
- assert_select 'content[type="html"]' do
- assert_select_encoded do
- assert_select 'p', /\d+ actions. Project is (active|hidden|completed)./
- end
- end
- assert_select 'published', /(#{Regexp.escape(projects(:timemachine).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/
- end
- end
- end
-
- def test_atom_feed_not_accessible_to_anonymous_user_without_token
- login_as nil
- get :index, { :format => "atom" }
- assert_response 401
- end
-
- def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token
- login_as nil
- get :index, { :format => "atom", :token => 'foo' }
- assert_response 401
- end
-
- def test_atom_feed_accessible_to_anonymous_user_with_valid_token
- login_as nil
- get :index, { :format => "atom", :token => users(:admin_user).token }
- assert_response :ok
- end
-
- def test_text_feed_content
- login_as :admin_user
- get :index, { :format => "txt" }
- assert_equal 'text/plain', @response.content_type
- assert !(/ /.match(@response.body))
- #puts @response.body
- end
-
- def test_text_feed_content_for_projects_with_no_actions
- login_as :admin_user
- p = projects(:timemachine)
- p.todos.each { |t| t.destroy }
-
- get :index, { :format => "txt", :only_active_with_no_next_actions => true }
- assert (/^\s*BUILD A WORKING TIME MACHINE\s+0 actions. Project is active.\s*$/.match(@response.body))
- assert !(/[1-9] actions/.match(@response.body))
- end
-
- def test_text_feed_not_accessible_to_anonymous_user_without_token
- login_as nil
- get :index, { :format => "txt" }
- assert_response 401
- end
-
- def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token
- login_as nil
- get :index, { :format => "txt", :token => 'foo' }
- assert_response 401
- end
-
- def test_text_feed_accessible_to_anonymous_user_with_valid_token
- login_as nil
- get :index, { :format => "txt", :token => users(:admin_user).token }
- assert_response :ok
- end
-
- def test_actionize_sorts_active_projects_by_number_of_tasks
- login_as :admin_user
- u = users(:admin_user)
- post :actionize, :state => "active", :format => 'js'
- assert_equal 2, projects(:gardenclean).position
- assert_equal 1, projects(:moremoney).position
- assert_equal 3, projects(:timemachine).position
- end
-
- def test_alphabetize_sorts_active_projects_alphabetically
- login_as :admin_user
- u = users(:admin_user)
- post :alphabetize, :state => "active", :format => 'js'
- assert_equal 1, projects(:timemachine).position
- assert_equal 2, projects(:gardenclean).position
- assert_equal 3, projects(:moremoney).position
- end
-
- def test_alphabetize_assigns_state
- login_as :admin_user
- post :alphabetize, :state => "active", :format => 'js'
- assert_equal "active", assigns['state']
- end
-
- def test_alphabetize_assigns_projects
- login_as :admin_user
- post :alphabetize, :state => "active", :format => 'js'
- exposed_projects = assigns['projects']
- assert_equal 3, exposed_projects.length
- assert_equal projects(:timemachine), exposed_projects[0]
- assert_equal projects(:gardenclean), exposed_projects[1]
- assert_equal projects(:moremoney), exposed_projects[2]
- end
-
- def protect_against_forgery?
- false
- end
-end
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/todo_container_controller_test_base'
+require 'projects_controller'
+
+# Re-raise errors caught by the controller.
+class ProjectsController; def rescue_action(e) raise e end; end
+
+class ProjectsControllerTest < TodoContainerControllerTestBase
+ fixtures :users, :todos, :preferences, :projects, :contexts
+
+ def setup
+ perform_setup(Project, ProjectsController)
+ end
+
+ def test_projects_list
+ login_as :admin_user
+ get :index
+ end
+
+ def test_show_exposes_deferred_todos
+ p = projects(:timemachine)
+ login_as :admin_user
+ get :show, :id => p.to_param
+ assert_not_nil assigns['deferred']
+ assert_equal 1, assigns['deferred'].size
+
+ t = p.not_done_todos[0]
+ t.show_from = 1.days.from_now.utc
+ t.save!
+
+ get :show, :id => p.to_param
+ assert_equal 2, assigns['deferred'].size
+ end
+
+ def test_show_exposes_next_project_in_same_state
+ login_as :admin_user
+ get :show, :id => projects(:timemachine).to_param
+ assert_equal(projects(:moremoney), assigns['next_project'])
+ end
+
+ def test_show_exposes_previous_project_in_same_state
+ login_as :admin_user
+ get :show, :id => projects(:moremoney).to_param
+ assert_equal(projects(:timemachine), assigns['previous_project'])
+ end
+
+ def test_create_project_via_ajax_increments_number_of_projects
+ assert_ajax_create_increments_count 'My New Project'
+ end
+
+ def test_create_project_with_ajax_success_rjs
+ ajax_create 'My New Project'
+ assert_rjs :insert_html, :bottom, "list-active-projects"
+ assert_rjs :sortable, 'list-active-projects', { :tag => 'div', :handle => 'handle', :complete => visual_effect(:highlight, 'list-active-projects'), :url => order_projects_path }
+ # not yet sure how to write the following properly...
+ assert_rjs :call, "Form.reset", "project-form"
+ assert_rjs :call, "Form.focusFirstElement", "project-form"
+ end
+
+ def test_create_project_and_go_to_project_page
+ num_projects = Project.count
+ xhr :post, :create, { :project => {:name => 'Immediate Project Planning Required'}, :go_to_project => 1}
+ assert_js_redirected_to %r{/?projects/\d+}
+ assert_equal num_projects + 1, Project.count
+ end
+
+ def test_create_with_comma_in_name_does_not_increment_number_of_projects
+ assert_ajax_create_does_not_increment_count 'foo,bar'
+ end
+
+ def test_create_with_comma_in_name_fails_with_rjs
+ ajax_create 'foo,bar'
+ assert_rjs :show, 'status'
+ # Not working with Rails 2.0 upgrade
+ # assert_rjs :update, 'status', "1 error prohibited this record from being saved
There were problems with the following fields:
Name cannot contain the comma (',') character
"
+ end
+
+ def test_todo_state_is_project_hidden_after_hiding_project
+ p = projects(:timemachine)
+ todos = p.todos.find_in_state(:all, :active)
+ login_as(:admin_user)
+ xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"}
+ todos.each do |t|
+ assert_equal :project_hidden, t.reload().current_state
+ end
+ assert p.reload().hidden?
+ end
+
+ def test_not_done_counts_after_hiding_and_unhiding_project
+ p = projects(:timemachine)
+ todos = p.todos.find_in_state(:all, :active)
+ login_as(:admin_user)
+ xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"}
+ xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"active"}
+ todos.each do |t|
+ assert_equal :active, t.reload().current_state
+ end
+ assert p.reload().active?
+ end
+
+ def test_rss_feed_content
+ login_as(:admin_user)
+ get :index, { :format => "rss" }
+ assert_equal 'application/rss+xml', @response.content_type
+ #puts @response.body
+
+ assert_xml_select 'rss[version="2.0"]' do
+ assert_select 'channel' do
+ assert_select '>title', 'Tracks Projects'
+ assert_select '>description', "Lists all the projects for #{users(:admin_user).display_name}"
+ assert_select 'language', 'en-us'
+ assert_select 'ttl', '40'
+ end
+ assert_select 'item', 3 do
+ assert_select 'title', /.+/
+ assert_select 'description' do
+ assert_select_encoded do
+ assert_select 'p', /^\d+ actions\. Project is (active|hidden|completed)\.$/
+ end
+ end
+ %w(guid link).each do |node|
+ assert_select node, /http:\/\/test.host\/projects\/.+/
+ end
+ assert_select 'pubDate', projects(:timemachine).updated_at.to_s(:rfc822)
+ end
+ end
+ end
+
+ def test_rss_feed_not_accessible_to_anonymous_user_without_token
+ login_as nil
+ get :index, { :format => "rss" }
+ assert_response 401
+ end
+
+ def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token
+ login_as nil
+ get :index, { :format => "rss", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_rss_feed_accessible_to_anonymous_user_with_valid_token
+ login_as nil
+ get :index, { :format => "rss", :token => users(:admin_user).token }
+ assert_response :ok
+ end
+
+ def test_atom_feed_content
+ login_as :admin_user
+ get :index, { :format => "atom" }
+ assert_equal 'application/atom+xml', @response.content_type
+ #puts @response.body
+
+ assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
+ assert_select '>title', 'Tracks Projects'
+ assert_select '>subtitle', "Lists all the projects for #{users(:admin_user).display_name}"
+ assert_select 'entry', 3 do
+ assert_select 'title', /.+/
+ assert_select 'content[type="html"]' do
+ assert_select_encoded do
+ assert_select 'p', /\d+ actions. Project is (active|hidden|completed)./
+ end
+ end
+ assert_select 'published', /(#{Regexp.escape(projects(:timemachine).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/
+ end
+ end
+ end
+
+ def test_atom_feed_not_accessible_to_anonymous_user_without_token
+ login_as nil
+ get :index, { :format => "atom" }
+ assert_response 401
+ end
+
+ def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token
+ login_as nil
+ get :index, { :format => "atom", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_atom_feed_accessible_to_anonymous_user_with_valid_token
+ login_as nil
+ get :index, { :format => "atom", :token => users(:admin_user).token }
+ assert_response :ok
+ end
+
+ def test_text_feed_content
+ login_as :admin_user
+ get :index, { :format => "txt" }
+ assert_equal 'text/plain', @response.content_type
+ assert !(/ /.match(@response.body))
+ #puts @response.body
+ end
+
+ def test_text_feed_content_for_projects_with_no_actions
+ login_as :admin_user
+ p = projects(:timemachine)
+ p.todos.each { |t| t.destroy }
+
+ get :index, { :format => "txt", :only_active_with_no_next_actions => true }
+ assert (/^\s*BUILD A WORKING TIME MACHINE\s+0 actions. Project is active.\s*$/.match(@response.body))
+ assert !(/[1-9] actions/.match(@response.body))
+ end
+
+ def test_text_feed_not_accessible_to_anonymous_user_without_token
+ login_as nil
+ get :index, { :format => "txt" }
+ assert_response 401
+ end
+
+ def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token
+ login_as nil
+ get :index, { :format => "txt", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_text_feed_accessible_to_anonymous_user_with_valid_token
+ login_as nil
+ get :index, { :format => "txt", :token => users(:admin_user).token }
+ assert_response :ok
+ end
+
+ def test_actionize_sorts_active_projects_by_number_of_tasks
+ login_as :admin_user
+ u = users(:admin_user)
+ post :actionize, :state => "active", :format => 'js'
+ assert_equal 1, projects(:gardenclean).position
+ assert_equal 2, projects(:moremoney).position
+ assert_equal 3, projects(:timemachine).position
+ end
+
+ def test_alphabetize_sorts_active_projects_alphabetically
+ login_as :admin_user
+ u = users(:admin_user)
+ post :alphabetize, :state => "active", :format => 'js'
+ assert_equal 1, projects(:timemachine).position
+ assert_equal 2, projects(:gardenclean).position
+ assert_equal 3, projects(:moremoney).position
+ end
+
+ def test_alphabetize_assigns_state
+ login_as :admin_user
+ post :alphabetize, :state => "active", :format => 'js'
+ assert_equal "active", assigns['state']
+ end
+
+ def test_alphabetize_assigns_projects
+ login_as :admin_user
+ post :alphabetize, :state => "active", :format => 'js'
+ exposed_projects = assigns['projects']
+ assert_equal 3, exposed_projects.length
+ assert_equal projects(:timemachine), exposed_projects[0]
+ assert_equal projects(:gardenclean), exposed_projects[1]
+ assert_equal projects(:moremoney), exposed_projects[2]
+ end
+
+ def protect_against_forgery?
+ false
+ end
+end
diff --git a/test/functional/todos_controller_test.rb b/test/functional/todos_controller_test.rb
index 832aa764..624a702d 100644
--- a/test/functional/todos_controller_test.rb
+++ b/test/functional/todos_controller_test.rb
@@ -1,488 +1,488 @@
-require File.dirname(__FILE__) + '/../test_helper'
-require 'todos_controller'
-
-# Re-raise errors caught by the controller.
-class TodosController; def rescue_action(e) raise e end; end
-
-class TodosControllerTest < Test::Rails::TestCase
- fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings, :recurring_todos
-
- def setup
- @controller = TodosController.new
- @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
- end
-
- def test_get_index_when_not_logged_in
- get :index
- assert_redirected_to :controller => 'login', :action => 'login'
- end
-
- def test_not_done_counts
- login_as(:admin_user)
- get :index
- assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id]
- assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id]
- assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id]
- end
-
- def test_tag_is_retrieved_properly
- login_as(:admin_user)
- get :index
- t = assigns['not_done_todos'].find{|t| t.id == 2}
- assert_equal 1, t.tags.count
- assert_equal 'foo', t.tags[0].name
- assert !t.starred?
- end
-
- def test_not_done_counts_after_hiding_project
- p = Project.find(1)
- p.hide!
- p.save!
- login_as(:admin_user)
- get :index
- assert_equal nil, assigns['project_not_done_counts'][projects(:timemachine).id]
- assert_equal 2, assigns['context_not_done_counts'][contexts(:call).id]
- assert_equal nil, assigns['context_not_done_counts'][contexts(:lab).id]
- end
-
- def test_not_done_counts_after_hiding_and_unhiding_project
- p = Project.find(1)
- p.hide!
- p.save!
- p.activate!
- p.save!
- login_as(:admin_user)
- get :index
- assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id]
- assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id]
- assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id]
- end
-
- def test_deferred_count_for_project_source_view
- login_as(:admin_user)
- xhr :post, :toggle_check, :id => 5, :_source_view => 'project'
- assert_equal 1, assigns['deferred_count']
- xhr :post, :toggle_check, :id => 15, :_source_view => 'project'
- assert_equal 0, assigns['deferred_count']
- end
-
- def test_destroy_todo
- login_as(:admin_user)
- xhr :post, :destroy, :id => 1, :_source_view => 'todo'
- assert_rjs :page, "todo_1", :remove
- # #assert_rjs :replace_html, "badge-count", '9'
- end
-
- def test_create_todo
- assert_difference Todo, :count do
- login_as(:admin_user)
- put :create, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
- end
- end
-
- def test_create_todo_via_xml
- login_as(:admin_user)
- assert_difference Todo, :count do
- put :create, :format => "xml", "request" => { "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" }
- assert_response 201
- end
- end
-
- def test_fail_to_create_todo_via_xml
- login_as(:admin_user)
- # #try to create with no context, which is not valid
- put :create, :format => "xml", "request" => { "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" }
- assert_response 422
- assert_xml_select "errors" do
- assert_xml_select "error", "Context can't be blank"
- end
- end
-
- def test_create_deferred_todo
- original_todo_count = Todo.count
- login_as(:admin_user)
- put :create, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2026", 'show_from' => '30/10/2026'}, "tag_list"=>"foo bar"
- assert_equal original_todo_count + 1, Todo.count
- end
-
- def test_update_todo_project
- t = Todo.find(1)
- login_as(:admin_user)
- xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
- t = Todo.find(1)
- assert_equal 1, t.project_id
- end
-
- def test_update_todo_project_to_none
- t = Todo.find(1)
- login_as(:admin_user)
- xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"None", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
- t = Todo.find(1)
- assert_nil t.project_id
- end
-
- def test_update_todo_to_deferred_is_reflected_in_badge_count
- login_as(:admin_user)
- get :index
- assert_equal 11, assigns['count']
- xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Make more money than Billy Gates", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006", "show_from"=>"30/11/2030"}, "tag_list"=>"foo bar"
- assert_equal 10, assigns['down_count']
- end
-
- def test_update_todo
- t = Todo.find(1)
- login_as(:admin_user)
- xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo, bar"
- t = Todo.find(1)
- assert_equal "Call Warren Buffet to find out how much he makes per day", t.description
- assert_equal "foo, bar", t.tag_list
- expected = Date.new(2006,11,30)
- actual = t.due.to_date
- assert_equal expected, actual, "Expected #{expected.to_s(:db)}, was #{actual.to_s(:db)}"
- end
-
- def test_update_todos_with_blank_project_name
- t = Todo.find(1)
- login_as(:admin_user)
- xhr :post, :update, :id => 1, :_source_view => 'todo', :project_name => '', "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo, bar"
- t.reload
- assert t.project.nil?
- end
-
- def test_update_todo_tags_to_none
- t = Todo.find(1)
- login_as(:admin_user)
- xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>""
- t = Todo.find(1)
- assert_equal true, t.tag_list.empty?
- end
-
- def test_update_todo_tags_with_whitespace_and_dots
- t = Todo.find(1)
- login_as(:admin_user)
- taglist = " one , two,three ,four, 8.1.2, version1.5"
- xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>taglist
- t = Todo.find(1)
- assert_equal "one, two, three, four, 8.1.2, version1.5", t.tag_list
- end
-
- def test_find_tagged_with
- login_as(:admin_user)
- @user = User.find(@request.session['user_id'])
- tag = Tag.find_by_name('foo').todos
- @tagged = tag.find(:all, :conditions => ['taggings.user_id = ?', @user.id]).size
- get :tag, :name => 'foo'
- assert_response :success
- assert_equal 3, @tagged
- end
-
- def test_rss_feed
- login_as(:admin_user)
- get :index, { :format => "rss" }
- assert_equal 'application/rss+xml', @response.content_type
- # puts @response.body
-
- assert_xml_select 'rss[version="2.0"]' do
- assert_select 'channel' do
- assert_select '>title', 'Actions'
- assert_select '>description', "Actions for #{users(:admin_user).display_name}"
- assert_select 'language', 'en-us'
- assert_select 'ttl', '40'
- assert_select 'item', 11 do
- assert_select 'title', /.+/
- assert_select 'description', /.*/
- assert_select 'link', %r{http://test.host/contexts/.+}
- assert_select 'guid', %r{http://test.host/todos/.+}
- assert_select 'pubDate', todos(:book).updated_at.to_s(:rfc822)
- end
- end
- end
- end
-
- def test_rss_feed_with_limit
- login_as(:admin_user)
- get :index, { :format => "rss", :limit => '5' }
-
- assert_xml_select 'rss[version="2.0"]' do
- assert_select 'channel' do
- assert_select '>title', 'Actions'
- assert_select '>description', "Actions for #{users(:admin_user).display_name}"
- assert_select 'item', 5 do
- assert_select 'title', /.+/
- assert_select 'description', /.*/
- end
- end
- end
- end
-
- def test_rss_feed_not_accessible_to_anonymous_user_without_token
- login_as nil
- get :index, { :format => "rss" }
- assert_response 401
- end
-
- def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token
- login_as nil
- get :index, { :format => "rss", :token => 'foo' }
- assert_response 401
- end
-
- def test_rss_feed_accessible_to_anonymous_user_with_valid_token
- login_as nil
- get :index, { :format => "rss", :token => users(:admin_user).token }
- assert_response :ok
- end
-
- def test_atom_feed_content
- login_as :admin_user
- get :index, { :format => "atom" }
- assert_equal 'application/atom+xml', @response.content_type
- # #puts @response.body
-
- assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
- assert_xml_select '>title', 'Actions'
- assert_xml_select '>subtitle', "Actions for #{users(:admin_user).display_name}"
- assert_xml_select 'entry', 11 do
- assert_xml_select 'title', /.+/
- assert_xml_select 'content[type="html"]', /.*/
- assert_xml_select 'published', /(#{Regexp.escape(todos(:book).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/
- end
- end
- end
-
- def test_atom_feed_not_accessible_to_anonymous_user_without_token
- login_as nil
- get :index, { :format => "atom" }
- assert_response 401
- end
-
- def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token
- login_as nil
- get :index, { :format => "atom", :token => 'foo' }
- assert_response 401
- end
-
- def test_atom_feed_accessible_to_anonymous_user_with_valid_token
- login_as nil
- get :index, { :format => "atom", :token => users(:admin_user).token }
- assert_response :ok
- end
-
- def test_text_feed_content
- login_as(:admin_user)
- get :index, { :format => "txt" }
- assert_equal 'text/plain', @response.content_type
- assert !(/ /.match(@response.body))
- # #puts @response.body
- end
-
- def test_text_feed_not_accessible_to_anonymous_user_without_token
- login_as nil
- get :index, { :format => "txt" }
- assert_response 401
- end
-
- def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token
- login_as nil
- get :index, { :format => "txt", :token => 'foo' }
- assert_response 401
- end
-
- def test_text_feed_accessible_to_anonymous_user_with_valid_token
- login_as nil
- get :index, { :format => "txt", :token => users(:admin_user).token }
- assert_response :ok
- end
-
- def test_ical_feed_content
- login_as :admin_user
- get :index, { :format => "ics" }
- assert_equal 'text/calendar', @response.content_type
- assert !(/ /.match(@response.body))
- # #puts @response.body
- end
-
- def test_mobile_index_uses_text_html_content_type
- login_as(:admin_user)
- get :index, { :format => "m" }
- assert_equal 'text/html', @response.content_type
- end
-
- def test_mobile_index_assigns_down_count
- login_as(:admin_user)
- get :index, { :format => "m" }
- assert_equal 11, assigns['down_count']
- end
-
- def test_mobile_create_action_creates_a_new_todo
- login_as(:admin_user)
- post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
- "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
- "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
- "project_id"=>"1",
- "notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
- t = Todo.find_by_description("test_mobile_create_action")
- assert_not_nil t
- assert_equal 2, t.context_id
- assert_equal 1, t.project_id
- assert t.active?
- assert_equal 'test notes', t.notes
- assert_nil t.show_from
- assert_equal Date.new(2007,1,2), t.due.to_date
- end
-
- def test_mobile_create_action_redirects_to_mobile_home_page_when_successful
- login_as(:admin_user)
- post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
- "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
- "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
- "project_id"=>"1",
- "notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
- assert_redirected_to '/m'
- end
-
- def test_mobile_create_action_renders_new_template_when_save_fails
- login_as(:admin_user)
- post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
- "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
- "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
- "project_id"=>"1",
- "notes"=>"test notes", "state"=>"0"}, "tag_list"=>"test, test2"}
- assert_template 'todos/new'
- end
-
- def test_index_html_assigns_default_project_name_map
- login_as(:admin_user)
- get :index, {"format"=>"html"}
- assert_equal '"{\\"Build a working time machine\\": \\"lab\\"}"', assigns(:default_project_context_name_map)
- end
-
- def test_toggle_check_on_recurring_todo
- login_as(:admin_user)
-
- # link todo_1 and recurring_todo_1
- recurring_todo_1 = RecurringTodo.find(1)
- todo_1 = Todo.find_by_recurring_todo_id(1)
-
- # mark todo_1 as complete by toggle_check
- xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
- todo_1.reload
- assert todo_1.completed?
-
- # check that there is only one active todo belonging to recurring_todo
- count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
- assert_equal 1, count
-
- # check there is a new todo linked to the recurring pattern
- next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
- assert_equal "Call Bill Gates every day", next_todo.description
- # check that the new todo is not the same as todo_1
- assert_not_equal todo_1.id, next_todo.id
-
- # change recurrence pattern to monthly and set show_from 2 days before due
- # date this forces the next todo to be put in the tickler
- recurring_todo_1.show_from_delta = 2
- recurring_todo_1.recurring_period = 'monthly'
- recurring_todo_1.recurrence_selector = 0
- recurring_todo_1.every_other1 = 1
- recurring_todo_1.every_other2 = 2
- recurring_todo_1.every_other3 = 5
- recurring_todo_1.save
-
- # mark next_todo as complete by toggle_check
- xhr :post, :toggle_check, :id => next_todo.id, :_source_view => 'todo'
- next_todo.reload
- assert next_todo.completed?
-
- # check that there are three todos belonging to recurring_todo: two
- # completed and one deferred
- count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id})
- assert_equal 3, count
-
- # check there is a new todo linked to the recurring pattern in the tickler
- next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
- assert !next_todo.nil?
- assert_equal "Call Bill Gates every day", next_todo.description
- # check that the todo is in the tickler
- assert !next_todo.show_from.nil?
- end
-
- def test_toggle_check_on_rec_todo_show_from_today
- login_as(:admin_user)
-
- # link todo_1 and recurring_todo_1
- recurring_todo_1 = RecurringTodo.find(1)
- todo_1 = Todo.find_by_recurring_todo_id(1)
- today = Time.now.utc.at_midnight
-
- # change recurrence pattern to monthly and set show_from to today
- recurring_todo_1.target = 'show_from_date'
- recurring_todo_1.recurring_period = 'monthly'
- recurring_todo_1.recurrence_selector = 0
- recurring_todo_1.every_other1 = today.day
- recurring_todo_1.every_other2 = 1
- recurring_todo_1.save
-
- # mark todo_1 as complete by toggle_check, this gets rid of todo_1 that was
- # not correctly created from the adjusted recurring pattern we defined
- # above.
- xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
- todo_1.reload
- assert todo_1.completed?
-
- # locate the new todo. This todo is created from the adjusted recurring
- # pattern defined in this test
- new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
- assert !new_todo.nil?
-
- # mark new_todo as complete by toggle_check
- xhr :post, :toggle_check, :id => new_todo.id, :_source_view => 'todo'
- new_todo.reload
- assert todo_1.completed?
-
- # locate the new todo in tickler
- new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
- assert !new_todo.nil?
-
- assert_equal "Call Bill Gates every day", new_todo.description
- # check that the new todo is not the same as todo_1
- assert_not_equal todo_1.id, new_todo.id
-
- # check that the new_todo is in the tickler to show next month
- assert !new_todo.show_from.nil?
- assert_equal Time.utc(today.year, today.month, today.day)+1.month, new_todo.show_from
- end
-
- def test_check_for_next_todo
- login_as :admin_user
-
- recurring_todo_1 = RecurringTodo.find(5)
- @todo = Todo.find_by_recurring_todo_id(1)
- assert @todo.from_recurring_todo?
- # rewire @todo to yearly recurring todo
- @todo.recurring_todo_id = 5
-
- # make todo due tomorrow and change recurring date also to tomorrow
- @todo.due = Time.zone.now + 1.day
- @todo.save
- recurring_todo_1.every_other1 = @todo.due.day
- recurring_todo_1.every_other2 = @todo.due.month
- recurring_todo_1.save
-
- # mark todo complete
- xhr :post, :toggle_check, :id => @todo.id, :_source_view => 'todo'
- @todo.reload
- assert @todo.completed?
-
- # check that there is no active todo
- next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
- assert next_todo.nil?
-
- # check for new deferred todo
- next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
- assert !next_todo.nil?
- # check that the due date of the new todo is later than tomorrow
- assert next_todo.due > @todo.due
- end
-
-end
+require File.dirname(__FILE__) + '/../test_helper'
+require 'todos_controller'
+
+# Re-raise errors caught by the controller.
+class TodosController; def rescue_action(e) raise e end; end
+
+class TodosControllerTest < Test::Rails::TestCase
+ fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings, :recurring_todos
+
+ def setup
+ @controller = TodosController.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ end
+
+ def test_get_index_when_not_logged_in
+ get :index
+ assert_redirected_to :controller => 'login', :action => 'login'
+ end
+
+ def test_not_done_counts
+ login_as(:admin_user)
+ get :index
+ assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id]
+ assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id]
+ assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id]
+ end
+
+ def test_tag_is_retrieved_properly
+ login_as(:admin_user)
+ get :index
+ t = assigns['not_done_todos'].find{|t| t.id == 2}
+ assert_equal 1, t.tags.count
+ assert_equal 'foo', t.tags[0].name
+ assert !t.starred?
+ end
+
+ def test_not_done_counts_after_hiding_project
+ p = Project.find(1)
+ p.hide!
+ p.save!
+ login_as(:admin_user)
+ get :index
+ assert_equal nil, assigns['project_not_done_counts'][projects(:timemachine).id]
+ assert_equal 2, assigns['context_not_done_counts'][contexts(:call).id]
+ assert_equal nil, assigns['context_not_done_counts'][contexts(:lab).id]
+ end
+
+ def test_not_done_counts_after_hiding_and_unhiding_project
+ p = Project.find(1)
+ p.hide!
+ p.save!
+ p.activate!
+ p.save!
+ login_as(:admin_user)
+ get :index
+ assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id]
+ assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id]
+ assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id]
+ end
+
+ def test_deferred_count_for_project_source_view
+ login_as(:admin_user)
+ xhr :post, :toggle_check, :id => 5, :_source_view => 'project'
+ assert_equal 1, assigns['deferred_count']
+ xhr :post, :toggle_check, :id => 15, :_source_view => 'project'
+ assert_equal 0, assigns['deferred_count']
+ end
+
+ def test_destroy_todo
+ login_as(:admin_user)
+ xhr :post, :destroy, :id => 1, :_source_view => 'todo'
+ assert_rjs :page, "todo_1", :remove
+ # #assert_rjs :replace_html, "badge-count", '9'
+ end
+
+ def test_create_todo
+ assert_difference Todo, :count do
+ login_as(:admin_user)
+ put :create, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
+ end
+ end
+
+ def test_create_todo_via_xml
+ login_as(:admin_user)
+ assert_difference Todo, :count do
+ put :create, :format => "xml", "request" => { "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" }
+ assert_response 201
+ end
+ end
+
+ def test_fail_to_create_todo_via_xml
+ login_as(:admin_user)
+ # #try to create with no context, which is not valid
+ put :create, :format => "xml", "request" => { "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" }
+ assert_response 422
+ assert_xml_select "errors" do
+ assert_xml_select "error", "Context can't be blank"
+ end
+ end
+
+ def test_create_deferred_todo
+ original_todo_count = Todo.count
+ login_as(:admin_user)
+ put :create, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2026", 'show_from' => '30/10/2026'}, "tag_list"=>"foo bar"
+ assert_equal original_todo_count + 1, Todo.count
+ end
+
+ def test_update_todo_project
+ t = Todo.find(1)
+ login_as(:admin_user)
+ xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
+ t = Todo.find(1)
+ assert_equal 1, t.project_id
+ end
+
+ def test_update_todo_project_to_none
+ t = Todo.find(1)
+ login_as(:admin_user)
+ xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"None", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
+ t = Todo.find(1)
+ assert_nil t.project_id
+ end
+
+ def test_update_todo_to_deferred_is_reflected_in_badge_count
+ login_as(:admin_user)
+ get :index
+ assert_equal 11, assigns['count']
+ xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Make more money than Billy Gates", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006", "show_from"=>"30/11/2030"}, "tag_list"=>"foo bar"
+ assert_equal 10, assigns['down_count']
+ end
+
+ def test_update_todo
+ t = Todo.find(1)
+ login_as(:admin_user)
+ xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo, bar"
+ t = Todo.find(1)
+ assert_equal "Call Warren Buffet to find out how much he makes per day", t.description
+ assert_equal "bar, foo", t.tag_list
+ expected = Date.new(2006,11,30)
+ actual = t.due.to_date
+ assert_equal expected, actual, "Expected #{expected.to_s(:db)}, was #{actual.to_s(:db)}"
+ end
+
+ def test_update_todos_with_blank_project_name
+ t = Todo.find(1)
+ login_as(:admin_user)
+ xhr :post, :update, :id => 1, :_source_view => 'todo', :project_name => '', "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo, bar"
+ t.reload
+ assert t.project.nil?
+ end
+
+ def test_update_todo_tags_to_none
+ t = Todo.find(1)
+ login_as(:admin_user)
+ xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>""
+ t = Todo.find(1)
+ assert_equal true, t.tag_list.empty?
+ end
+
+ def test_update_todo_tags_with_whitespace_and_dots
+ t = Todo.find(1)
+ login_as(:admin_user)
+ taglist = " one , two,three ,four, 8.1.2, version1.5"
+ xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>taglist
+ t = Todo.find(1)
+ assert_equal "8.1.2, four, one, three, two, version1.5", t.tag_list
+ end
+
+ def test_find_tagged_with
+ login_as(:admin_user)
+ @user = User.find(@request.session['user_id'])
+ tag = Tag.find_by_name('foo').todos
+ @tagged = tag.find(:all, :conditions => ['taggings.user_id = ?', @user.id]).size
+ get :tag, :name => 'foo'
+ assert_response :success
+ assert_equal 3, @tagged
+ end
+
+ def test_rss_feed
+ login_as(:admin_user)
+ get :index, { :format => "rss" }
+ assert_equal 'application/rss+xml', @response.content_type
+ # puts @response.body
+
+ assert_xml_select 'rss[version="2.0"]' do
+ assert_select 'channel' do
+ assert_select '>title', 'Actions'
+ assert_select '>description', "Actions for #{users(:admin_user).display_name}"
+ assert_select 'language', 'en-us'
+ assert_select 'ttl', '40'
+ assert_select 'item', 11 do
+ assert_select 'title', /.+/
+ assert_select 'description', /.*/
+ assert_select 'link', %r{http://test.host/contexts/.+}
+ assert_select 'guid', %r{http://test.host/todos/.+}
+ assert_select 'pubDate', todos(:book).updated_at.to_s(:rfc822)
+ end
+ end
+ end
+ end
+
+ def test_rss_feed_with_limit
+ login_as(:admin_user)
+ get :index, { :format => "rss", :limit => '5' }
+
+ assert_xml_select 'rss[version="2.0"]' do
+ assert_select 'channel' do
+ assert_select '>title', 'Actions'
+ assert_select '>description', "Actions for #{users(:admin_user).display_name}"
+ assert_select 'item', 5 do
+ assert_select 'title', /.+/
+ assert_select 'description', /.*/
+ end
+ end
+ end
+ end
+
+ def test_rss_feed_not_accessible_to_anonymous_user_without_token
+ login_as nil
+ get :index, { :format => "rss" }
+ assert_response 401
+ end
+
+ def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token
+ login_as nil
+ get :index, { :format => "rss", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_rss_feed_accessible_to_anonymous_user_with_valid_token
+ login_as nil
+ get :index, { :format => "rss", :token => users(:admin_user).token }
+ assert_response :ok
+ end
+
+ def test_atom_feed_content
+ login_as :admin_user
+ get :index, { :format => "atom" }
+ assert_equal 'application/atom+xml', @response.content_type
+ # #puts @response.body
+
+ assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
+ assert_xml_select '>title', 'Actions'
+ assert_xml_select '>subtitle', "Actions for #{users(:admin_user).display_name}"
+ assert_xml_select 'entry', 11 do
+ assert_xml_select 'title', /.+/
+ assert_xml_select 'content[type="html"]', /.*/
+ assert_xml_select 'published', /(#{Regexp.escape(todos(:book).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/
+ end
+ end
+ end
+
+ def test_atom_feed_not_accessible_to_anonymous_user_without_token
+ login_as nil
+ get :index, { :format => "atom" }
+ assert_response 401
+ end
+
+ def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token
+ login_as nil
+ get :index, { :format => "atom", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_atom_feed_accessible_to_anonymous_user_with_valid_token
+ login_as nil
+ get :index, { :format => "atom", :token => users(:admin_user).token }
+ assert_response :ok
+ end
+
+ def test_text_feed_content
+ login_as(:admin_user)
+ get :index, { :format => "txt" }
+ assert_equal 'text/plain', @response.content_type
+ assert !(/ /.match(@response.body))
+ # #puts @response.body
+ end
+
+ def test_text_feed_not_accessible_to_anonymous_user_without_token
+ login_as nil
+ get :index, { :format => "txt" }
+ assert_response 401
+ end
+
+ def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token
+ login_as nil
+ get :index, { :format => "txt", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_text_feed_accessible_to_anonymous_user_with_valid_token
+ login_as nil
+ get :index, { :format => "txt", :token => users(:admin_user).token }
+ assert_response :ok
+ end
+
+ def test_ical_feed_content
+ login_as :admin_user
+ get :index, { :format => "ics" }
+ assert_equal 'text/calendar', @response.content_type
+ assert !(/ /.match(@response.body))
+ # #puts @response.body
+ end
+
+ def test_mobile_index_uses_text_html_content_type
+ login_as(:admin_user)
+ get :index, { :format => "m" }
+ assert_equal 'text/html', @response.content_type
+ end
+
+ def test_mobile_index_assigns_down_count
+ login_as(:admin_user)
+ get :index, { :format => "m" }
+ assert_equal 11, assigns['down_count']
+ end
+
+ def test_mobile_create_action_creates_a_new_todo
+ login_as(:admin_user)
+ post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
+ "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
+ "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
+ "project_id"=>"1",
+ "notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
+ t = Todo.find_by_description("test_mobile_create_action")
+ assert_not_nil t
+ assert_equal 2, t.context_id
+ assert_equal 1, t.project_id
+ assert t.active?
+ assert_equal 'test notes', t.notes
+ assert_nil t.show_from
+ assert_equal Date.new(2007,1,2), t.due.to_date
+ end
+
+ def test_mobile_create_action_redirects_to_mobile_home_page_when_successful
+ login_as(:admin_user)
+ post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
+ "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
+ "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
+ "project_id"=>"1",
+ "notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
+ assert_redirected_to '/m'
+ end
+
+ def test_mobile_create_action_renders_new_template_when_save_fails
+ login_as(:admin_user)
+ post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
+ "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
+ "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
+ "project_id"=>"1",
+ "notes"=>"test notes", "state"=>"0"}, "tag_list"=>"test, test2"}
+ assert_template 'todos/new'
+ end
+
+ def test_index_html_assigns_default_project_name_map
+ login_as(:admin_user)
+ get :index, {"format"=>"html"}
+ assert_equal '"{\\"Build a working time machine\\": \\"lab\\"}"', assigns(:default_project_context_name_map)
+ end
+
+ def test_toggle_check_on_recurring_todo
+ login_as(:admin_user)
+
+ # link todo_1 and recurring_todo_1
+ recurring_todo_1 = RecurringTodo.find(1)
+ todo_1 = Todo.find_by_recurring_todo_id(1)
+
+ # mark todo_1 as complete by toggle_check
+ xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
+ todo_1.reload
+ assert todo_1.completed?
+
+ # check that there is only one active todo belonging to recurring_todo
+ count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
+ assert_equal 1, count
+
+ # check there is a new todo linked to the recurring pattern
+ next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
+ assert_equal "Call Bill Gates every day", next_todo.description
+ # check that the new todo is not the same as todo_1
+ assert_not_equal todo_1.id, next_todo.id
+
+ # change recurrence pattern to monthly and set show_from 2 days before due
+ # date this forces the next todo to be put in the tickler
+ recurring_todo_1.show_from_delta = 2
+ recurring_todo_1.recurring_period = 'monthly'
+ recurring_todo_1.recurrence_selector = 0
+ recurring_todo_1.every_other1 = 1
+ recurring_todo_1.every_other2 = 2
+ recurring_todo_1.every_other3 = 5
+ recurring_todo_1.save
+
+ # mark next_todo as complete by toggle_check
+ xhr :post, :toggle_check, :id => next_todo.id, :_source_view => 'todo'
+ next_todo.reload
+ assert next_todo.completed?
+
+ # check that there are three todos belonging to recurring_todo: two
+ # completed and one deferred
+ count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id})
+ assert_equal 3, count
+
+ # check there is a new todo linked to the recurring pattern in the tickler
+ next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
+ assert !next_todo.nil?
+ assert_equal "Call Bill Gates every day", next_todo.description
+ # check that the todo is in the tickler
+ assert !next_todo.show_from.nil?
+ end
+
+ def test_toggle_check_on_rec_todo_show_from_today
+ login_as(:admin_user)
+
+ # link todo_1 and recurring_todo_1
+ recurring_todo_1 = RecurringTodo.find(1)
+ todo_1 = Todo.find_by_recurring_todo_id(1)
+ today = Time.now.utc.at_midnight
+
+ # change recurrence pattern to monthly and set show_from to today
+ recurring_todo_1.target = 'show_from_date'
+ recurring_todo_1.recurring_period = 'monthly'
+ recurring_todo_1.recurrence_selector = 0
+ recurring_todo_1.every_other1 = today.day
+ recurring_todo_1.every_other2 = 1
+ recurring_todo_1.save
+
+ # mark todo_1 as complete by toggle_check, this gets rid of todo_1 that was
+ # not correctly created from the adjusted recurring pattern we defined
+ # above.
+ xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
+ todo_1.reload
+ assert todo_1.completed?
+
+ # locate the new todo. This todo is created from the adjusted recurring
+ # pattern defined in this test
+ new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
+ assert !new_todo.nil?
+
+ # mark new_todo as complete by toggle_check
+ xhr :post, :toggle_check, :id => new_todo.id, :_source_view => 'todo'
+ new_todo.reload
+ assert todo_1.completed?
+
+ # locate the new todo in tickler
+ new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
+ assert !new_todo.nil?
+
+ assert_equal "Call Bill Gates every day", new_todo.description
+ # check that the new todo is not the same as todo_1
+ assert_not_equal todo_1.id, new_todo.id
+
+ # check that the new_todo is in the tickler to show next month
+ assert !new_todo.show_from.nil?
+ assert_equal Time.utc(today.year, today.month, today.day)+1.month, new_todo.show_from
+ end
+
+ def test_check_for_next_todo
+ login_as :admin_user
+
+ recurring_todo_1 = RecurringTodo.find(5)
+ @todo = Todo.find_by_recurring_todo_id(1)
+ assert @todo.from_recurring_todo?
+ # rewire @todo to yearly recurring todo
+ @todo.recurring_todo_id = 5
+
+ # make todo due tomorrow and change recurring date also to tomorrow
+ @todo.due = Time.zone.now + 1.day
+ @todo.save
+ recurring_todo_1.every_other1 = @todo.due.day
+ recurring_todo_1.every_other2 = @todo.due.month
+ recurring_todo_1.save
+
+ # mark todo complete
+ xhr :post, :toggle_check, :id => @todo.id, :_source_view => 'todo'
+ @todo.reload
+ assert @todo.completed?
+
+ # check that there is no active todo
+ next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
+ assert next_todo.nil?
+
+ # check for new deferred todo
+ next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
+ assert !next_todo.nil?
+ # check that the due date of the new todo is later than tomorrow
+ assert next_todo.due > @todo.due
+ end
+
+end
diff --git a/test/selenium/mobile/create_new_action.rsel b/test/selenium/mobile/create_new_action.rsel
index 8756d3ea..93ec8505 100644
--- a/test/selenium/mobile/create_new_action.rsel
+++ b/test/selenium/mobile/create_new_action.rsel
@@ -1,18 +1,18 @@
-setup :fixtures => :all
-login :as => 'admin'
-
-open '/m'
-wait_for_text 'css=h1 span.count', '11'
-
-click_and_wait "link=0-Add new action"
-
-type "todo_notes", "test notes"
-type "todo_description", "test name"
-select "todo_context_id", "label=call"
-select "todo_project_id", "label=Make more money than Billy Gates"
-select "todo_due_3i", "label=1"
-select "todo_due_2i", "label=January"
-select "todo_due_1i", "label=2009"
-click_and_wait "//input[@value='Create']"
-
-wait_for_text 'css=h1 span.count', '12'
+setup :fixtures => :all
+login :as => 'admin'
+
+open '/m'
+wait_for_text 'css=h1 span.count', '11'
+
+click_and_wait "link=0-New action"
+
+type "todo_notes", "test notes"
+type "todo_description", "test name"
+select "todo_context_id", "label=call"
+select "todo_project_id", "label=Make more money than Billy Gates"
+select "todo_due_3i", "label=1"
+select "todo_due_2i", "label=January"
+select "todo_due_1i", "label=2009"
+click_and_wait "//input[@value='Create']"
+
+wait_for_text 'css=h1 span.count', '12'
diff --git a/test/unit/recurring_todo_test.rb b/test/unit/recurring_todo_test.rb
index 80106554..e7f5ef25 100644
--- a/test/unit/recurring_todo_test.rb
+++ b/test/unit/recurring_todo_test.rb
@@ -1,283 +1,283 @@
-require File.dirname(__FILE__) + '/../test_helper'
-
-class RecurringTodoTest < Test::Rails::TestCase
- fixtures :todos, :users, :contexts, :preferences, :tags, :taggings, :recurring_todos
-
- def setup
- @every_day = RecurringTodo.find(1).reload
- @every_workday = RecurringTodo.find(2).reload
- @weekly_every_day = RecurringTodo.find(3).reload
- @monthly_every_last_friday = RecurringTodo.find(4).reload
- @yearly = RecurringTodo.find(5).reload
-
- @today = Time.now.utc
- @tomorrow = @today + 1.day
- @in_three_days = Time.now.utc + 3.days
- @in_four_days = @in_three_days + 1.day # need a day after start_from
-
- @friday = Time.zone.local(2008,6,6)
- @saturday = Time.zone.local(2008,6,7)
- @sunday = Time.zone.local(2008,6,8) # june 8, 2008 was a sunday
- @monday = Time.zone.local(2008,6,9)
- @tuesday = Time.zone.local(2008,6,10)
- @wednesday = Time.zone.local(2008,6,11)
- @thursday = Time.zone.local(2008,6,12)
- end
-
- def test_pattern_text
- assert_equal "every day", @every_day.recurrence_pattern
- assert_equal "on work days", @every_workday.recurrence_pattern
- assert_equal "every last Friday of every 2 months", @monthly_every_last_friday.recurrence_pattern
- assert_equal "every year on June 8", @yearly.recurrence_pattern
- end
-
- def test_daily_every_day
- # every_day should return todays date if there was no previous date
- due_date = @every_day.get_due_date(nil)
- # use strftime in compare, because milisec / secs could be different
- assert_equal @today.strftime("%d-%m-%y"), due_date.strftime("%d-%m-%y")
-
- # when the last todo was completed today, the next todo is due tomorrow
- due_date =@every_day.get_due_date(@today)
- assert_equal @tomorrow, due_date
-
- # do something every 14 days
- @every_day.every_other1=14
- due_date = @every_day.get_due_date(@today)
- assert_equal @today+14.days, due_date
- end
-
- def test_daily_work_days
- assert_equal @monday, @every_workday.get_due_date(@friday)
- assert_equal @monday, @every_workday.get_due_date(@saturday)
- assert_equal @monday, @every_workday.get_due_date(@sunday)
- assert_equal @tuesday, @every_workday.get_due_date(@monday)
- end
-
- def test_show_from_date
- # assume that target due_date works fine, i.e. don't do the same tests over
-
- @every_day.target='show_from_date'
- # when recurrence is targeted on show_from, due date shoult remain nil
- assert_equal nil, @every_day.get_due_date(nil)
- assert_equal nil, @every_day.get_due_date(@today-3.days)
-
- # check show from get the next day
- assert_equal @today, @every_day.get_show_from_date(@today-1.days)
- assert_equal @today+1.day, @every_day.get_show_from_date(@today)
-
- @every_day.target='due_date'
- # when target on due_date, show_from is relative to due date unless delta=0
- assert_equal nil, @every_day.get_show_from_date(@today-1.days)
-
- @every_day.show_from_delta=10
- assert_equal @today, @every_day.get_show_from_date(@today+9.days) #today+1+9-10
-
- # TODO: show_from has no use case for daily pattern. Need to test on
- # weekly/monthly/yearly
- end
-
- def test_end_date_on_recurring_todo
- assert_equal true, @every_day.has_next_todo(@in_three_days)
- assert_equal true, @every_day.has_next_todo(@in_four_days)
- @every_day.end_date = @in_four_days
- assert_equal false, @every_day.has_next_todo(@in_four_days)
- end
-
- def test_weekly_every_day_setters
- @weekly_every_day.every_day = ' '
-
- @weekly_every_day.weekly_return_sunday=('s')
- assert_equal 's ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_monday=('m')
- assert_equal 'sm ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_tuesday=('t')
- assert_equal 'smt ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_wednesday=('w')
- assert_equal 'smtw ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_thursday=('t')
- assert_equal 'smtwt ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_friday=('f')
- assert_equal 'smtwtf ', @weekly_every_day.every_day
- @weekly_every_day.weekly_return_saturday=('s')
- assert_equal 'smtwtfs', @weekly_every_day.every_day
-
- # test remove
- @weekly_every_day.weekly_return_wednesday=(' ')
- assert_equal 'smt tfs', @weekly_every_day.every_day
- end
-
- def test_weekly_pattern
- assert_equal true, @weekly_every_day.has_next_todo(nil)
-
- due_date = @weekly_every_day.get_due_date(@sunday)
- assert_equal @monday, due_date
-
- # saturday is last day in week, so the next date should be sunday + n_weeks
- due_date = @weekly_every_day.get_due_date(@saturday)
- assert_equal @sunday + 2.weeks, due_date
-
- # remove tuesday and wednesday
- @weekly_every_day.weekly_return_tuesday=(' ')
- @weekly_every_day.weekly_return_wednesday=(' ')
- assert_equal 'sm tfs', @weekly_every_day.every_day
- due_date = @weekly_every_day.get_due_date(@monday)
- assert_equal @thursday, due_date
-
- @weekly_every_day.every_other1 = 1
- @weekly_every_day.every_day = ' tw '
- due_date = @weekly_every_day.get_due_date(@tuesday)
- assert_equal @wednesday, due_date
- due_date = @weekly_every_day.get_due_date(@wednesday)
- assert_equal @tuesday+1.week, due_date
- end
-
- def test_monthly_pattern
- due_date = @monthly_every_last_friday.get_due_date(@sunday)
- assert_equal Time.zone.local(2008,6,27), due_date
-
- friday_is_last_day_of_month = Time.zone.local(2008,10,31)
- due_date = @monthly_every_last_friday.get_due_date(friday_is_last_day_of_month-1.day )
- assert_equal friday_is_last_day_of_month , due_date
-
- @monthly_every_third_friday = @monthly_every_last_friday
- @monthly_every_third_friday.every_other3=3 #third
- due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008
- assert_equal Time.zone.local(2008, 6, 20), due_date
- # set date past third friday of this month
- due_date = @monthly_every_last_friday.get_due_date(Time.zone.local(2008,6,21)) # june 21th 2008
- assert_equal Time.zone.local(2008, 8, 15), due_date # every 2 months, so aug
-
- @monthly = @monthly_every_last_friday
- @monthly.recurrence_selector=0
- @monthly.every_other1 = 8 # every 8th day of the month
- @monthly.every_other2 = 2 # every 2 months
-
- due_date = @monthly.get_due_date(@saturday) # june 7th
- assert_equal @sunday, due_date # june 8th
-
- due_date = @monthly.get_due_date(@sunday) # june 8th
- assert_equal Time.zone.local(2008,8,8), due_date # aug 8th
- end
-
- def test_yearly_pattern
- # beginning of same year
- due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
- assert_equal @sunday, due_date # june 8th
-
- # same month, previous date
- due_date = @yearly.get_due_date(@saturday) # june 7th
- show_from_date = @yearly.get_show_from_date(@saturday) # june 7th
- assert_equal @sunday, due_date # june 8th
- assert_equal @sunday-5.days, show_from_date
-
- # same month, day after
- due_date = @yearly.get_due_date(@monday) # june 9th
- assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
- # very overdue
- due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7
- assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
-
- @yearly.recurrence_selector = 1
- @yearly.every_other3 = 2 # second
- @yearly.every_count = 3 # wednesday
- # beginning of same year
- due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
- assert_equal Time.zone.local(2008,6,11), due_date # june 11th
- # same month, before second wednesday
- due_date = @yearly.get_due_date(@saturday) # june 7th
- assert_equal Time.zone.local(2008,6,11), due_date # june 11th
- # same month, after second wednesday
- due_date = @yearly.get_due_date(Time.zone.local(2008,6,12)) # june 7th
- assert_equal Time.zone.local(2009,6,10), due_date # june 10th
-
- # test handling of nil
- due_date1 = @yearly.get_due_date(nil)
- due_date2 = @yearly.get_due_date(Time.now.utc + 1.day)
- assert_equal due_date1, due_date2
- end
-
- def test_last_sunday_of_march
- @yearly.recurrence_selector = 1
- @yearly.every_other2 = 3 # march
- @yearly.every_other3 = 5 # last
- @yearly.every_count = 0 # sunday
- due_date = @yearly.get_due_date(Time.zone.local(2008,10,1)) # oct 1st
- assert_equal Time.zone.local(2009,3,29), due_date # march 29th
- end
-
- def test_start_from_in_future
- # every_day should return start_day if it is in the future
- @every_day.start_from = @in_three_days
- due_date = @every_day.get_due_date(nil)
- assert_equal @in_three_days, due_date
- due_date = @every_day.get_due_date(@tomorrow)
- assert_equal @in_three_days, due_date
-
- # if we give a date in the future for the previous todo, the next to do
- # should be based on that future date.
- due_date = @every_day.get_due_date(@in_four_days)
- assert_equal @in_four_days+1.day, due_date
-
- @weekly_every_day.start_from = Time.zone.local(2020,1,1)
- assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(nil)
- assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(Time.zone.local(2019,10,1))
- assert_equal Time.zone.local(2020,1,10), @weekly_every_day.get_due_date(Time.zone.local(2020,1,9))
-
- @monthly_every_last_friday.start_from = Time.zone.local(2020,1,1)
- assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(nil) # last friday of jan
- assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(Time.zone.local(2019,12,1)) # last friday of jan
- assert_equal Time.zone.local(2020,2,28), @monthly_every_last_friday.get_due_date(Time.zone.local(2020,2,1)) # last friday of feb
-
- # start from after june 8th 2008
- @yearly.start_from = Time.zone.local(2020,6,12)
- assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(nil) # jun 8th next year
- assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2019,6,1)) # also next year
- assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2020,6,15)) # also next year
-
- this_year = Time.now.utc.year
- @yearly.start_from = Time.zone.local(this_year+1,6,12)
- due_date = @yearly.get_due_date(nil)
- assert_equal due_date.year, this_year+2
- end
-
- def test_toggle_completion
- t = @yearly
- assert_equal :active, t.current_state
- t.toggle_completion!
- assert_equal :completed, t.current_state
- t.toggle_completion!
- assert_equal :active, t.current_state
- end
-
- def test_starred
- @yearly.tag_with("1, 2, starred", User.find(@yearly.user_id))
- @yearly.tags.reload
-
- assert_equal true, @yearly.starred?
- assert_equal false, @weekly_every_day.starred?
-
- @yearly.toggle_star!
- assert_equal false, @yearly.starred?
- @yearly.toggle_star!
- assert_equal true, @yearly.starred?
- end
-
- def test_occurence_count
- @every_day.number_of_occurences = 2
- assert_equal true, @every_day.has_next_todo(@in_three_days)
- @every_day.inc_occurences
- assert_equal true, @every_day.has_next_todo(@in_three_days)
- @every_day.inc_occurences
- assert_equal false, @every_day.has_next_todo(@in_three_days)
-
- # after completion, when you reactivate the recurring todo, the occurences
- # count should be reset
- assert_equal 2, @every_day.occurences_count
- @every_day.toggle_completion!
- @every_day.toggle_completion!
- assert_equal true, @every_day.has_next_todo(@in_three_days)
- assert_equal 0, @every_day.occurences_count
- end
-
-end
+require File.dirname(__FILE__) + '/../test_helper'
+
+class RecurringTodoTest < Test::Rails::TestCase
+ fixtures :todos, :users, :contexts, :preferences, :tags, :taggings, :recurring_todos
+
+ def setup
+ @every_day = RecurringTodo.find(1).reload
+ @every_workday = RecurringTodo.find(2).reload
+ @weekly_every_day = RecurringTodo.find(3).reload
+ @monthly_every_last_friday = RecurringTodo.find(4).reload
+ @yearly = RecurringTodo.find(5).reload
+
+ @today = Time.now.utc
+ @tomorrow = @today + 1.day
+ @in_three_days = Time.now.utc + 3.days
+ @in_four_days = @in_three_days + 1.day # need a day after start_from
+
+ @friday = Time.zone.local(2008,6,6)
+ @saturday = Time.zone.local(2008,6,7)
+ @sunday = Time.zone.local(2008,6,8) # june 8, 2008 was a sunday
+ @monday = Time.zone.local(2008,6,9)
+ @tuesday = Time.zone.local(2008,6,10)
+ @wednesday = Time.zone.local(2008,6,11)
+ @thursday = Time.zone.local(2008,6,12)
+ end
+
+ def test_pattern_text
+ assert_equal "every day", @every_day.recurrence_pattern
+ assert_equal "on work days", @every_workday.recurrence_pattern
+ assert_equal "every last Friday of every 2 months", @monthly_every_last_friday.recurrence_pattern
+ assert_equal "every year on June 8", @yearly.recurrence_pattern
+ end
+
+ def test_daily_every_day
+ # every_day should return todays date if there was no previous date
+ due_date = @every_day.get_due_date(nil)
+ # use strftime in compare, because milisec / secs could be different
+ assert_equal @today.strftime("%d-%m-%y"), due_date.strftime("%d-%m-%y")
+
+ # when the last todo was completed today, the next todo is due tomorrow
+ due_date =@every_day.get_due_date(@today)
+ assert_equal @tomorrow, due_date
+
+ # do something every 14 days
+ @every_day.every_other1=14
+ due_date = @every_day.get_due_date(@today)
+ assert_equal @today+14.days, due_date
+ end
+
+ def test_daily_work_days
+ assert_equal @monday, @every_workday.get_due_date(@friday)
+ assert_equal @monday, @every_workday.get_due_date(@saturday)
+ assert_equal @monday, @every_workday.get_due_date(@sunday)
+ assert_equal @tuesday, @every_workday.get_due_date(@monday)
+ end
+
+ def test_show_from_date
+ # assume that target due_date works fine, i.e. don't do the same tests over
+
+ @every_day.target='show_from_date'
+ # when recurrence is targeted on show_from, due date shoult remain nil
+ assert_equal nil, @every_day.get_due_date(nil)
+ assert_equal nil, @every_day.get_due_date(@today-3.days)
+
+ # check show from get the next day
+ assert_equal @today, @every_day.get_show_from_date(@today-1.days)
+ assert_equal @today+1.day, @every_day.get_show_from_date(@today)
+
+ @every_day.target='due_date'
+ # when target on due_date, show_from is relative to due date unless delta=0
+ assert_equal nil, @every_day.get_show_from_date(@today-1.days)
+
+ @every_day.show_from_delta=10
+ assert_equal @today, @every_day.get_show_from_date(@today+9.days) #today+1+9-10
+
+ # TODO: show_from has no use case for daily pattern. Need to test on
+ # weekly/monthly/yearly
+ end
+
+ def test_end_date_on_recurring_todo
+ assert_equal true, @every_day.has_next_todo(@in_three_days)
+ assert_equal true, @every_day.has_next_todo(@in_four_days)
+ @every_day.end_date = @in_four_days
+ assert_equal false, @every_day.has_next_todo(@in_four_days)
+ end
+
+ def test_weekly_every_day_setters
+ @weekly_every_day.every_day = ' '
+
+ @weekly_every_day.weekly_return_sunday=('s')
+ assert_equal 's ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_monday=('m')
+ assert_equal 'sm ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_tuesday=('t')
+ assert_equal 'smt ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_wednesday=('w')
+ assert_equal 'smtw ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_thursday=('t')
+ assert_equal 'smtwt ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_friday=('f')
+ assert_equal 'smtwtf ', @weekly_every_day.every_day
+ @weekly_every_day.weekly_return_saturday=('s')
+ assert_equal 'smtwtfs', @weekly_every_day.every_day
+
+ # test remove
+ @weekly_every_day.weekly_return_wednesday=(' ')
+ assert_equal 'smt tfs', @weekly_every_day.every_day
+ end
+
+ def test_weekly_pattern
+ assert_equal true, @weekly_every_day.has_next_todo(nil)
+
+ due_date = @weekly_every_day.get_due_date(@sunday)
+ assert_equal @monday, due_date
+
+ # saturday is last day in week, so the next date should be sunday + n_weeks
+ due_date = @weekly_every_day.get_due_date(@saturday)
+ assert_equal @sunday + 2.weeks, due_date
+
+ # remove tuesday and wednesday
+ @weekly_every_day.weekly_return_tuesday=(' ')
+ @weekly_every_day.weekly_return_wednesday=(' ')
+ assert_equal 'sm tfs', @weekly_every_day.every_day
+ due_date = @weekly_every_day.get_due_date(@monday)
+ assert_equal @thursday, due_date
+
+ @weekly_every_day.every_other1 = 1
+ @weekly_every_day.every_day = ' tw '
+ due_date = @weekly_every_day.get_due_date(@tuesday)
+ assert_equal @wednesday, due_date
+ due_date = @weekly_every_day.get_due_date(@wednesday)
+ assert_equal @tuesday+1.week, due_date
+ end
+
+ def test_monthly_pattern
+ due_date = @monthly_every_last_friday.get_due_date(@sunday)
+ assert_equal Time.zone.local(2008,6,27), due_date
+
+ friday_is_last_day_of_month = Time.zone.local(2008,10,31)
+ due_date = @monthly_every_last_friday.get_due_date(friday_is_last_day_of_month-1.day )
+ assert_equal friday_is_last_day_of_month , due_date
+
+ @monthly_every_third_friday = @monthly_every_last_friday
+ @monthly_every_third_friday.every_other3=3 #third
+ due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008
+ assert_equal Time.zone.local(2008, 6, 20), due_date
+ # set date past third friday of this month
+ due_date = @monthly_every_last_friday.get_due_date(Time.zone.local(2008,6,21)) # june 21th 2008
+ assert_equal Time.zone.local(2008, 8, 15), due_date # every 2 months, so aug
+
+ @monthly = @monthly_every_last_friday
+ @monthly.recurrence_selector=0
+ @monthly.every_other1 = 8 # every 8th day of the month
+ @monthly.every_other2 = 2 # every 2 months
+
+ due_date = @monthly.get_due_date(@saturday) # june 7th
+ assert_equal @sunday, due_date # june 8th
+
+ due_date = @monthly.get_due_date(@sunday) # june 8th
+ assert_equal Time.zone.local(2008,8,8), due_date # aug 8th
+ end
+
+ def test_yearly_pattern
+ # beginning of same year
+ due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
+ assert_equal @sunday, due_date # june 8th
+
+ # same month, previous date
+ due_date = @yearly.get_due_date(@saturday) # june 7th
+ show_from_date = @yearly.get_show_from_date(@saturday) # june 7th
+ assert_equal @sunday, due_date # june 8th
+ assert_equal @sunday-5.days, show_from_date
+
+ # same month, day after
+ due_date = @yearly.get_due_date(@monday) # june 9th
+ assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
+ # very overdue
+ due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7
+ assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
+
+ @yearly.recurrence_selector = 1
+ @yearly.every_other3 = 2 # second
+ @yearly.every_count = 3 # wednesday
+ # beginning of same year
+ due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
+ assert_equal Time.zone.local(2008,6,11), due_date # june 11th
+ # same month, before second wednesday
+ due_date = @yearly.get_due_date(@saturday) # june 7th
+ assert_equal Time.zone.local(2008,6,11), due_date # june 11th
+ # same month, after second wednesday
+ due_date = @yearly.get_due_date(Time.zone.local(2008,6,12)) # june 7th
+ assert_equal Time.zone.local(2009,6,10), due_date # june 10th
+
+ # test handling of nil
+ due_date1 = @yearly.get_due_date(nil)
+ due_date2 = @yearly.get_due_date(Time.now.utc + 1.day)
+ assert_equal due_date1, due_date2
+ end
+
+ def test_last_sunday_of_march
+ @yearly.recurrence_selector = 1
+ @yearly.every_other2 = 3 # march
+ @yearly.every_other3 = 5 # last
+ @yearly.every_count = 0 # sunday
+ due_date = @yearly.get_due_date(Time.zone.local(2008,10,1)) # oct 1st
+ assert_equal Time.zone.local(2009,3,29), due_date # march 29th
+ end
+
+ def test_start_from_in_future
+ # every_day should return start_day if it is in the future
+ @every_day.start_from = @in_three_days
+ due_date = @every_day.get_due_date(nil)
+ assert_equal @in_three_days, due_date
+ due_date = @every_day.get_due_date(@tomorrow)
+ assert_equal @in_three_days, due_date
+
+ # if we give a date in the future for the previous todo, the next to do
+ # should be based on that future date.
+ due_date = @every_day.get_due_date(@in_four_days)
+ assert_equal @in_four_days+1.day, due_date
+
+ @weekly_every_day.start_from = Time.zone.local(2020,1,1)
+ assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(nil)
+ assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(Time.zone.local(2019,10,1))
+ assert_equal Time.zone.local(2020,1,10), @weekly_every_day.get_due_date(Time.zone.local(2020,1,9))
+
+ @monthly_every_last_friday.start_from = Time.zone.local(2020,1,1)
+ assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(nil) # last friday of jan
+ assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(Time.zone.local(2019,12,1)) # last friday of jan
+ assert_equal Time.zone.local(2020,2,28), @monthly_every_last_friday.get_due_date(Time.zone.local(2020,2,1)) # last friday of feb
+
+ # start from after june 8th 2008
+ @yearly.start_from = Time.zone.local(2020,6,12)
+ assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(nil) # jun 8th next year
+ assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2019,6,1)) # also next year
+ assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2020,6,15)) # also next year
+
+ this_year = Time.now.utc.year
+ @yearly.start_from = Time.zone.local(this_year+1,6,12)
+ due_date = @yearly.get_due_date(nil)
+ assert_equal due_date.year, this_year+2
+ end
+
+ def test_toggle_completion
+ t = @yearly
+ assert_equal :active, t.current_state
+ t.toggle_completion!
+ assert_equal :completed, t.current_state
+ t.toggle_completion!
+ assert_equal :active, t.current_state
+ end
+
+ def test_starred
+ @yearly.tag_with("1, 2, starred")
+ @yearly.tags.reload
+
+ assert_equal true, @yearly.starred?
+ assert_equal false, @weekly_every_day.starred?
+
+ @yearly.toggle_star!
+ assert_equal false, @yearly.starred?
+ @yearly.toggle_star!
+ assert_equal true, @yearly.starred?
+ end
+
+ def test_occurence_count
+ @every_day.number_of_occurences = 2
+ assert_equal true, @every_day.has_next_todo(@in_three_days)
+ @every_day.inc_occurences
+ assert_equal true, @every_day.has_next_todo(@in_three_days)
+ @every_day.inc_occurences
+ assert_equal false, @every_day.has_next_todo(@in_three_days)
+
+ # after completion, when you reactivate the recurring todo, the occurences
+ # count should be reset
+ assert_equal 2, @every_day.occurences_count
+ @every_day.toggle_completion!
+ @every_day.toggle_completion!
+ assert_equal true, @every_day.has_next_todo(@in_three_days)
+ assert_equal 0, @every_day.occurences_count
+ end
+
+end
diff --git a/test/unit/tag_test.rb b/test/unit/tag_test.rb
index 031d8bb7..c0aac834 100644
--- a/test/unit/tag_test.rb
+++ b/test/unit/tag_test.rb
@@ -4,7 +4,26 @@ class TagTest < Test::Rails::TestCase
fixtures :tags
# Replace this with your real tests.
- def test_truth
- assert true
+ def test_find_or_create_with_single_word
+ tag = Tag.find_or_create_by_name("test")
+ assert !tag.new_record?
+ end
+
+ def test_find_or_create_with_space
+ tag = Tag.find_or_create_by_name("test test")
+ assert !tag.new_record?
+ end
+
+ def test_find_or_create_with_dot
+ tag = Tag.find_or_create_by_name("a.b.c")
+ assert !tag.new_record?
+ end
+
+ def test_find_or_create_with_number_as_string
+ tag = Tag.find_or_create_by_name("12343")
+ assert !tag.new_record?
+
+ tag = Tag.find_or_create_by_name("8.1.2")
+ assert !tag.new_record?
end
end
diff --git a/test/unit/todo_test.rb b/test/unit/todo_test.rb
index 85c69b39..3e0fdf4d 100644
--- a/test/unit/todo_test.rb
+++ b/test/unit/todo_test.rb
@@ -1,181 +1,181 @@
-require File.dirname(__FILE__) + '/../test_helper'
-require 'date'
-
-class TodoTest < Test::Rails::TestCase
- fixtures :todos, :users, :contexts, :preferences, :tags, :taggings
-
- def setup
- @not_completed1 = Todo.find(1).reload
- @not_completed2 = Todo.find(2).reload
- @completed = Todo.find(8).reload
- end
-
- # Test loading a todo item
- def test_load
- assert_kind_of Todo, @not_completed1
- assert_equal 1, @not_completed1.id
- assert_equal 1, @not_completed1.context_id
- assert_equal 2, @not_completed1.project_id
- assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
- assert_nil @not_completed1.notes
- assert @not_completed1.completed? == false
- assert_equal 1.week.ago.beginning_of_day.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M")
- assert_equal 2.week.from_now.beginning_of_day.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d")
- assert_nil @not_completed1.completed_at
- assert_equal 1, @not_completed1.user_id
- end
-
- def test_completed
- assert_kind_of Todo, @completed
- assert @completed.completed?
- assert_not_nil @completed.completed_at
- end
-
- def test_completed_at_cleared_after_toggle_to_active
- assert_kind_of Todo, @completed
- assert @completed.completed?
- @completed.toggle_completion!
- assert @completed.active?
- assert_nil @completed.completed_at
- end
-
-
- # Validation tests
- #
- def test_validate_presence_of_description
- assert_equal "Call dinosaur exterminator", @not_completed2.description
- @not_completed2.description = ""
- assert !@not_completed2.save
- assert_equal 1, @not_completed2.errors.count
- assert_equal "can't be blank", @not_completed2.errors.on(:description)
- end
-
- def test_validate_length_of_description
- assert_equal "Call dinosaur exterminator", @not_completed2.description
- @not_completed2.description = generate_random_string(101)
- assert !@not_completed2.save
- assert_equal 1, @not_completed2.errors.count
- assert_equal "is too long (maximum is 100 characters)", @not_completed2.errors.on(:description)
- end
-
- def test_validate_length_of_notes
- assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes
- @not_completed2.notes = generate_random_string(60001)
- assert !@not_completed2.save
- assert_equal 1, @not_completed2.errors.count
- assert_equal "is too long (maximum is 60000 characters)", @not_completed2.errors.on(:notes)
- end
-
- def test_validate_show_from_must_be_a_date_in_the_future
- t = @not_completed2
- t[:show_from] = 1.week.ago # we have to set this via the indexer because show_from=() updates the state
- # and actual show_from value appropriately based on the date
- assert !t.save
- assert_equal 1, t.errors.count
- assert_equal "must be a date in the future", t.errors.on(:show_from)
- end
-
- def test_defer_an_existing_todo
- @not_completed2
- assert_equal :active, @not_completed2.current_state
- @not_completed2.show_from = next_week
- assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml
- assert_equal :deferred, @not_completed2.current_state
- end
-
- def test_create_a_new_deferred_todo
- user = users(:other_user)
- todo = user.todos.build
- todo.show_from = next_week
- todo.context_id = 1
- todo.description = 'foo'
- assert todo.save, "should have saved successfully" + todo.errors.to_xml
- assert_equal :deferred, todo.current_state
- end
-
- def test_create_a_new_deferred_todo_by_passing_attributes
- user = users(:other_user)
- todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo')
- assert todo.save, "should have saved successfully" + todo.errors.to_xml
- assert_equal :deferred, todo.current_state
- end
-
- def test_feed_options
- opts = Todo.feed_options(users(:admin_user))
- assert_equal 'Tracks Actions', opts[:title], 'Unexpected value for :title key of feed_options'
- assert_equal 'Actions for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options'
- end
-
- def test_toggle_completion
- t = @not_completed1
- assert_equal :active, t.current_state
- t.toggle_completion!
- assert_equal :completed, t.current_state
- t.toggle_completion!
- assert_equal :active, t.current_state
- end
-
- def test_activate_also_saves
- t = @not_completed1
- t.show_from = 1.week.from_now
- t.save!
- assert t.deferred?
- t.reload
- t.activate!
- assert t.active?
- t.reload
- assert t.active?
- end
-
- def test_project_returns_null_object_when_nil
- t = @not_completed1
- assert !t.project.is_a?(NullProject)
- t.project = nil
- assert t.project.is_a?(NullProject)
- end
-
- def test_initial_state_defaults_to_active
- t = Todo.new
- t.description = 'foo'
- t.context_id = 1
- t.save!
- t.reload
- assert_equal :active, t.current_state
- end
-
- def test_initial_state_is_deferred_when_show_from_in_future
- t = Todo.new
- t.user = users(:admin_user)
- t.description = 'foo'
- t.context_id = 1
- t.show_from = 1.week.from_now.to_date
- t.save!
- t.reload
- assert_equal :deferred, t.current_state
- end
-
- def test_todo_is_not_starred
- assert !@not_completed1.starred?
- end
-
- def test_todo_2_is_not_starred
- assert !Todo.find(2).starred?
- end
-
- def test_todo_is_starred_after_starred_tag_is_added
- @not_completed1.add_tag('starred')
- assert @not_completed1.starred?
- end
-
- def test_todo_is_starred_after_toggle_starred
- @not_completed1.toggle_star!
- assert @not_completed1.starred?
- end
-
- def test_todo_is_not_starred_after_toggle_starred_twice
- @not_completed1.toggle_star!
- @not_completed1.toggle_star!
- assert !@not_completed1.starred?
- end
-
-end
+require File.dirname(__FILE__) + '/../test_helper'
+require 'date'
+
+class TodoTest < Test::Rails::TestCase
+ fixtures :todos, :users, :contexts, :preferences, :tags, :taggings
+
+ def setup
+ @not_completed1 = Todo.find(1).reload
+ @not_completed2 = Todo.find(2).reload
+ @completed = Todo.find(8).reload
+ end
+
+ # Test loading a todo item
+ def test_load
+ assert_kind_of Todo, @not_completed1
+ assert_equal 1, @not_completed1.id
+ assert_equal 1, @not_completed1.context_id
+ assert_equal 2, @not_completed1.project_id
+ assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
+ assert_nil @not_completed1.notes
+ assert @not_completed1.completed? == false
+ assert_equal 1.week.ago.beginning_of_day.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M")
+ assert_equal 2.week.from_now.beginning_of_day.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d")
+ assert_nil @not_completed1.completed_at
+ assert_equal 1, @not_completed1.user_id
+ end
+
+ def test_completed
+ assert_kind_of Todo, @completed
+ assert @completed.completed?
+ assert_not_nil @completed.completed_at
+ end
+
+ def test_completed_at_cleared_after_toggle_to_active
+ assert_kind_of Todo, @completed
+ assert @completed.completed?
+ @completed.toggle_completion!
+ assert @completed.active?
+ assert_nil @completed.completed_at
+ end
+
+
+ # Validation tests
+ #
+ def test_validate_presence_of_description
+ assert_equal "Call dinosaur exterminator", @not_completed2.description
+ @not_completed2.description = ""
+ assert !@not_completed2.save
+ assert_equal 1, @not_completed2.errors.count
+ assert_equal "can't be blank", @not_completed2.errors.on(:description)
+ end
+
+ def test_validate_length_of_description
+ assert_equal "Call dinosaur exterminator", @not_completed2.description
+ @not_completed2.description = generate_random_string(101)
+ assert !@not_completed2.save
+ assert_equal 1, @not_completed2.errors.count
+ assert_equal "is too long (maximum is 100 characters)", @not_completed2.errors.on(:description)
+ end
+
+ def test_validate_length_of_notes
+ assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes
+ @not_completed2.notes = generate_random_string(60001)
+ assert !@not_completed2.save
+ assert_equal 1, @not_completed2.errors.count
+ assert_equal "is too long (maximum is 60000 characters)", @not_completed2.errors.on(:notes)
+ end
+
+ def test_validate_show_from_must_be_a_date_in_the_future
+ t = @not_completed2
+ t[:show_from] = 1.week.ago # we have to set this via the indexer because show_from=() updates the state
+ # and actual show_from value appropriately based on the date
+ assert !t.save
+ assert_equal 1, t.errors.count
+ assert_equal "must be a date in the future", t.errors.on(:show_from)
+ end
+
+ def test_defer_an_existing_todo
+ @not_completed2
+ assert_equal :active, @not_completed2.current_state
+ @not_completed2.show_from = next_week
+ assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml
+ assert_equal :deferred, @not_completed2.current_state
+ end
+
+ def test_create_a_new_deferred_todo
+ user = users(:other_user)
+ todo = user.todos.build
+ todo.show_from = next_week
+ todo.context_id = 1
+ todo.description = 'foo'
+ assert todo.save, "should have saved successfully" + todo.errors.to_xml
+ assert_equal :deferred, todo.current_state
+ end
+
+ def test_create_a_new_deferred_todo_by_passing_attributes
+ user = users(:other_user)
+ todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo')
+ assert todo.save, "should have saved successfully" + todo.errors.to_xml
+ assert_equal :deferred, todo.current_state
+ end
+
+ def test_feed_options
+ opts = Todo.feed_options(users(:admin_user))
+ assert_equal 'Tracks Actions', opts[:title], 'Unexpected value for :title key of feed_options'
+ assert_equal 'Actions for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options'
+ end
+
+ def test_toggle_completion
+ t = @not_completed1
+ assert_equal :active, t.current_state
+ t.toggle_completion!
+ assert_equal :completed, t.current_state
+ t.toggle_completion!
+ assert_equal :active, t.current_state
+ end
+
+ def test_activate_also_saves
+ t = @not_completed1
+ t.show_from = 1.week.from_now
+ t.save!
+ assert t.deferred?
+ t.reload
+ t.activate!
+ assert t.active?
+ t.reload
+ assert t.active?
+ end
+
+ def test_project_returns_null_object_when_nil
+ t = @not_completed1
+ assert !t.project.is_a?(NullProject)
+ t.project = nil
+ assert t.project.is_a?(NullProject)
+ end
+
+ def test_initial_state_defaults_to_active
+ t = Todo.new
+ t.description = 'foo'
+ t.context_id = 1
+ t.save!
+ t.reload
+ assert_equal :active, t.current_state
+ end
+
+ def test_initial_state_is_deferred_when_show_from_in_future
+ t = Todo.new
+ t.user = users(:admin_user)
+ t.description = 'foo'
+ t.context_id = 1
+ t.show_from = 1.week.from_now.to_date
+ t.save!
+ t.reload
+ assert_equal :deferred, t.current_state
+ end
+
+ def test_todo_is_not_starred
+ assert !@not_completed1.starred?
+ end
+
+ def test_todo_2_is_not_starred
+ assert !Todo.find(2).starred?
+ end
+
+ def test_todo_is_starred_after_starred_tag_is_added
+ @not_completed1._add_tags('starred')
+ assert @not_completed1.starred?
+ end
+
+ def test_todo_is_starred_after_toggle_starred
+ @not_completed1.toggle_star!
+ assert @not_completed1.starred?
+ end
+
+ def test_todo_is_not_starred_after_toggle_starred_twice
+ @not_completed1.toggle_star!
+ @not_completed1.toggle_star!
+ assert !@not_completed1.starred?
+ end
+
+end
diff --git a/vendor/gems/highline-1.5.0/.specification b/vendor/gems/highline-1.5.0/.specification
new file mode 100644
index 00000000..2ebbb33c
--- /dev/null
+++ b/vendor/gems/highline-1.5.0/.specification
@@ -0,0 +1,87 @@
+--- !ruby/object:Gem::Specification
+name: highline
+version: !ruby/object:Gem::Version
+ version: 1.5.0
+platform: ruby
+authors:
+- James Edward Gray II
+autorequire:
+bindir: bin
+cert_chain: []
+
+date: 2008-11-05 00:00:00 +01:00
+default_executable:
+dependencies: []
+
+description: A high-level IO library that provides validation, type conversion, and more for command-line interfaces. HighLine also includes a complete menu system that can crank out anything from simple list selection to complete shells with just minutes of work.
+email: james@grayproductions.net
+executables: []
+
+extensions: []
+
+extra_rdoc_files:
+- README
+- INSTALL
+- TODO
+- CHANGELOG
+- LICENSE
+files:
+- examples/ansi_colors.rb
+- examples/asking_for_arrays.rb
+- examples/basic_usage.rb
+- examples/color_scheme.rb
+- examples/menus.rb
+- examples/overwrite.rb
+- examples/page_and_wrap.rb
+- examples/password.rb
+- examples/trapping_eof.rb
+- examples/using_readline.rb
+- lib/highline/color_scheme.rb
+- lib/highline/import.rb
+- lib/highline/menu.rb
+- lib/highline/question.rb
+- lib/highline/system_extensions.rb
+- lib/highline.rb
+- test/tc_color_scheme.rb
+- test/tc_highline.rb
+- test/tc_import.rb
+- test/tc_menu.rb
+- test/ts_all.rb
+- Rakefile
+- setup.rb
+- README
+- INSTALL
+- TODO
+- CHANGELOG
+- LICENSE
+has_rdoc: true
+homepage: http://highline.rubyforge.org
+post_install_message:
+rdoc_options:
+- --title
+- HighLine Documentation
+- --main
+- README
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+requirements: []
+
+rubyforge_project: highline
+rubygems_version: 1.3.1
+signing_key:
+specification_version: 2
+summary: HighLine is a high-level command-line IO library.
+test_files:
+- test/ts_all.rb
diff --git a/vendor/gems/ruby-openid-2.1.2/.specification b/vendor/gems/ruby-openid-2.1.2/.specification
index e6c9612d..6a1a03db 100644
--- a/vendor/gems/ruby-openid-2.1.2/.specification
+++ b/vendor/gems/ruby-openid-2.1.2/.specification
@@ -8,7 +8,7 @@ authors:
autorequire: openid
bindir: bin
cert_chain:
-date: 2008-06-27 00:00:00 -04:00
+date: 2008-06-27 05:00:00 +01:00
default_executable:
dependencies: []
@@ -265,6 +265,7 @@ rdoc_options:
- --main
- README
require_paths:
+- bin
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
@@ -281,7 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
requirements: []
rubyforge_project:
-rubygems_version: 1.2.0
+rubygems_version: 1.0.1
signing_key:
specification_version: 1
summary: A library for consuming and serving OpenID identities.
diff --git a/vendor/plugins/has_many_polymorphs/generators/tagging/templates/tagging_extensions.rb b/vendor/plugins/has_many_polymorphs/generators/tagging/templates/tagging_extensions.rb
index 280aa3ce..9795abd5 100644
--- a/vendor/plugins/has_many_polymorphs/generators/tagging/templates/tagging_extensions.rb
+++ b/vendor/plugins/has_many_polymorphs/generators/tagging/templates/tagging_extensions.rb
@@ -120,7 +120,7 @@ class ActiveRecord::Base #:nodoc:
sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} "
- add_joins!(sql, options, scope)
+ add_joins!(sql, options[:joins], scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "
diff --git a/vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/class_methods.rb b/vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/class_methods.rb
index 86c7be90..394985cf 100644
--- a/vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/class_methods.rb
+++ b/vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/class_methods.rb
@@ -360,10 +360,14 @@ Be aware, however, that NULL != 'Spot' returns false due to SQ
begin
table = plural._as_class.table_name
rescue NameError => e
- raise PolymorphicError, "Could not find a valid class for #{plural.inspect}. If it's namespaced, be sure to specify it as :\"module/#{plural}\" instead."
+ raise PolymorphicError, "Could not find a valid class for #{plural.inspect} (tried #{plural.to_s._classify}). If it's namespaced, be sure to specify it as :\"module/#{plural}\" instead."
end
- plural._as_class.columns.map(&:name).each_with_index do |field, f_index|
- aliases["#{table}.#{field}"] = "t#{t_index}_r#{f_index}"
+ begin
+ plural._as_class.columns.map(&:name).each_with_index do |field, f_index|
+ aliases["#{table}.#{field}"] = "t#{t_index}_r#{f_index}"
+ end
+ rescue ActiveRecord::StatementInvalid => e
+ _logger_warn "Looks like your table doesn't exist for #{plural.to_s._classify}.\nError #{e}\nSkipping..."
end
end
end
diff --git a/vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/reflection.rb b/vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/reflection.rb
index 67c69d5a..0091397d 100644
--- a/vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/reflection.rb
+++ b/vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/reflection.rb
@@ -33,7 +33,7 @@ Inherits from ActiveRecord::Reflection::AssociationReflection.
=end
- class PolymorphicReflection < AssociationReflection
+ class PolymorphicReflection < ThroughReflection
# Stub out the validity check. Has_many_polymorphs checks validity on macro creation, not on reflection.
def check_validity!
# nothing
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/public/dispatch.cgi b/vendor/plugins/has_many_polymorphs/test/integration/app/public/dispatch.cgi
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/public/dispatch.fcgi b/vendor/plugins/has_many_polymorphs/test/integration/app/public/dispatch.fcgi
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/public/dispatch.rb b/vendor/plugins/has_many_polymorphs/test/integration/app/public/dispatch.rb
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/about b/vendor/plugins/has_many_polymorphs/test/integration/app/script/about
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/breakpointer b/vendor/plugins/has_many_polymorphs/test/integration/app/script/breakpointer
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/console b/vendor/plugins/has_many_polymorphs/test/integration/app/script/console
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/destroy b/vendor/plugins/has_many_polymorphs/test/integration/app/script/destroy
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/generate b/vendor/plugins/has_many_polymorphs/test/integration/app/script/generate
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/performance/benchmarker b/vendor/plugins/has_many_polymorphs/test/integration/app/script/performance/benchmarker
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/performance/profiler b/vendor/plugins/has_many_polymorphs/test/integration/app/script/performance/profiler
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/plugin b/vendor/plugins/has_many_polymorphs/test/integration/app/script/plugin
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/process/inspector b/vendor/plugins/has_many_polymorphs/test/integration/app/script/process/inspector
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/process/reaper b/vendor/plugins/has_many_polymorphs/test/integration/app/script/process/reaper
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/process/spawner b/vendor/plugins/has_many_polymorphs/test/integration/app/script/process/spawner
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/runner b/vendor/plugins/has_many_polymorphs/test/integration/app/script/runner
old mode 100755
new mode 100644
diff --git a/vendor/plugins/has_many_polymorphs/test/integration/app/script/server b/vendor/plugins/has_many_polymorphs/test/integration/app/script/server
old mode 100755
new mode 100644