diff --git a/.gitignore b/.gitignore
index 433e85a0..35dee02b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,15 @@
*.tmproj
-config/database.yml
-config/environment.rb
-log
-tmp
-db/data.yml
-db/*.sqlite3
-nbproject
-vendor/plugins/query_trace/
-db/schema.rb
.dotest
+/.emacs-project
+config/database.yml
+config/deploy.rb
+config/environment.rb
+db/*.sqlite3
+db/data.yml
+db/schema.rb
+log
+nbproject
public/javascripts/cache
-public/stylesheets/cache
\ No newline at end of file
+public/stylesheets/cache
+tmp
+vendor/plugins/query_trace/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..bca6838e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "doc/manual"]
+ path = doc/manual
+ url = git://github.com/bsag/tracks_manual.git
diff --git a/Capfile b/Capfile
new file mode 100644
index 00000000..0402d1a1
--- /dev/null
+++ b/Capfile
@@ -0,0 +1,2 @@
+load 'deploy' if respond_to?(:namespace) # cap2 differentiator
+load 'config/deploy'
\ No newline at end of file
diff --git a/README b/README
index 5cce7ac7..8f080e2c 100644
--- a/README
+++ b/README
@@ -1,19 +1,21 @@
# Tracks: a GTD(TM) web application, built with Ruby on Rails
-* Project homepage: http://www.rousette.org.uk/projects/
+* Project homepage: http://getontracks.org/
* GitHub: http://github.com/bsag/tracks/
-* Trac (for bug reports and feature requests): http://dev.rousette.org.uk/report/6
-* Wiki (community contributed information): http://www.rousette.org.uk/projects/wiki/
-* Forum: http://www.rousette.org.uk/projects/forums/
+* Assembla space (for bug reports and feature requests): http://www.assembla.com/spaces/tracks-tickets/tickets
+* Wiki (community contributed information): http://getontracks.org/wiki/
+* Forum: http://getontracks.org/forums/
* Mailing list: http://lists.rousette.org.uk/mailman/listinfo/tracks-discuss
* Original developer: bsag (http://www.rousette.org.uk/)
-* Contributors: http://dev.rousette.org.uk/wiki/Tracks/Contributing/Contributors
-* Version: 1.7RC
+* Contributors: http://getontracks.org/wiki/Tracks/Contributing/Contributors
+* Version: 1.7
* Copyright: (cc) 2004-2008 rousette.org.uk.
* License: GNU GPL
All the documentation for Tracks can be found within the /doc directory. It contains a manual in HTML (manual.html) or PDF format (manual.pdf), and this includes full instructions for both new installations and upgrades from older installations of Tracks. The instructions might appear long and intimidatingly complex, but that is mostly because of the number of different platforms supported, and the different configurations which can be used (e.g. running Tracks on your local computer or on a remote server). If you choose the appropriate section for your situation (installation vs. upgrade), and use the easiest (recommended) method, you should find the instructions easy to follow. If you encounter problems, try searching the wiki, forum or mailing list (URLs above), and ask a question if you cannot find a solution to your problem.
+If you checked out Tracks from the GitHub repository, the manual is not provided by default and is in its own git submodule. To checkout the manual's source files, type "git submodule init doc/manual & git submodule update doc/manual". From then on, you should be able to issue the command "git pull" in the doc/manual directory to update the manual with the latest changes.
+
For those upgrading, change notes are available in /doc/CHANGELOG. If you are thinking about contributing towards the development of Tracks, please read /doc/README_DEVELOPERS for general information, or /doc/tracks_api_wrapper.rb for information on Tracks' API.
While fully usable for everyday use, Tracks is still a work in progress. Make sure that you take sensible precautions and back up all your data frequently, taking particular care when you are upgrading.
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 9443168f..1c8dcb5f 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -40,8 +40,10 @@ class ProjectsController < ApplicationController
end
def show
+ @contexts = current_user.contexts(true)
init_data_for_sidebar unless mobile?
@projects = current_user.projects
+ @contexts = current_user.contexts
@page_title = "TRACKS::Project: #{@project.name}"
@project.todos.send :with_scope, :find => { :include => [:context] } do
@not_done = @project.not_done_todos(:include_project_hidden_todos => true)
diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb
index 2d0f95c6..2a7009e4 100755
--- a/app/controllers/stats_controller.rb
+++ b/app/controllers/stats_controller.rb
@@ -6,8 +6,9 @@ class StatsController < ApplicationController
def index
@page_title = 'TRACKS::Statistics'
-
- @unique_tags = @tags.count(:all, {:group=>"tag_id"})
+
+ @tags_count = get_total_number_of_tags_of_user
+ @unique_tags_count = get_unique_tags_of_user.size
@hidden_contexts = @contexts.hidden
@first_action = @actions.find(:first, :order => "created_at ASC")
@@ -394,7 +395,7 @@ class StatsController < ApplicationController
def context_running_actions_data
# get incomplete action count per visible context
- #
+ #
# Went from GROUP BY c.id to c.name for compatibility with postgresql. Since
# the name is forced to be unique, this should work.
@all_actions_per_context = @contexts.find_by_sql(
@@ -643,11 +644,32 @@ class StatsController < ApplicationController
private
+ def get_unique_tags_of_user
+ tag_ids = @actions.find_by_sql([
+ "SELECT DISTINCT tags.id as id "+
+ "FROM tags, taggings, todos "+
+ "WHERE todos.user_id=? "+
+ "AND tags.id = taggings.tag_id " +
+ "AND taggings.taggable_id = todos.id ", current_user.id])
+ tags_ids_s = tag_ids.map(&:id).sort.join(",")
+ return {} if tags_ids_s.blank? # return empty array for .size to work
+ return Tag.find(:all, :conditions => "id in (" + tags_ids_s + ")")
+ end
+
+ def get_total_number_of_tags_of_user
+ # same query as get_unique_tags_of_user except for the DISTINCT
+ return @actions.find_by_sql([
+ "SELECT tags.id as id "+
+ "FROM tags, taggings, todos "+
+ "WHERE todos.user_id=? "+
+ "AND tags.id = taggings.tag_id " +
+ "AND taggings.taggable_id = todos.id ", current_user.id]).size
+ end
+
def init
@actions = @user.todos
@projects = @user.projects
@contexts = @user.contexts
- @tags = @user.tags
# default chart dimensions
@chart_width=460
@@ -724,7 +746,7 @@ class StatsController < ApplicationController
def get_stats_contexts
# get action count per context for TOP 5
- #
+ #
# Went from GROUP BY c.id to c.id, c.name for compatibility with postgresql.
# Since the name is forced to be unique, this should work.
@actions_per_context = @contexts.find_by_sql(
@@ -737,7 +759,7 @@ class StatsController < ApplicationController
)
# get incomplete action count per visible context for TOP 5
- #
+ #
# Went from GROUP BY c.id to c.id, c.name for compatibility with postgresql.
# Since the name is forced to be unique, this should work.
@running_actions_per_context = @contexts.find_by_sql(
@@ -752,7 +774,7 @@ class StatsController < ApplicationController
def get_stats_projects
# get the first 10 projects and their action count (all actions)
- #
+ #
# Went from GROUP BY p.id to p.name for compatibility with postgresql. Since
# the name is forced to be unique, this should work.
@projects_and_actions = @projects.find_by_sql(
@@ -812,9 +834,10 @@ class StatsController < ApplicationController
# Get the tag cloud for all tags for actions
query = "SELECT tags.id, name, count(*) AS count"
- query << " FROM taggings, tags"
+ query << " FROM taggings, tags, todos"
query << " WHERE tags.id = tag_id"
- query << " AND taggings.user_id="+@user.id.to_s+" "
+ query << " AND taggings.taggable_id = todos.id"
+ query << " AND todos.user_id="+current_user.id.to_s+" "
query << " AND taggings.taggable_type='Todo' "
query << " GROUP BY tags.id, tags.name"
query << " ORDER BY count DESC, name"
@@ -833,7 +856,7 @@ class StatsController < ApplicationController
query = "SELECT tags.id, tags.name AS name, count(*) AS count"
query << " FROM taggings, tags, todos"
query << " WHERE tags.id = tag_id"
- query << " AND taggings.user_id=? "
+ query << " AND todos.user_id=? "
query << " AND taggings.taggable_type='Todo' "
query << " AND taggings.taggable_id=todos.id "
query << " AND (todos.created_at > ? OR "
@@ -842,7 +865,7 @@ class StatsController < ApplicationController
query << " ORDER BY count DESC, name"
query << " LIMIT 100"
@tags_for_cloud_90days = Tag.find_by_sql(
- [query, @user.id, @cut_off_3months, @cut_off_3months]
+ [query, current_user.id, @cut_off_3months, @cut_off_3months]
).sort_by { |tag| tag.name.downcase }
max_90days, @tags_min_90days = 0, 0
diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb
index ff5b767f..70f149b2 100644
--- a/app/controllers/todos_controller.rb
+++ b/app/controllers/todos_controller.rb
@@ -1,932 +1,935 @@
-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||''}\n
Project: #{project_link}
\n
Context: #{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?
+ if params[:tag_list]
+ @todo.tag_with(params[:tag_list])
+ @todo.tags(true) #force a reload for proper rendering
+ end
+ @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 => ['todos.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 => ['todos.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 => ['todos.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 => ['todos.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||''}\n
Project: #{project_link}
\n
Context: #{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/models/preference.rb b/app/models/preference.rb
index d4530f3f..d9da36bb 100644
--- a/app/models/preference.rb
+++ b/app/models/preference.rb
@@ -1,28 +1,37 @@
class Preference < ActiveRecord::Base
belongs_to :user
belongs_to :sms_context, :class_name => 'Context'
-
+
def self.due_styles
{ :due_in_n_days => 0, :due_on => 1}
end
-
+
def self.day_number_to_name_map
{ 0 => "Sunday",
- 1 => "Monday",
- 2 => "Tuesday",
- 3 => "Wednesday",
- 4 => "Thursday",
- 5 => "Friday",
- 6 => "Saturday"}
+ 1 => "Monday",
+ 2 => "Tuesday",
+ 3 => "Wednesday",
+ 4 => "Thursday",
+ 5 => "Friday",
+ 6 => "Saturday"}
end
-
+
def hide_completed_actions?
return show_number_completed == 0
end
-
+
def parse_date(s)
return nil if s.blank?
- user.at_midnight(Date.strptime(s, date_format))
+ date = nil
+
+ if s.is_a?(Time)
+ date = s.to_date
+ elsif s.is_a?(String)
+ date = Date.strptime(s, date_format)
+ else
+ raise ArgumentError.new("Bad argument type:#{s.class}")
+ end
+
+ user.at_midnight(date)
end
-
-end
\ No newline at end of file
+end
diff --git a/app/models/tagging.rb b/app/models/tagging.rb
index de6c3743..4d16acd2 100644
--- a/app/models/tagging.rb
+++ b/app/models/tagging.rb
@@ -5,7 +5,6 @@ class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :taggable, :polymorphic => true
- # 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
diff --git a/app/models/user.rb b/app/models/user.rb
index 6c0302cc..f2ecedb8 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -104,8 +104,6 @@ class User < ActiveRecord::Base
end
has_many :notes, :order => "created_at DESC", :dependent => :delete_all
has_one :preference, :dependent => :destroy
- has_many :taggings
- has_many :tags, :through => :taggings, :select => "DISTINCT tags.*"
attr_protected :is_admin
@@ -202,7 +200,7 @@ class User < ActiveRecord::Base
# These create and unset the fields required for remembering users between browser closes
def remember_me
self.remember_token_expires_at = 2.weeks.from_now.utc
- self.remember_token = self.class.sha1("#{login}--#{remember_token_expires_at}")
+ self.remember_token ||= self.class.sha1("#{login}--#{remember_token_expires_at}")
save(false)
end
diff --git a/app/views/login/login_mobile.html.erb b/app/views/login/login_mobile.html.erb
index f3ff6511..a2b7cd94 100644
--- a/app/views/login/login_mobile.html.erb
+++ b/app/views/login/login_mobile.html.erb
@@ -39,7 +39,7 @@
...or login with an Open ID:
- <% form_tag formatted_open_id_begin_path(:format => 'm') do %>
+ <% form_tag formatted_login_path(:format => 'm') do %>
diff --git a/app/views/shared/_footer.rhtml b/app/views/shared/_footer.rhtml
index 21ef0340..95fd3a78 100644
--- a/app/views/shared/_footer.rhtml
+++ b/app/views/shared/_footer.rhtml
@@ -1,3 +1,3 @@
diff --git a/app/views/stats/_totals.rhtml b/app/views/stats/_totals.rhtml
index ebb3b986..fe83f388 100755
--- a/app/views/stats/_totals.rhtml
+++ b/app/views/stats/_totals.rhtml
@@ -16,7 +16,7 @@ deferred actions.
a total of <%= @actions.count %> actions.
<%= @actions.count(:conditions => "NOT completed_at IS NULL") %> of these are completed.
-
You have <%= @tags.count-%> tags placed on actions. Of those tags,
-<%= @unique_tags.size -%> are unique.
+
You have <%= @tags_count-%> tags placed on actions. Of those tags,
+<%= @unique_tags_count -%> are unique.
<% end -%>
diff --git a/app/views/todos/_todo.html.erb b/app/views/todos/_todo.html.erb
index 231eaf97..6e123e62 100644
--- a/app/views/todos/_todo.html.erb
+++ b/app/views/todos/_todo.html.erb
@@ -31,4 +31,4 @@
<% end -%>
-<%= apply_behaviour ".date_clear:click","var selector_x = this.getAttribute('id').replace('_x', ''); $(selector_x).value='';" %>
\ No newline at end of file
+<%= apply_behaviour ".date_clear:click","var selector_x = this.getAttribute('id').replace('_x', ''); $(selector_x).value='';" %>
diff --git a/app/views/todos/edit.js.rjs b/app/views/todos/edit.js.rjs
index 9f6da060..9e57e85c 100644
--- a/app/views/todos/edit.js.rjs
+++ b/app/views/todos/edit.js.rjs
@@ -1,4 +1,4 @@
page[dom_id(@todo, 'form')].down('.placeholder').replace_html :partial => 'todos/edit_form'
page[dom_id(@todo, 'line')].hide
page[dom_id(@todo, 'edit')].show
-page[dom_id(@todo, 'form')].down('table').down('input').focus
\ No newline at end of file
+page[dom_id(@todo, 'form')].down('input#todo_description').focus
\ No newline at end of file
diff --git a/config/deploy.rb-example b/config/deploy.rb-example
new file mode 100644
index 00000000..77c7475f
--- /dev/null
+++ b/config/deploy.rb-example
@@ -0,0 +1,101 @@
+#############################################################
+# This file is designed as a starting point to use
+# capistrano to deploy the trunk of tracks to a webhost
+# where it is served using Phusion Passenger. For more
+# info on getting started with Passenger, see
+# http://www.modrails.com/
+#############################################################
+
+
+#############################################################
+# Application
+#############################################################
+
+set :application, "tracks"
+set :deploy_to, "/var/www/apps/tracks"
+
+#############################################################
+# Settings
+#############################################################
+
+default_run_options[:pty] = true
+ssh_options[:forward_agent] = true
+set :use_sudo, true
+set :scm_verbose, true
+set :rails_env, "production"
+
+#############################################################
+# Servers
+#############################################################
+
+#set :user, "your_login_name_on_your_webhost_if_different_from_local"
+set :domain, "tracks.yoursite.com"
+server domain, :app, :web
+role :db, domain, :primary => true
+
+#############################################################
+# Git
+#############################################################
+
+set :scm, :git
+set :branch, "master"
+set :repository, "git://github.com/bsag/tracks.git"
+set :deploy_via, :remote_cache
+
+#############################################################
+# Passenger
+#############################################################
+
+namespace :deploy do
+ desc "Symlink config files"
+ task :before_symlink do
+ run "rm #{release_path}/public/.htaccess" #not compatible with Passenger
+ run "ln -s #{shared_path}/config/database.yml #{release_path}/config/database.yml"
+ run "ln -s #{shared_path}/config/environment.rb #{release_path}/config/environment.rb"
+ end
+
+ # Restart passenger on deploy
+ desc "Restarting mod_rails with restart.txt"
+ task :restart, :roles => :app, :except => { :no_release => true } do
+ run "touch #{current_path}/tmp/restart.txt"
+ end
+
+ [:start, :stop].each do |t|
+ desc "#{t} task is a no-op with mod_rails"
+ task t, :roles => :app do ; end
+ end
+
+end
+
+namespace :db do
+ desc 'Dumps the production database to db/production_data.sql on the remote server'
+ task :remote_db_dump, :roles => :db, :only => { :primary => true } do
+ run "cd #{deploy_to}/#{current_dir} && " +
+ "rake RAILS_ENV=#{rails_env} db:dump_sql --trace"
+ end
+
+ desc 'Downloads db/production_data.sql from the remote production environment to your local machine'
+ task :remote_db_download, :roles => :db, :only => { :primary => true } do
+ execute_on_servers(options) do |servers|
+ self.sessions[servers.first].sftp.connect do |tsftp|
+ tsftp.download!("#{deploy_to}/#{current_dir}/db/production_data.sql", "db/production_data.sql")
+ end
+ end
+ end
+
+ desc 'Cleans up data dump file'
+ task :remote_db_cleanup, :roles => :db, :only => { :primary => true } do
+ execute_on_servers(options) do |servers|
+ self.sessions[servers.first].sftp.connect do |tsftp|
+ tsftp.remove! "#{deploy_to}/#{current_dir}/db/production_data.sql"
+ end
+ end
+ end
+
+ desc 'Dumps, downloads and then cleans up the production data dump'
+ task :remote_db_runner do
+ remote_db_dump
+ remote_db_download
+ remote_db_cleanup
+ end
+end
\ No newline at end of file
diff --git a/config/environment.rb.tmpl b/config/environment.rb.tmpl
index fa48939e..5b98cccc 100644
--- a/config/environment.rb.tmpl
+++ b/config/environment.rb.tmpl
@@ -1,111 +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.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
+# 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.7'
+
+# 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
\ No newline at end of file
diff --git a/db/migrate/045_remove_user_from_taggings.rb.rb b/db/migrate/045_remove_user_from_taggings.rb.rb
new file mode 100644
index 00000000..dec44876
--- /dev/null
+++ b/db/migrate/045_remove_user_from_taggings.rb.rb
@@ -0,0 +1,9 @@
+class RemoveUserFromTaggings < ActiveRecord::Migration
+ def self.up
+ remove_column :taggings, :user_id
+ end
+
+ def self.down
+ add_column :taggings, :user_id, :integer
+ end
+end
\ No newline at end of file
diff --git a/doc/CHANGELOG b/doc/CHANGELOG
index a4076b85..ec52365c 100644
--- a/doc/CHANGELOG
+++ b/doc/CHANGELOG
@@ -1,13 +1,13 @@
= Tracks: a GTD web application, built with Ruby on Rails
-* Project homepage: http://www.rousette.org.uk/projects/
+* Project homepage: http://getontracks.org/
* GitHub: http://github.com/bsag/tracks/
-* Trac (for bug reports and feature requests): http://dev.rousette.org.uk/report/6
-* Wiki (community contributed information): http://www.rousette.org.uk/projects/wiki/
-* Forum: http://www.rousette.org.uk/projects/forums/
+* Assembla space (for bug reports and feature requests): http://www.assembla.com/spaces/tracks-tickets/tickets
+* Wiki (community contributed information): http://getontracks.org/wiki/
+* Forum: http://getontracks.org/forums/
* Mailing list: http://lists.rousette.org.uk/mailman/listinfo/tracks-discuss
* Original developer: bsag (http://www.rousette.org.uk/)
-* Contributors: http://dev.rousette.org.uk/wiki/Tracks/Contributing/Contributors
+* Contributors: http://getontracks.org/wiki/Tracks/Contributing/Contributors
* Version: 1.7
* Copyright: (cc) 2004-2008 rousette.org.uk.
* License: GNU GPL
diff --git a/doc/README_DEVELOPERS b/doc/README_DEVELOPERS
index 7366a16a..5a1306c3 100644
--- a/doc/README_DEVELOPERS
+++ b/doc/README_DEVELOPERS
@@ -1,6 +1,6 @@
1. Wiki
-There are some pointers for setting up your Tracks copy for testing at http://dev.rousette.org.uk/wiki/Tracks/Testing
+There are some pointers for setting up your Tracks copy for testing at http://www.rousette.org.uk/projects/wiki/Testing/
2. SQLITE3 FOR TESTING
diff --git a/doc/manual b/doc/manual
new file mode 160000
index 00000000..d3746adc
--- /dev/null
+++ b/doc/manual
@@ -0,0 +1 @@
+Subproject commit d3746adc39333f056f14969d40aa1f0072eb4eed
diff --git a/doc/manual.css b/doc/manual.css
deleted file mode 100644
index 246f9b51..00000000
--- a/doc/manual.css
+++ /dev/null
@@ -1,29 +0,0 @@
-body {
- width: 60%;
- font-family: Helvetica, Arial, sans-serif;
- font-size: 1em;
- margin: 20px auto;
- line-height: 1.5em;
-}
-
-a {
- color: #9E2029;
- text-decoration: none;
-}
-
-a:hover {
- color: #DE8E30;
- text-decoration: underline;
-}
-
-a:visited {
- color: #DE8E30;
-}
-
-code {
- color: #000;
- background-color: #E3E3E3;
- padding: 2px;
-}
-
-.footnote {font-size: 0.9em; vertical-align: super;}
\ No newline at end of file
diff --git a/doc/manual.html b/doc/manual.html
deleted file mode 100644
index 609cccd7..00000000
--- a/doc/manual.html
+++ /dev/null
@@ -1,281 +0,0 @@
-
-
-
-
-
Tracks 1.7 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it’s only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in doc/CHANGELOG. Full API documentation can be found at doc/app/index.html, once you have run rake appdoc
-
-
There are two methods of downloading Tracks 1.7:
-
-
-
(Recommended for most people) Download the zipped package, and unzip in your preferred location (e.g. ~/Sites for Mac OS X users).
-
If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions):
-
-
-
-
- cd ~/Sites
- git clone git://github.com/bsag/tracks.git
- cd tracks
-
-
-
-
Requirements
-
-
The Tracks interface is accessed through a web browser, so you need to run a webserver to serve the Tracks pages up to you. This isn’t as daunting as it sounds, however: Tracks ships with a built-in web server called Mongrel which you can run on your own computer to serve the Tracks application locally. If you want to be able to access Tracks from any computer connected to the Internet, then you need to install Tracks on a publicly accessible server, and you will probably be better off using a more robust server such as Apache or Lighttpd to serve the pages, particularly if it will be used by many people.
-
-
Tracks stores its data in a database, and you can either use SQLite3, MySQL or PostgreSQL. SQLite3 is the best choice for a single user (or a small number of users) on a local installation, while MySQL or PostgreSQL is better for multiple users on a remote installation.
-
-
Easy installation options
-
-
If you’d like to install Tracks on a local machine, try BitNami – it runs on Windows, Mac OS X and Linux.
-
-
If you’d like an easy way to access Tracks from any internet-connected computer, sign up for a free account at Morph eXchange. Sign up for a free account, then choose ‘Subscriptions’ to subscribe to the Tracks service.
-
-
What is included with the Tracks package
-
-
-
Tracks itself
-
Rails 2.2.2 (installed in the /vendor/rails directory, so you do not need to install Rails yourself)
-
An empty SQLite3 database, set up with the correct database schema
-
-
-
What you need to install
-
-
If you don’t want to (or can’t) use one of the all in one installations, you’ll need to install a few things, depending on your platform and your needs.
-
-
-
Ruby. Version 1.8.6 is recommended, but it is also possible to use 1.8.5, 1.8.4 and 1.8.2. Note that 1.8.3 is not compatible. If you are running Mac OS X Leopard, you already have Ruby 1.8.6 installed by default, so you have nothing to do here. You can get the source to compile yourself here for all platforms, or Windows users can use an easy installer. If you’re using a version of Mac OS X earlier than 10.5.0, it is recommended that you use the instructions here to install all the Rails dependencies, though you can skip the step to install Rails if you like.
-
RubyGems. The gems needed by Rails to interact with the database have to be compiled on the platform on which they will be run, so we cannot include them with the Tracks package, unlike some other gems. So you will need to download and install RubyGems (run ruby setup.rb after extracting the package). Note that once again, Mac OS X Leopard users get an easy life, because RubyGems and the SQLite3 gem is already installed. Once installed you can use RubyGems to install the gems you need for your database. If you are using SQLite3, run sudo gem install sqlite3-ruby, then select the appropriate package for your platform (version 1.2.1 recommended). You can use MySQL without installing a gem, but installing the gem can speed things up a bit: sudo install gem mysql. If you’re using Leopard, there are a few work-arounds necessary, which are explained on Mac OS Forge. The ruby-mysql bindings can sometimes be a bit troublesome to install, so to be honest, it’s probably not worth the bother unless you are trying to wring maximum speed out of your system. If you are using PostgreSQL, then you can install a postgres gem: gem install postgres.
-
Database. The easiest option is to use SQLite3, as the database is included in the package. All you need then is the sqlite3-ruby gem, as described in step 2, and the SQLite3 libraries and binary (see sqlite.org for downloads and installation instructions). If you want to use MySQL, download and install a package for your platform from MySQL.com. The basic steps for Postgresql should be similar to those for MySQL, but they will not be discussed further here.
-
-
-
You can find several installation howtos for specific setups here. They were contributed by various Tracks users.
-
-
Installation
-
-
This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see Upgrading to Tracks 1.7.
Unzip the package and move Tracks into the directory you want to run it from. For example, for Mac OS X users, ~/Sites is a good choice.
-
-
Decide on a database
-
-
Before you go any further, you need to decide which database you will use. See the What you need to install section for details on installing the required components for you choice of database.
-
-
-
SQLite3. All you need to do is make sure that you point Tracks to the included SQLite3 database in /db in the next step, Configure variables.
-
MySQL. Once you have MySQL installed, you need to create a database and database-user to use with Tracks 1.7. For this, you can use MySQL Administrator or go into a terminal and issue the following commands:
-
-
-
-
- mysql -uroot -p
- mysql> CREATE DATABASE tracks16;
- mysql> GRANT ALL PRIVILEGES ON tracks16.* TO yourmysqluser@localhost \
- IDENTIFIED BY 'password-goes-here' WITH GRANT OPTION;
-
-
-
-
Configure variables
-
-
-
If you downloaded Tracks 1.7 via Subversion, you need to duplicate the files database.yml.tmpl and environment.yml.tmpl and remove the *.tmpl extension from the duplicates. Similarly, duplicate /log.tmpl and remove the *.tmpl extension, then edit the files as described in steps 2 and 3.
-
Open the file /config/database.yml and edit the production: section with the details of your database. If you are using MySQL the adapter: line should read adapter: mysql, host: localhost (in the majority of cases), and your username and password should match those you assigned when you created the database. If you are using SQLite3, you should have only two lines under the production section: adapter: sqlite3 and database: db/tracks-15-blank.db. If you downloaded the zipped file, the database.yml file is already configured to use the provided SQLite3 file.
-
Open the file /config/environment.rb, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the SALT = "change-me" line (change the string “change-me” to some other string of your choice), and the time zone setting. For the time zone setting, most people will only want to change the config.time_zone option and leave the timezone to use in your database to :utc
-
If you are using Windows, you may need to check the ‘shebang’ lines (#!/usr/bin/env ruby) of the /public/dispatch.* files and all the files in the /script directory. They are set to #!/usr/bin/env ruby by default. This should work for all *nix based setups (Linux or Mac OS X), but Windows users will probably have to change it to something like #c:/ruby/bin/ruby to point to the Ruby binary on your system.
-
-
-
Populate your database with the Tracks 1.7 schema
-
-
Open a terminal and change into the root of your Tracks 1.7 directory. Enter the following command:
-
-
rake db:migrate RAILS_ENV=production
-
-
This will update your database with the required schema for Tracks 1.7. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).
-
-
Start the server
-
-
While still in the Terminal inside the Tracks 1.7 root directory, issue the following command:
-
-
script/server -e production
-
-
If all goes well, you should see some text informing you that the Mongrel server is running: ** Mongrel available at 0.0.0.0:3000. If you are already running other services on port 3000, you need to select a different port when running the server, using the -p option. You can stop the server again by the key combination Ctrl-C.
-
-
Visit Tracks in a browser
-
-
Visit http://0.0.0.0:3000/signup in a browser (or whatever URL and port was reported when you started the server in the step above) and chose a user name and password for admin user. Once logged in as admin, you can add other (ordinary level) users. If you need to access Tracks from a mobile/cellular phone browser, visit http://yourdomain.com/mobile/. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
-
-
Customise Tracks
-
-
Once logged in, add some Contexts and Projects, and then go ahead and add your actions. You might also want to visit the Preferences page to edit various settings to your liking. Have fun!
-
-
Upgrading to Tracks 1.7
-
-
Upgrading from Tracks 1.6
-
-
You will need to upgrade your config/environment.rb with the new content from config/environment.rb.tmpl included in 1.7, as the format of this file has changed a bit between 1.6 and 1.7. Also, there were some database changes in Tracks 1.7, so you need to migrate to them.
-
-
-
Back up your existing database and installation of Tracks
There are no changes to the database between 1.5 and 1.6, but you will need to upgrade your config/environment.rb with the new content from config/environment.rb.tmpl included in 1.6, as the format of this file has changed a great deal between 1.5 and 1.6.
-
-
-
Back up your existing database and installation of Tracks
It’s very important that you back up your database before you start the upgrade process. It’s always possible for things to go wrong with the database update, and you don’t want to lose any data. If you are using SQLite3 and you are leaving your old Tracks directory in place, then you don’t need to do anything. However, there is no harm in taking extra precautions and copying your database from /db to a safe location as an extra backup, or making a dump of the schema and contents. You will never regret making too many backups! If you are using MySQL, make a SQL dump of your database, replacing the terms in square brackets with the correct information for your setup:
Rename your old Tracks installation (e.g. to ‘tracks-old’) so that you can install Tracks 1.7 along side it.
-
-
Install Tracks 1.7
-
-
There are two methods of downloading Tracks 1.7:
-
-
-
(Recommended for most people) Download the zipped package, and unzip in your preferred location (e.g. ~/Sites for Mac OS X users).
-
If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions):
-3.
-
-
-
-
- cd ~/Sites
- git clone git://github.com/bsag/tracks.git
- cd tracks
-
-
-
-
Copy over old configuration files
-
-
There are a few files you need to copy over from your old installation. If you copy them over rather than moving them, you can still run your old version of Tracks if anything goes awry with the installation process.
-
-
-
Copy /config/database.yml from your old Tracks directory to the same location in the new one. Double check that the information there is still correct.
-
Duplicate /config/environment.rb.tmpl in the Tracks 1.7 directory, and rename the file to environment.rb. Open the file and alter the line SALT = "change-me" so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location. If you have made any other customisations to environment.rb in the past, copy those over, but the contents of the file have slightly changed since 1.5, so check it carefully.
-
Copy your /log directory over from your old installation to the root of the new one, or just rename /log.tmpl to log to start afresh.
-
If you are using SQLite3, copy your database from /db in your old Tracks directory to the same location in the new one.
-
If you are using Windows, you may need to check the ‘shebang’ lines (#!/usr/bin/env ruby)1 of the /public/dispatch.* files and all the files in the /script directory. They are set to #!/usr/bin/env ruby by default. Check the format of those lines in your old installation, and change the new ones as necessary.
-
-
-
Update your old database to the new format
-
-
In a terminal, change directories so that you are inside the Tracks 1.7 directory. Then issue the command to update your Tracks 1.6 database to the format required for Tracks 1.7:
-
-
rake db:migrate RAILS_ENV=production
-
-
Watch the output carefully for errors, but it should report at the end of the process that everything worked OK. If you do get errors, you’ll have to fix them before you proceed any further. Running rake with the --trace option can help to track down the problem.
-
-
Start the server
-
-
If you’re still in the Tracks 1.7 root directory in a terminal, enter the following command to start up Tracks in production mode:
-
-
script/server -e production
-
-
Visit the URL indicated by the output (e.g. ** Mongrel available at 0.0.0.0:3000
-) in a browser, and with any luck, you should be able to log in and find all your actions as you left them! If you need to access Tracks from a mobile/cellular phone browser, visit http://yourdomain.com/mobile/. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
-
-
Clean up your old installation
-
-
Once you’re certain that your new Tracks 1.7 installation is working perfectly, you can delete your old Tracks directory.
-
-
Upgrading from versions prior to 1.043
-
-
The best option for versions prior to 1.043 is to follow the instructions below to upgrade to version 1.043, then use the instructions above to upgrade from version 1.043.
-
-
-
For safety, rename your current Tracks directory to ‘tracks-old’ or something similar.
-
Before you do anything else, BACK UP YOUR DATABASE (tables and content) and keep the SQL dumps somewhere safe so that you can recreate the old database if necesary.
-
Download a copy of Tracks 1.043 and unzip alongside your ‘tracks-old’ directory.
-
Open the file config/environment.rb and look at the last line which should read: SALT = "change-me". Change the word change-me to something else of your choosing. This string will be used as a ‘salt’ to encrypt your password and make it a bit more secure. Also look at the timezone setting at the bottom. You can leave it commented out if your server is in the same time zone as you, but you may need to adjust it if your server is in a different time zone.
-
In database.yml insert your old database name, user and password under the ‘development’ section. If you are using SQLite3 rather than MySQL or PostgreSQL, you need only the database name, and to change the ‘adapter’ line to ‘sqlite3’. You also need to copy (NOT MOVE!), your SQLite3 database from your tracks-old db directory to your new tracks db directory
-
Run the command rake extract_fixtures inside the Tracks directory. This will populate the db/exported_fixtures directory with *.yml files corresponding to the contexts, projects and todos table from the contents of your old database.
-
Open db/exported_fixtures/todos.yml and search for the lines starting created: and replace with created_at:. If you are using SQLite3, you also need to change the following: done: "0" with done: "f" and done: "1" with done: "t". You need to replace the similar ‘done’ lines in projects.yml, and in contexts.yml replace hide: "0" with hide: "f" and hide: "1" with hide: "t".
-
Create a new MySQL database (named tracks1043, for example). In database.yml insert this new database name, user and password under the ‘development’ and ‘production’ sections. If you are using SQLite3, insert a new name for a database to hold your Tracks 1.043 data.
-
Run the command rake db_schema_import inside the Tracks directory. This should import the upgraded schema for 1.043 into your new database.
-
Run the command rake load_exported_fixtures which will import the contents of your old database from the fixtures files in db/exported_fixtures.
-
If you are using Windows, you may need to check the ‘shebang’ lines (#!/usr/bin/env ruby)2 of the /public/dispatch.* files and all the files in the /script directory. They are set to #!/usr/bin/env ruby by default. Check the format of those lines in your old installation, and change the new ones as necessary.
-
Try starting up the server with script/server to make sure that all your data has migrated successfully. If all is well, follow the instructions above to upgrade from version 1.043 to Tracks 1.6. If you need to access Tracks from a mobile/cellular phone browser, visit http://yourdomain.com/mobile/. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
-
-
-
-
-
-
-
The env binary helps to locate other binaries, regardless of their location. If you don’t have env installed, you’ll need to change this line to point to the location of your Ruby binary. ↩
-
-
The env binary helps to locate other binaries, regardless of their location. If you don’t have env installed, you’ll need to change this line to point to the location of your Ruby binary. ↩
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/manual.markdown b/doc/manual.markdown
deleted file mode 100644
index b1c9327c..00000000
--- a/doc/manual.markdown
+++ /dev/null
@@ -1,235 +0,0 @@
-Title: Tracks 1.7 Manual
-Author: Tracks Development Team
-Date: 2008-12-14
-Revision: $Id: manual.markdown 2008-12-14 11:50:00Z bsag $
-Version: 1.7
-Copyright: 2008 rousette.org.uk
- This work is licensed under a Creative Commons License.
- http://creativecommons.org/licenses/by-nc-sa/3.0/
-XMP: CCAttributionShareAlike
-Format: complete
-Base Header Level: 2
-LaTeX XSLT: memoir-twosided-manual.xslt
-CSS: manual.css
-
-
-
-# Installing Tracks 1.7 #
-
-## Introduction ##
-
-Tracks 1.7 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it's only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in `doc/CHANGELOG`. Full API documentation can be found at `doc/app/index.html`, once you have run `rake appdoc`
-
-There are two methods of downloading Tracks 1.7:
-
-1. (Recommended for most people) Download the [zipped package](http://www.rousette.org.uk/projects/files/tracks-current.zip), and unzip in your preferred location (e.g. `~/Sites` for Mac OS X users).
-2. If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions):
-
-
-
- cd ~/Sites
- git clone git://github.com/bsag/tracks.git
- cd tracks
-
-
-
-## Requirements ##
-
-The Tracks interface is accessed through a web browser, so you need to run a webserver to serve the Tracks pages up to you. This isn't as daunting as it sounds, however: Tracks ships with a built-in web server called Mongrel which you can run on your own computer to serve the Tracks application locally. If you want to be able to access Tracks from any computer connected to the Internet, then you need to install Tracks on a publicly accessible server, and you will probably be better off using a more robust server such as [Apache](http://www.apache.org/) or [Lighttpd](http://www.lighttpd.net/) to serve the pages, particularly if it will be used by many people.
-
-Tracks stores its data in a database, and you can either use SQLite3, MySQL or PostgreSQL. SQLite3 is the best choice for a single user (or a small number of users) on a local installation, while MySQL or PostgreSQL is better for multiple users on a remote installation.
-
-### Easy installation options ###
-
-If you'd like to install Tracks on a local machine, try [BitNami](http://bitnami.org/stack/tracks) -- it runs on Windows, Mac OS X and Linux.
-
-If you'd like an easy way to access Tracks from any internet-connected computer, sign up for a free account at [Morph eXchange](http://www.morphexchange.com/). Sign up for a free account, then choose 'Subscriptions' to subscribe to the Tracks service.
-
-### What is included with the Tracks package ###
-
-1. Tracks itself
-2. Rails 2.2.2 (installed in the `/vendor/rails` directory, so you do not need to install Rails yourself)
-3. An empty SQLite3 database, set up with the correct database schema
-
-### What you need to install [whatyouneed] ###
-
-If you don't want to (or can't) use one of the all in one installations, you'll need to install a few things, depending on your platform and your needs.
-
-1. **Ruby**. Version 1.8.6 is recommended, but it is also possible to use 1.8.5, 1.8.4 and 1.8.2. Note that 1.8.3 is not compatible. If you are running Mac OS X Leopard, you already have Ruby 1.8.6 installed by default, so you have nothing to do here. You can get the source to compile yourself [here](http://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.gz) for all platforms, or Windows users can use an easy [installer](http://rubyforge.org/frs/?group_id=167). If you're using a version of Mac OS X earlier than 10.5.0, it is recommended that you use the [instructions here](http://hivelogic.com/narrative/articles/ruby-rails-mongrel-mysql-osx) to install all the Rails dependencies, though you can skip the step to install Rails if you like.
-2. **RubyGems**. The gems needed by Rails to interact with the database have to be compiled on the platform on which they will be run, so we cannot include them with the Tracks package, unlike some other gems. So you will need to [download](http://rubyforge.org/frs/?group_id=126) and install RubyGems (run `ruby setup.rb` after extracting the package). Note that once again, Mac OS X Leopard users get an easy life, because RubyGems and the SQLite3 gem is already installed. Once installed you can use RubyGems to install the gems you need for your database. If you are using SQLite3, run `sudo gem install sqlite3-ruby`, then select the appropriate package for your platform (version 1.2.1 recommended). You can use MySQL without installing a gem, but installing the gem can speed things up a bit: `sudo install gem mysql`. If you're using Leopard, there are a few work-arounds necessary, which are explained on [Mac OS Forge](http://trac.macosforge.org/projects/ruby/wiki/Troubleshooting#IcannotbuildrubymysqlonLeopardwithmysql.combinaries). The ruby-mysql bindings can sometimes be a bit troublesome to install, so to be honest, it's probably not worth the bother unless you are trying to wring maximum speed out of your system. If you are using PostgreSQL, then you can install a postgres gem: `gem install postgres`.
-3. **Database**. The easiest option is to use SQLite3, as the database is included in the package. All you need then is the `sqlite3-ruby` gem, as described in step 2, and the SQLite3 libraries and binary (see [sqlite.org](http://sqlite.org/download.html) for downloads and installation instructions). If you want to use MySQL, download and install a package for your platform from [MySQL.com](http://dev.mysql.com/downloads/mysql/5.0.html). The basic steps for Postgresql should be similar to those for MySQL, but they will not be discussed further here.
-
-You can find several installation howtos for specific setups [here](http://dev.rousette.org.uk/wiki/Tracks/Install). They were contributed by various Tracks users.
-
-## Installation ##
-
-This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see [Upgrading to Tracks 1.7][upgrading].
-
-1. [Unzip tracks][unzip_install] and install in a directory
-2. Decide on a [database][database_install] to use
- 1. SQLite3 - change database.yml to point to SQLite3 database. Make sure you add the complete path to the database
- 2. MySQL - create new MySQL db and grant all privileges
-3. [Configure some variables][config_install]
-4. Populate the database with the [Tracks 1.7 schema][rake_install]
-5. [Start the server][startserver_install]
-6. [Visit Tracks in a browser][signup_install]
-7. [Customise Tracks][customise_install]
-
-### Unzip Tracks and install [unzip_install] ###
-
-Unzip the package and move Tracks into the directory you want to run it from. For example, for Mac OS X users, `~/Sites` is a good choice.
-
-### Decide on a database [database_install] ###
-
-Before you go any further, you need to decide which database you will use. See the [What you need to install][whatyouneed] section for details on installing the required components for you choice of database.
-
-1. **SQLite3**. All you need to do is make sure that you point Tracks to the included SQLite3 database in `/db` in the next step, [Configure variables][config_install].
-2. **MySQL**. Once you have MySQL installed, you need to create a database and database-user to use with Tracks 1.7. For this, you can use MySQL Administrator or go into a terminal and issue the following commands:
-
-
- mysql -uroot -p
- mysql> CREATE DATABASE tracks16;
- mysql> GRANT ALL PRIVILEGES ON tracks16.* TO yourmysqluser@localhost \
- IDENTIFIED BY 'password-goes-here' WITH GRANT OPTION;
-
-
-
-### Configure variables [config_install] ###
-
-1. If you downloaded Tracks 1.7 via Subversion, you need to duplicate the files `database.yml.tmpl` and `environment.yml.tmpl` and remove the `*.tmpl` extension from the duplicates. Similarly, duplicate `/log.tmpl` and remove the `*.tmpl` extension, then edit the files as described in steps 2 and 3.
-2. Open the file `/config/database.yml` and edit the `production:` section with the details of your database. If you are using MySQL the `adapter:` line should read `adapter: mysql`, `host: localhost` (in the majority of cases), and your username and password should match those you assigned when you created the database. If you are using SQLite3, you should have only two lines under the production section: `adapter: sqlite3` and `database: db/tracks-15-blank.db`. If you downloaded the zipped file, the database.yml file is already configured to use the provided SQLite3 file.
-3. Open the file `/config/environment.rb`, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the `SALT = "change-me"` line (change the string "change-me" to some other string of your choice), and the time zone setting. For the time zone setting, most people will only want to change the config.time_zone option and leave the timezone to use in your database to :utc
-4. If you are using Windows, you may need to check the 'shebang' lines (`#!/usr/bin/env ruby`) of the `/public/dispatch.*` files and all the files in the `/script` directory. They are set to `#!/usr/bin/env ruby` by default. This should work for all *nix based setups (Linux or Mac OS X), but Windows users will probably have to change it to something like `#c:/ruby/bin/ruby` to point to the Ruby binary on your system.
-
-### Populate your database with the Tracks 1.7 schema [rake_install] ###
-
-Open a terminal and change into the root of your Tracks 1.7 directory. Enter the following command:
-
-`rake db:migrate RAILS_ENV=production`
-
-This will update your database with the required schema for Tracks 1.7. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).
-
-### Start the server [startserver_install] ###
-
-While still in the Terminal inside the Tracks 1.7 root directory, issue the following command:
-
-`script/server -e production`
-
-If all goes well, you should see some text informing you that the Mongrel server is running: `** Mongrel available at 0.0.0.0:3000`. If you are already running other services on port 3000, you need to select a different port when running the server, using the `-p` option. You can stop the server again by the key combination Ctrl-C.
-
-### Visit Tracks in a browser [signup_install] ###
-
-Visit `http://0.0.0.0:3000/signup` in a browser (or whatever URL and port was reported when you started the server in the step above) and chose a user name and password for admin user. Once logged in as admin, you can add other (ordinary level) users. If you need to access Tracks from a mobile/cellular phone browser, visit `http://yourdomain.com/mobile/`. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
-
-### Customise Tracks [customise_install] ###
-
-Once logged in, add some Contexts and Projects, and then go ahead and add your actions. You might also want to visit the Preferences page to edit various settings to your liking. Have fun!
-
-# Upgrading to Tracks 1.7 [upgrading] #
-
-## Upgrading from Tracks 1.6 [upgrading_1.6] ##
-
-You will need to upgrade your `config/environment.rb` with the new content from `config/environment.rb.tmpl` included in 1.7, as the format of this file has changed a bit between 1.6 and 1.7. Also, there were some database changes in Tracks 1.7, so you need to migrate to them.
-
-1. [Back up][backup_upgrade] your existing database and installation of Tracks
-2. [Install Tracks 1.7][install_upgrade] in a new directory
-3. [Copy over][config_upgrade] a few configuration files from your Tracks 1.6 directory. If using SQLite3, copy the old database into the new Tracks 1.7 directory.
-4. Rebuild your environment.rb from the updated environment.rb.tmpl
-5. Run `rake db:migrate RAILS_ENV=production` to [update your old database][rake_upgrade] to the new schema -- you did back up your database didn't you?
-6. Run `script/server` inside your Tracks 1.7 directory to [start up Tracks 1.7][startserver_upgrade].
-7. Once you are happy that everything is working well, [delete your old Tracks directory][cleanup_upgrade].
-
-## Upgrading from Tracks 1.5 [upgrading_1.5] ##
-
-There are no changes to the database between 1.5 and 1.6, but you will need to upgrade your `config/environment.rb` with the new content from `config/environment.rb.tmpl` included in 1.6, as the format of this file has changed a great deal between 1.5 and 1.6.
-
-1. [Back up][backup_upgrade] your existing database and installation of Tracks
-2. [Install Tracks 1.6][install_upgrade] in a new directory
-3. [Copy over][config_upgrade] a few configuration files from your Tracks 1.043 directory. If using SQLite3, copy the old database into the new Tracks 1.6 directory
-4. Rebuild your environment.rb from the updated environment.rb.tmpl
-5. Run `script/server` inside your Tracks 1.6 directory to [start up Tracks 1.6][startserver_upgrade].
-6. Once you are happy that everything is working well, [delete your old Tracks directory][cleanup_upgrade].
-
-## Upgrading from Tracks 1.043 [upgrading_1043] ##
-
-This should be a relatively straightforward, and involves the following main steps:
-
-1. [Back up][backup_upgrade] your existing database and installation of Tracks
-2. [Install Tracks 1.6][install_upgrade] in a new directory
-3. [Copy over][config_upgrade] a few configuration files from your Tracks 1.043 directory. If using SQLite3, copy the old database into the new Tracks 1.6 directory
-5. Run `rake db:migrate RAILS_ENV=production` to [update your old database][rake_upgrade] to the new schema -- you did back up your database didn't you?
-6. Run `script/server` inside your Tracks 1.6 directory to [start up Tracks 1.6][startserver_upgrade].
-7. Once you are happy that everything is working well, [delete your old Tracks directory][cleanup_upgrade].
-
-### Backing up [backup_upgrade] ###
-
-It's very important that you **back up your database** before you start the upgrade process. It's always possible for things to go wrong with the database update, and you don't want to lose any data. If you are using SQLite3 and you are leaving your old Tracks directory in place, then you don't need to do anything. However, there is no harm in taking extra precautions and copying your database from `/db` to a safe location as an extra backup, or making a dump of the schema and contents. You will never regret making too many backups! If you are using MySQL, make a SQL dump of your database, replacing the terms in square brackets with the correct information for your setup:
-
-`mysqldump –-user [user name] –-password=[password] [database name] > [dump file]`
-
-Rename your old Tracks installation (e.g. to 'tracks-old') so that you can install Tracks 1.7 along side it.
-
-### Install Tracks 1.7 [install_upgrade] ###
-
-There are two methods of downloading Tracks 1.7:
-
-1. (Recommended for most people) Download the [zipped package](http://www.rousette.org.uk/projects/files/tracks-current.zip), and unzip in your preferred location (e.g. `~/Sites` for Mac OS X users).
-2. If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions):
-3.
-
-
- cd ~/Sites
- git clone git://github.com/bsag/tracks.git
- cd tracks
-
-
-
-### Copy over old configuration files [config_upgrade] ###
-
-There are a few files you need to copy over from your old installation. If you copy them over rather than moving them, you can still run your old version of Tracks if anything goes awry with the installation process.
-
-1. Copy `/config/database.yml` from your old Tracks directory to the same location in the new one. Double check that the information there is still correct.
-2. Duplicate `/config/environment.rb.tmpl` in the Tracks 1.7 directory, and rename the file to `environment.rb`. Open the file and alter the line `SALT = "change-me"` so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location. If you have made any other customisations to `environment.rb` in the past, copy those over, but the contents of the file have slightly changed since 1.5, so check it carefully.
-3. Copy your `/log` directory over from your old installation to the root of the new one, or just rename `/log.tmpl` to `log` to start afresh.
-4. If you are using SQLite3, copy your database from `/db` in your old Tracks directory to the same location in the new one.
-5. If you are using Windows, you may need to check the 'shebang' lines (`#!/usr/bin/env ruby`)[^env] of the `/public/dispatch.*` files and all the files in the `/script` directory. They are set to `#!/usr/bin/env ruby` by default. Check the format of those lines in your old installation, and change the new ones as necessary.
-
-### Update your old database to the new format [rake_upgrade] ###
-
-In a terminal, change directories so that you are inside the Tracks 1.7 directory. Then issue the command to update your Tracks 1.6 database to the format required for Tracks 1.7:
-
-`rake db:migrate RAILS_ENV=production`
-
-Watch the output carefully for errors, but it should report at the end of the process that everything worked OK. If you do get errors, you'll have to fix them before you proceed any further. Running rake with the `--trace` option can help to track down the problem.
-
-### Start the server [startserver_upgrade] ###
-
-If you're still in the Tracks 1.7 root directory in a terminal, enter the following command to start up Tracks in production mode:
-
-`script/server -e production`
-
-Visit the URL indicated by the output (e.g. `** Mongrel available at 0.0.0.0:3000`
-) in a browser, and with any luck, you should be able to log in and find all your actions as you left them! If you need to access Tracks from a mobile/cellular phone browser, visit `http://yourdomain.com/mobile/`. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
-
-### Clean up your old installation [cleanup_upgrade] ###
-
-Once you're certain that your new Tracks 1.7 installation is working perfectly, you can delete your old Tracks directory.
-
-[^env]: The `env` binary helps to locate other binaries, regardless of their location. If you don't have `env` installed, you'll need to change this line to point to the location of your Ruby binary.
-
-## Upgrading from versions prior to 1.043 ##
-
-The best option for versions prior to 1.043 is to follow the instructions below to upgrade to version 1.043, then use the instructions above to [upgrade from version 1.043][upgrading_1043].
-
-1. For safety, rename your current Tracks directory to 'tracks-old' or something similar.
-2. Before you do anything else, **BACK UP YOUR DATABASE** (tables and content) and keep the SQL dumps somewhere safe so that you can recreate the old database if necesary.
-3. Download a copy of Tracks 1.043 and unzip alongside your 'tracks-old' directory.
-4. Open the file `config/environment.rb` and look at the last line which should read: `SALT = "change-me"`. Change the word change-me to something else of your choosing. This string will be used as a 'salt' to encrypt your password and make it a bit more secure. Also look at the timezone setting at the bottom. You can leave it commented out if your server is in the same time zone as you, but you may need to adjust it if your server is in a different time zone.
-5. In `database.yml` insert your old database name, user and password under the 'development' section. If you are using SQLite3 rather than MySQL or PostgreSQL, you need only the database name, and to change the 'adapter' line to 'sqlite3'. You also need to copy (NOT MOVE!), your SQLite3 database from your tracks-old `db` directory to your new tracks `db` directory
-6. Run the command `rake extract_fixtures` inside the Tracks directory. This will populate the `db/exported_fixtures` directory with `*.yml` files corresponding to the contexts, projects and todos table from the contents of your old database.
-7. Open `db/exported_fixtures/todos.yml` and search for the lines starting `created:` and replace with `created_at:`. If you are using SQLite3, you also need to change the following: `done: "0"` with `done: "f"` and `done: "1"` with `done: "t"`. You need to replace the similar 'done' lines in `projects.yml`, and in `contexts.yml` replace `hide: "0"` with `hide: "f"` and `hide: "1"` with `hide: "t"`.
-8. Create a new MySQL database (named tracks1043, for example). In `database.yml` insert this new database name, user and password under the 'development' and 'production' sections. If you are using SQLite3, insert a new name for a database to hold your Tracks 1.043 data.
-9. Run the command `rake db_schema_import` inside the Tracks directory. This should import the upgraded schema for 1.043 into your new database.
-10. Run the command `rake load_exported_fixtures` which will import the contents of your old database from the fixtures files in `db/exported_fixtures`.
-11. If you are using Windows, you may need to check the 'shebang' lines (`#!/usr/bin/env ruby`)[^env] of the `/public/dispatch.*` files and all the files in the `/script` directory. They are set to `#!/usr/bin/env ruby` by default. Check the format of those lines in your old installation, and change the new ones as necessary.
-12. Try starting up the server with `script/server` to make sure that all your data has migrated successfully. If all is well, follow the instructions above to [upgrade from version 1.043][upgrading_1043] to Tracks 1.6. If you need to access Tracks from a mobile/cellular phone browser, visit `http://yourdomain.com/mobile/`. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
\ No newline at end of file
diff --git a/doc/manual.pdf b/doc/manual.pdf
deleted file mode 100644
index d342967b..00000000
Binary files a/doc/manual.pdf and /dev/null differ
diff --git a/doc/manual.tex b/doc/manual.tex
deleted file mode 100644
index c2602643..00000000
--- a/doc/manual.tex
+++ /dev/null
@@ -1,668 +0,0 @@
-\documentclass[10pt,twoside]{memoir}
-\usepackage{layouts}[2001/04/29]
-
-\usepackage{palatino}
-\usepackage{color,calc}
-\newsavebox{\ChpNumBox}
-\definecolor{ChapBlue}{rgb}{0.00,0.65,0.65}
-\makeatletter
-\newcommand*{\thickhrulefill}{%
-\leavevmode\leaders\hrule height 1\p@ \hfill \kern \z@}
-\newcommand*\BuildChpNum[2]{%
-\begin{tabular}[t]{@{}c@{}}
-\makebox[0pt][c]{#1\strut} \\[.5ex]
-\colorbox{ChapBlue}{%
-\rule[-10em]{0pt}{0pt}%
-\rule{1ex}{0pt}\color{black}#2\strut
-\rule{1ex}{0pt}}%
-\end{tabular}}
-\makechapterstyle{BlueBox}{%
-\renewcommand{\chapnamefont}{\large\scshape}
-\renewcommand{\chapnumfont}{\Huge\bfseries}
-\renewcommand{\chaptitlefont}{\raggedright\Huge\bfseries}
-\setlength{\beforechapskip}{20pt}
-\setlength{\midchapskip}{26pt}
-\setlength{\afterchapskip}{40pt}
-\renewcommand{\printchaptername}{}
-\renewcommand{\chapternamenum}{}
-\renewcommand{\printchapternum}{%
-\sbox{\ChpNumBox}{%
-\BuildChpNum{\chapnamefont\@chapapp}%
-{\chapnumfont\thechapter}}}
-\renewcommand{\printchapternonum}{%
-\sbox{\ChpNumBox}{%
-\BuildChpNum{\chapnamefont\vphantom{\@chapapp}}%
-{\chapnumfont\hphantom{\thechapter}}}}
-\renewcommand{\afterchapternum}{}
-\renewcommand{\printchaptertitle}[1]{%
-\usebox{\ChpNumBox}\hfill
-\parbox[t]{\hsize-\wd\ChpNumBox-1em}{%
-\vspace{\midchapskip}%
-\thickhrulefill\par
-\chaptitlefont ##1\par}}%
-}
-\chapterstyle{BlueBox}
-
-\setsecheadstyle{\sffamily\bfseries\Large}
-\setsubsecheadstyle{\sffamily\bfseries\normal}
-
-\makepagestyle{myruledpagestyle}
-\makeevenhead{myruledpagestyle}{\thepage}{}{\leftmark}
-\makeoddhead{myruledpagestyle}{\rightmark}{}{\thepage}
-\makeatletter
-\makepsmarks{myruledpagestyle}{
- \def\chaptermark##1{\markboth{%
- \ifnum \value{secnumdepth} > -1
- \if@mainmatter
- \chaptername\ \thechapter\ --- %
- \fi
- \fi
- ##1}{}}
- \def\sectionmark##1{\markright{%
- \ifnum \value{secnumdepth} > 0
- \thesection. \ %
- \fi
- ##1}}
-}
-\makeatother
-
-\makerunningwidth{myruledpagestyle}{1.1\textwidth}
-\makeheadposition{myruledpagestyle}{flushright}{flushleft}{flushright}{flushleft}
-\makeheadrule{myruledpagestyle}{1.1\textwidth}{\normalrulethickness}
-
-\makeglossary
-\makeindex
-
-\def\mychapterstyle{BlueBox}
-\def\mypagestyle{myruledpagestyle}
-\def\revision{}
-%%% need more space for ToC page numbers
-\setpnumwidth{2.55em}
-\setrmarg{3.55em}
-
-%%% need more space for ToC section numbers
-\cftsetindents{part}{0em}{3em}
-\cftsetindents{chapter}{0em}{3em}
-\cftsetindents{section}{3em}{3em}
-\cftsetindents{subsection}{4.5em}{3.9em}
-\cftsetindents{subsubsection}{8.4em}{4.8em}
-\cftsetindents{paragraph}{10.7em}{5.7em}
-\cftsetindents{subparagraph}{12.7em}{6.7em}
-
-%%% need more space for LoF numbers
-\cftsetindents{figure}{0em}{3.0em}
-
-%%% and do the same for the LoT
-\cftsetindents{table}{0em}{3.0em}
-
-%%% set up the page layout
-\settrimmedsize{\stockheight}{\stockwidth}{*} % Use entire page
-\settrims{0pt}{0pt}
-
-\setlrmarginsandblock{1.5in}{1.5in}{*}
-\setulmarginsandblock{1.5in}{1.5in}{*}
-
-\setmarginnotes{17pt}{51pt}{\onelineskip}
-\setheadfoot{\onelineskip}{2\onelineskip}
-\setheaderspaces{*}{2\onelineskip}{*}
-\checkandfixthelayout
-
-\usepackage{fancyvrb} % Allow \verbatim et al. in footnotes
-\usepackage{graphicx} % To include graphics in pdf's (jpg, gif, png, etc)
-\usepackage{booktabs} % Better tables
-\usepackage{tabulary} % Support longer table cells
-\usepackage[utf8]{inputenc} % For UTF-8 support
-%\usepackage{xcolor} % Allow for color (annotations)
-
-%\geometry{landscape} % Activate for rotated page geometry
-
-%\usepackage[parfill]{parskip} % Activate to begin paragraphs with an empty
- % line rather than an indent
-
-
-\def\myauthor{Author} % In case these were not included in metadata
-\def\mytitle{Title}
-\def\mykeywords{}
-\def\mybibliostyle{plain}
-\def\bibliocommand{}
-
-\VerbatimFootnotes
-\def\myauthor{Tracks Development Team}
-\def\baseheaderlevel{2}
-\def\mycopyright{2008 rousette.org.uk \\ This work is licensed under a Creative Commons License. \\ http://creativecommons.org/licenses/by-nc-sa/3.0/}
-\date{2008-12-14}
-\def\format{complete}
-\def\latexxslt{memoir-twosided-manual.xslt}
-\def\revision{Revision: \$Id: manual.markdown 2008-12-14 11:50:00Z bsag \$}
-\def\mytitle{Tracks 1.7 Manual}
-\def\version{1.7}
-\usepackage{xmpincl}
-\includexmp{CCAttributionShareAlike}
-
-
-%
-% PDF Stuff
-%
-
-%\ifpdf % Removed for XeLaTeX compatibility
-% \pdfoutput=1 % Removed for XeLaTeX compatibility
-\usepackage[
- plainpages=false,
- pdfpagelabels,
- pdftitle={\mytitle},
- pagebackref,
- pdfauthor={\myauthor},
- pdfkeywords={\mykeywords},
- colorlinks=true,
- urlcolor=blue,
- linkcolor=red
- ]{hyperref}
- \usepackage{memhfixc}
-%\fi % Removed for XeLaTeX compatibility
-
-
-%
-% Title Information
-%
-
-\ifx\subtitle\undefined
-\else
- \addtodef{\mytitle}{}{ \\ \subtitle}
-\fi
-
-\ifx\affiliation\undefined
-\else
- \addtodef{\myauthor}{}{ \\ \affiliation}
-\fi
-
-\ifx\address\undefined
-\else
- \addtodef{\myauthor}{}{ \\ \address}
-\fi
-
-\ifx\phone\undefined
-\else
- \addtodef{\myauthor}{}{ \\ \phone}
-\fi
-
-\ifx\email\undefined
-\else
- \addtodef{\myauthor}{}{ \\ \email}
-\fi
-
-\ifx\web\undefined
- \else
- \addtodef{\myauthor}{}{ \\ \web}
-\fi
-
-\title{\mytitle}
-\author{\myauthor}
-
-\maxsecnumdepth{subsection}
-\setsecnumdepth{subsection}
-
-\begin{document}
-
-\chapterstyle{\mychapterstyle}
-\pagestyle{\mypagestyle}
-
-%
-% Front Matter
-%
-
-\frontmatter
-
-
-% Title Page
-
-\maketitle
-\clearpage
-
-% Copyright Page
-\vspace*{\fill}
-
-\setlength{\parindent}{0pt}
-
-\ifx\mycopyright\undefined
-\else
- \textcopyright{} \mycopyright
-\fi
-
-\revision
-
-\begin{center}
-\framebox{ \parbox[t]{1.5in}{\centering Formatted for \LaTeX \\
- by MultiMarkdown}}
-\end{center}
-
-\setlength{\parindent}{1em}
-\clearpage
-
-% Table of Contents
-\tableofcontents
-%\listoffigures % activate to include a List of Figures
-%\listoftables % activate to include a List of Tables
-
-
-%
-% Main Content
-%
-
-
-% Layout settings
-\setlength{\parindent}{0pt}
-\setlength{\parskip}{\baselineskip/2}
-
-\mainmatter
-\chapter{Installing Tracks 1.7}
-\label{installingtracks1.7}
-
-\section{Introduction}
-\label{introduction}
-
-Tracks 1.7 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it's only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in \texttt{doc/CHANGELOG}. Full API documentation can be found at \texttt{doc/app/index.html}, once you have run \texttt{rake appdoc}
-
-
-There are two methods of downloading Tracks 1.7:
-
-
-\begin{enumerate}
-
-
-\item (Recommended for most people) Download the \href{http://www.rousette.org.uk/projects/files/tracks-current.zip}{zipped package}, and unzip in your preferred location (e.g.\ \texttt{\ensuremath{\sim}/Sites} for Mac OS X users).
-
-\item If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions):
-\end{enumerate}
-
-\begin{adjustwidth}{2.5em}{2.5em}
-\begin{verbatim}
-
-
- cd ~/Sites
- git clone git://github.com/bsag/tracks.git
- cd tracks
-
-
-\end{verbatim}
-\end{adjustwidth}
-
-\section{Requirements}
-\label{requirements}
-
-The Tracks interface is accessed through a web browser, so you need to run a webserver to serve the Tracks pages up to you. This isn't as daunting as it sounds, however: Tracks ships with a built-in web server called Mongrel which you can run on your own computer to serve the Tracks application locally. If you want to be able to access Tracks from any computer connected to the Internet, then you need to install Tracks on a publicly accessible server, and you will probably be better off using a more robust server such as \href{http://www.apache.org/}{Apache} or \href{http://www.lighttpd.net/}{Lighttpd} to serve the pages, particularly if it will be used by many people.
-
-
-Tracks stores its data in a database, and you can either use SQLite3, MySQL or PostgreSQL. SQLite3 is the best choice for a single user (or a small number of users) on a local installation, while MySQL or PostgreSQL is better for multiple users on a remote installation.
-
-
-\subsection{Easy installation options}
-\label{easyinstallationoptions}
-
-If you'd like to install Tracks on a local machine, try \href{http://bitnami.org/stack/tracks}{BitNami} -- it runs on Windows, Mac OS X and Linux.
-
-
-If you'd like an easy way to access Tracks from any internet-connected computer, sign up for a free account at \href{http://www.morphexchange.com/}{Morph eXchange}. Sign up for a free account, then choose `Subscriptions' to subscribe to the Tracks service.
-
-
-\subsection{What is included with the Tracks package}
-\label{whatisincludedwiththetrackspackage}
-
-\begin{enumerate}
-
-
-\item Tracks itself
-
-\item Rails 2.2.2 (installed in the \texttt{/vendor/rails} directory, so you do not need to install Rails yourself)
-
-\item An empty SQLite3 database, set up with the correct database schema
-\end{enumerate}
-
-\subsection{What you need to install}
-\label{whatyouneed}
-
-If you don't want to (or can't) use one of the all in one installations, you'll need to install a few things, depending on your platform and your needs.
-
-
-\begin{enumerate}
-
-
-\item \textbf{Ruby}. Version 1.8.6 is recommended, but it is also possible to use 1.8.5, 1.8.4 and 1.8.2. Note that 1.8.3 is not compatible. If you are running Mac OS X Leopard, you already have Ruby 1.8.6 installed by default, so you have nothing to do here. You can get the source to compile yourself \href{http://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.gz}{here} for all platforms, or Windows users can use an easy \href{http://rubyforge.org/frs/?group_id=167}{installer}. If you're using a version of Mac OS X earlier than 10.5.0, it is recommended that you use the \href{http://hivelogic.com/narrative/articles/ruby-rails-mongrel-mysql-osx}{instructions here} to install all the Rails dependencies, though you can skip the step to install Rails if you like.
-
-\item \textbf{RubyGems}. The gems needed by Rails to interact with the database have to be compiled on the platform on which they will be run, so we cannot include them with the Tracks package, unlike some other gems. So you will need to \href{http://rubyforge.org/frs/?group_id=126}{download} and install RubyGems (run \texttt{ruby setup.rb} after extracting the package). Note that once again, Mac OS X Leopard users get an easy life, because RubyGems and the SQLite3 gem is already installed. Once installed you can use RubyGems to install the gems you need for your database. If you are using SQLite3, run \texttt{sudo gem install sqlite3-ruby}, then select the appropriate package for your platform (version 1.2.1 recommended). You can use MySQL without installing a gem, but installing the gem can speed things up a bit: \texttt{sudo install gem mysql}. If you're using Leopard, there are a few work-arounds necessary, which are explained on \href{http://trac.macosforge.org/projects/ruby/wiki/Troubleshooting#IcannotbuildrubymysqlonLeopardwithmysql.combinaries}{Mac OS Forge}. The ruby-mysql bindings can sometimes be a bit troublesome to install, so to be honest, it's probably not worth the bother unless you are trying to wring maximum speed out of your system. If you are using PostgreSQL, then you can install a postgres gem: \texttt{gem install postgres}.
-
-\item \textbf{Database}. The easiest option is to use SQLite3, as the database is included in the package. All you need then is the \texttt{sqlite3-ruby} gem, as described in step 2, and the SQLite3 libraries and binary (see \href{http://sqlite.org/download.html}{sqlite.org} for downloads and installation instructions). If you want to use MySQL, download and install a package for your platform from \href{http://dev.mysql.com/downloads/mysql/5.0.html}{MySQL.com}. The basic steps for Postgresql should be similar to those for MySQL, but they will not be discussed further here.
-\end{enumerate}
-
-You can find several installation howtos for specific setups \href{http://dev.rousette.org.uk/wiki/Tracks/Install}{here}. They were contributed by various Tracks users.
-
-
-\section{Installation}
-\label{installation}
-
-This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see Upgrading to Tracks 1.7 (\autoref{upgrading}).
-
-
-\begin{enumerate}
-
-
-\item Unzip tracks (\autoref{unzip_install}) and install in a directory
-
-\item Decide on a database (\autoref{database_install}) to use
-\begin{enumerate}
-
-
-\item SQLite3 - change database.yml to point to SQLite3 database. Make sure you add the complete path to the database
-
-\item MySQL - create new MySQL db and grant all privileges
-\end{enumerate}
-
-
-
-\item Configure some variables (\autoref{config_install})
-
-\item Populate the database with the Tracks 1.7 schema (\autoref{rake_install})
-
-\item Start the server (\autoref{startserver_install})
-
-\item Visit Tracks in a browser (\autoref{signup_install})
-
-\item Customise Tracks (\autoref{customise_install})
-\end{enumerate}
-
-\subsection{Unzip Tracks and install}
-\label{unzip_install}
-
-Unzip the package and move Tracks into the directory you want to run it from. For example, for Mac OS X users, \texttt{\ensuremath{\sim}/Sites} is a good choice.
-
-
-\subsection{Decide on a database}
-\label{database_install}
-
-Before you go any further, you need to decide which database you will use. See the What you need to install (\autoref{whatyouneed}) section for details on installing the required components for you choice of database.
-
-
-\begin{enumerate}
-
-
-\item \textbf{SQLite3}. All you need to do is make sure that you point Tracks to the included SQLite3 database in \texttt{/db} in the next step, Configure variables (\autoref{config_install}).
-
-\item \textbf{MySQL}. Once you have MySQL installed, you need to create a database and database-user to use with Tracks 1.7. For this, you can use MySQL Administrator or go into a terminal and issue the following commands:
-\end{enumerate}
-
-\begin{adjustwidth}{2.5em}{2.5em}
-\begin{verbatim}
-
-
- mysql -uroot -p
- mysql> CREATE DATABASE tracks16;
- mysql> GRANT ALL PRIVILEGES ON tracks16.* TO yourmysqluser@localhost \
- IDENTIFIED BY 'password-goes-here' WITH GRANT OPTION;
-
-
-\end{verbatim}
-\end{adjustwidth}
-
-\subsection{Configure variables}
-\label{config_install}
-
-\begin{enumerate}
-
-
-\item If you downloaded Tracks 1.7 via Subversion, you need to duplicate the files \texttt{database.yml.tmpl} and \texttt{environment.yml.tmpl} and remove the \texttt{*.tmpl} extension from the duplicates. Similarly, duplicate \texttt{/log.tmpl} and remove the \texttt{*.tmpl} extension, then edit the files as described in steps 2 and 3.
-
-\item Open the file \texttt{/config/database.yml} and edit the \texttt{production:} section with the details of your database. If you are using MySQL the \texttt{adapter:} line should read \texttt{adapter: mysql}, \texttt{host: localhost} (in the majority of cases), and your username and password should match those you assigned when you created the database. If you are using SQLite3, you should have only two lines under the production section: \texttt{adapter: sqlite3} and \texttt{database: db/tracks-15-blank.db}. If you downloaded the zipped file, the database.yml file is already configured to use the provided SQLite3 file.
-
-\item Open the file \texttt{/config/environment.rb}, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the \texttt{SALT = "change-me"} line (change the string ``change-me" to some other string of your choice), and the time zone setting. For the time zone setting, most people will only want to change the config.time\_zone option and leave the timezone to use in your database to :utc
-
-\item If you are using Windows, you may need to check the `shebang' lines (\texttt{\#!/usr/bin/env ruby}) of the \texttt{/public/dispatch.*} files and all the files in the \texttt{/script} directory. They are set to \texttt{\#!/usr/bin/env ruby} by default. This should work for all *nix based setups (Linux or Mac OS X), but Windows users will probably have to change it to something like \texttt{\#c:/ruby/bin/ruby} to point to the Ruby binary on your system.
-\end{enumerate}
-
-\subsection{Populate your database with the Tracks 1.7 schema}
-\label{rake_install}
-
-Open a terminal and change into the root of your Tracks 1.7 directory. Enter the following command:
-
-
-\texttt{rake db:migrate RAILS\_ENV=production}
-
-
-This will update your database with the required schema for Tracks 1.7. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).
-
-
-\subsection{Start the server}
-\label{startserver_install}
-
-While still in the Terminal inside the Tracks 1.7 root directory, issue the following command:
-
-
-\texttt{script/server -e production}
-
-
-If all goes well, you should see some text informing you that the Mongrel server is running: \texttt{** Mongrel available at 0.0.0.0:3000}. If you are already running other services on port 3000, you need to select a different port when running the server, using the \texttt{-p} option. You can stop the server again by the key combination Ctrl-C.
-
-
-\subsection{Visit Tracks in a browser}
-\label{signup_install}
-
-Visit \texttt{http://0.0.0.0:3000/signup} in a browser (or whatever URL and port was reported when you started the server in the step above) and chose a user name and password for admin user. Once logged in as admin, you can add other (ordinary level) users. If you need to access Tracks from a mobile/cellular phone browser, visit \texttt{http://yourdomain.com/mobile/}. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
-
-
-\subsection{Customise Tracks}
-\label{customise_install}
-
-Once logged in, add some Contexts and Projects, and then go ahead and add your actions. You might also want to visit the Preferences page to edit various settings to your liking. Have fun!
-
-
-\chapter{Upgrading to Tracks 1.7}
-\label{upgrading}
-
-\section{Upgrading from Tracks 1.6}
-\label{upgrading_1.6}
-
-You will need to upgrade your \texttt{config/environment.rb} with the new content from \texttt{config/environment.rb.tmpl} included in 1.7, as the format of this file has changed a bit between 1.6 and 1.7. Also, there were some database changes in Tracks 1.7, so you need to migrate to them.
-
-
-\begin{enumerate}
-
-
-\item Back up (\autoref{backup_upgrade}) your existing database and installation of Tracks
-
-\item Install Tracks 1.7 (\autoref{install_upgrade}) in a new directory
-
-\item Copy over (\autoref{config_upgrade}) a few configuration files from your Tracks 1.6 directory. If using SQLite3, copy the old database into the new Tracks 1.7 directory.
-
-\item Rebuild your environment.rb from the updated environment.rb.tmpl
-
-\item Run \texttt{rake db:migrate RAILS\_ENV=production} to update your old database (\autoref{rake_upgrade}) to the new schema -- you did back up your database didn't you?
-
-\item Run \texttt{script/server} inside your Tracks 1.7 directory to start up Tracks 1.7 (\autoref{startserver_upgrade}).
-
-\item Once you are happy that everything is working well, delete your old Tracks directory (\autoref{cleanup_upgrade}).
-\end{enumerate}
-
-\section{Upgrading from Tracks 1.5}
-\label{upgrading_1.5}
-
-There are no changes to the database between 1.5 and 1.6, but you will need to upgrade your \texttt{config/environment.rb} with the new content from \texttt{config/environment.rb.tmpl} included in 1.6, as the format of this file has changed a great deal between 1.5 and 1.6.
-
-
-\begin{enumerate}
-
-
-\item Back up (\autoref{backup_upgrade}) your existing database and installation of Tracks
-
-\item Install Tracks 1.6 (\autoref{install_upgrade}) in a new directory
-
-\item Copy over (\autoref{config_upgrade}) a few configuration files from your Tracks 1.043 directory. If using SQLite3, copy the old database into the new Tracks 1.6 directory
-
-\item Rebuild your environment.rb from the updated environment.rb.tmpl
-
-\item Run \texttt{script/server} inside your Tracks 1.6 directory to start up Tracks 1.6 (\autoref{startserver_upgrade}).
-
-\item Once you are happy that everything is working well, delete your old Tracks directory (\autoref{cleanup_upgrade}).
-\end{enumerate}
-
-\section{Upgrading from Tracks 1.043}
-\label{upgrading_1043}
-
-This should be a relatively straightforward, and involves the following main steps:
-
-
-\begin{enumerate}
-
-
-\item Back up (\autoref{backup_upgrade}) your existing database and installation of Tracks
-
-\item Install Tracks 1.6 (\autoref{install_upgrade}) in a new directory
-
-\item Copy over (\autoref{config_upgrade}) a few configuration files from your Tracks 1.043 directory. If using SQLite3, copy the old database into the new Tracks 1.6 directory
-
-\item Run \texttt{rake db:migrate RAILS\_ENV=production} to update your old database (\autoref{rake_upgrade}) to the new schema -- you did back up your database didn't you?
-
-\item Run \texttt{script/server} inside your Tracks 1.6 directory to start up Tracks 1.6 (\autoref{startserver_upgrade}).
-
-\item Once you are happy that everything is working well, delete your old Tracks directory (\autoref{cleanup_upgrade}).
-\end{enumerate}
-
-\subsection{Backing up}
-\label{backup_upgrade}
-
-It's very important that you \textbf{back up your database} before you start the upgrade process. It's always possible for things to go wrong with the database update, and you don't want to lose any data. If you are using SQLite3 and you are leaving your old Tracks directory in place, then you don't need to do anything. However, there is no harm in taking extra precautions and copying your database from \texttt{/db} to a safe location as an extra backup, or making a dump of the schema and contents. You will never regret making too many backups! If you are using MySQL, make a SQL dump of your database, replacing the terms in square brackets with the correct information for your setup:
-
-
-\texttt{mysqldump ---user [user name] ---password=[password] [database name] $>$ [dump file]}
-
-
-Rename your old Tracks installation (e.g.\ to `tracks-old') so that you can install Tracks 1.7 along side it.
-
-
-\subsection{Install Tracks 1.7}
-\label{install_upgrade}
-
-There are two methods of downloading Tracks 1.7:
-
-
-\begin{enumerate}
-
-
-\item (Recommended for most people) Download the \href{http://www.rousette.org.uk/projects/files/tracks-current.zip}{zipped package}, and unzip in your preferred location (e.g.\ \texttt{\ensuremath{\sim}/Sites} for Mac OS X users).
-
-\item If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions):
-\end{enumerate}
-
-\begin{adjustwidth}{2.5em}{2.5em}
-\begin{verbatim}
-
-
- cd ~/Sites
- git clone git://github.com/bsag/tracks.git
- cd tracks
-
-
-\end{verbatim}
-\end{adjustwidth}
-
-\subsection{Copy over old configuration files}
-\label{config_upgrade}
-
-There are a few files you need to copy over from your old installation. If you copy them over rather than moving them, you can still run your old version of Tracks if anything goes awry with the installation process.
-
-
-\begin{enumerate}
-
-
-\item Copy \texttt{/config/database.yml} from your old Tracks directory to the same location in the new one. Double check that the information there is still correct.
-
-\item Duplicate \texttt{/config/environment.rb.tmpl} in the Tracks 1.7 directory, and rename the file to \texttt{environment.rb}. Open the file and alter the line \texttt{SALT = "change-me"} so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location. If you have made any other customisations to \texttt{environment.rb} in the past, copy those over, but the contents of the file have slightly changed since 1.5, so check it carefully.
-
-\item Copy your \texttt{/log} directory over from your old installation to the root of the new one, or just rename \texttt{/log.tmpl} to \texttt{log} to start afresh.
-
-\item If you are using SQLite3, copy your database from \texttt{/db} in your old Tracks directory to the same location in the new one.
-
-\item If you are using Windows, you may need to check the `shebang' lines (\texttt{\#!/usr/bin/env ruby})\footnote{The \texttt{env} binary helps to locate other binaries, regardless of their location. If you don't have \texttt{env} installed, you'll need to change this line to point to the location of your Ruby binary.The \texttt{env} binary helps to locate other binaries, regardless of their location. If you don't have \texttt{env} installed, you'll need to change this line to point to the location of your Ruby binary.} of the \texttt{/public/dispatch.*} files and all the files in the \texttt{/script} directory. They are set to \texttt{\#!/usr/bin/env ruby} by default. Check the format of those lines in your old installation, and change the new ones as necessary.
-\end{enumerate}
-
-\subsection{Update your old database to the new format}
-\label{rake_upgrade}
-
-In a terminal, change directories so that you are inside the Tracks 1.7 directory. Then issue the command to update your Tracks 1.6 database to the format required for Tracks 1.7:
-
-
-\texttt{rake db:migrate RAILS\_ENV=production}
-
-
-Watch the output carefully for errors, but it should report at the end of the process that everything worked OK. If you do get errors, you'll have to fix them before you proceed any further. Running rake with the \texttt{--trace} option can help to track down the problem.
-
-
-\subsection{Start the server}
-\label{startserver_upgrade}
-
-If you're still in the Tracks 1.7 root directory in a terminal, enter the following command to start up Tracks in production mode:
-
-
-\texttt{script/server -e production}
-
-
-Visit the URL indicated by the output (e.g.\ \texttt{** Mongrel available at 0.0.0.0:3000}
-) in a browser, and with any luck, you should be able to log in and find all your actions as you left them! If you need to access Tracks from a mobile/cellular phone browser, visit \texttt{http://yourdomain.com/mobile/}. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
-
-
-\subsection{Clean up your old installation}
-\label{cleanup_upgrade}
-
-Once you're certain that your new Tracks 1.7 installation is working perfectly, you can delete your old Tracks directory.
-
-
-\section{Upgrading from versions prior to 1.043}
-\label{upgradingfromversionspriorto1.043}
-
-The best option for versions prior to 1.043 is to follow the instructions below to upgrade to version 1.043, then use the instructions above to upgrade from version 1.043 (\autoref{upgrading_1043}).
-
-
-\begin{enumerate}
-
-
-\item For safety, rename your current Tracks directory to `tracks-old' or something similar.
-
-\item Before you do anything else, \textbf{BACK UP YOUR DATABASE} (tables and content) and keep the SQL dumps somewhere safe so that you can recreate the old database if necesary.
-
-\item Download a copy of Tracks 1.043 and unzip alongside your `tracks-old' directory.
-
-\item Open the file \texttt{config/environment.rb} and look at the last line which should read: \texttt{SALT = "change-me"}. Change the word change-me to something else of your choosing. This string will be used as a `salt' to encrypt your password and make it a bit more secure. Also look at the timezone setting at the bottom. You can leave it commented out if your server is in the same time zone as you, but you may need to adjust it if your server is in a different time zone.
-
-\item In \texttt{database.yml} insert your old database name, user and password under the `development' section. If you are using SQLite3 rather than MySQL or PostgreSQL, you need only the database name, and to change the `adapter' line to `sqlite3'. You also need to copy (NOT MOVE!), your SQLite3 database from your tracks-old \texttt{db} directory to your new tracks \texttt{db} directory
-
-\item Run the command \texttt{rake extract\_fixtures} inside the Tracks directory. This will populate the \texttt{db/exported\_fixtures} directory with \texttt{*.yml} files corresponding to the contexts, projects and todos table from the contents of your old database.
-
-\item Open \texttt{db/exported\_fixtures/todos.yml} and search for the lines starting \texttt{created:} and replace with \texttt{created\_at:}. If you are using SQLite3, you also need to change the following: \texttt{done: "0"} with \texttt{done: "f"} and \texttt{done: "1"} with \texttt{done: "t"}. You need to replace the similar `done' lines in \texttt{projects.yml}, and in \texttt{contexts.yml} replace \texttt{hide: "0"} with \texttt{hide: "f"} and \texttt{hide: "1"} with \texttt{hide: "t"}.
-
-\item Create a new MySQL database (named tracks1043, for example). In \texttt{database.yml} insert this new database name, user and password under the `development' and `production' sections. If you are using SQLite3, insert a new name for a database to hold your Tracks 1.043 data.
-
-\item Run the command \texttt{rake db\_schema\_import} inside the Tracks directory. This should import the upgraded schema for 1.043 into your new database.
-
-\item Run the command \texttt{rake load\_exported\_fixtures} which will import the contents of your old database from the fixtures files in \texttt{db/exported\_fixtures}.
-
-\item If you are using Windows, you may need to check the `shebang' lines (\texttt{\#!/usr/bin/env ruby})\footnote{The \texttt{env} binary helps to locate other binaries, regardless of their location. If you don't have \texttt{env} installed, you'll need to change this line to point to the location of your Ruby binary.The \texttt{env} binary helps to locate other binaries, regardless of their location. If you don't have \texttt{env} installed, you'll need to change this line to point to the location of your Ruby binary.} of the \texttt{/public/dispatch.*} files and all the files in the \texttt{/script} directory. They are set to \texttt{\#!/usr/bin/env ruby} by default. Check the format of those lines in your old installation, and change the new ones as necessary.
-
-\item Try starting up the server with \texttt{script/server} to make sure that all your data has migrated successfully. If all is well, follow the instructions above to upgrade from version 1.043 (\autoref{upgrading_1043}) to Tracks 1.6. If you need to access Tracks from a mobile/cellular phone browser, visit \texttt{http://yourdomain.com/mobile/}. This mobile version is a special, lightweight version of Tracks, designed to use on a mobile browser.
-\end{enumerate}
-
-%
-% Back Matter
-%
-
-\backmatter
-%\appendixpage
-
-% Bibliography
-\bibliographystyle{\mybibliostyle}
-\bibliocommand
-
-% Glossary
-\printglossary
-
-
-% Index
-\printindex
-
-\end{document}
diff --git a/doc/memoir-twosided-manual.xslt b/doc/memoir-twosided-manual.xslt
deleted file mode 100644
index 59a9d752..00000000
--- a/doc/memoir-twosided-manual.xslt
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- \documentclass[10pt,twoside]{memoir}
-\usepackage{layouts}[2001/04/29]
-
-\usepackage{palatino}
-\usepackage{color,calc}
-\newsavebox{\ChpNumBox}
-\definecolor{ChapBlue}{rgb}{0.00,0.65,0.65}
-\makeatletter
-\newcommand*{\thickhrulefill}{%
-\leavevmode\leaders\hrule height 1\p@ \hfill \kern \z@}
-\newcommand*\BuildChpNum[2]{%
-\begin{tabular}[t]{@{}c@{}}
-\makebox[0pt][c]{#1\strut} \\[.5ex]
-\colorbox{ChapBlue}{%
-\rule[-10em]{0pt}{0pt}%
-\rule{1ex}{0pt}\color{black}#2\strut
-\rule{1ex}{0pt}}%
-\end{tabular}}
-\makechapterstyle{BlueBox}{%
-\renewcommand{\chapnamefont}{\large\scshape}
-\renewcommand{\chapnumfont}{\Huge\bfseries}
-\renewcommand{\chaptitlefont}{\raggedright\Huge\bfseries}
-\setlength{\beforechapskip}{20pt}
-\setlength{\midchapskip}{26pt}
-\setlength{\afterchapskip}{40pt}
-\renewcommand{\printchaptername}{}
-\renewcommand{\chapternamenum}{}
-\renewcommand{\printchapternum}{%
-\sbox{\ChpNumBox}{%
-\BuildChpNum{\chapnamefont\@chapapp}%
-{\chapnumfont\thechapter}}}
-\renewcommand{\printchapternonum}{%
-\sbox{\ChpNumBox}{%
-\BuildChpNum{\chapnamefont\vphantom{\@chapapp}}%
-{\chapnumfont\hphantom{\thechapter}}}}
-\renewcommand{\afterchapternum}{}
-\renewcommand{\printchaptertitle}[1]{%
-\usebox{\ChpNumBox}\hfill
-\parbox[t]{\hsize-\wd\ChpNumBox-1em}{%
-\vspace{\midchapskip}%
-\thickhrulefill\par
-\chaptitlefont ##1\par}}%
-}
-\chapterstyle{BlueBox}
-
-\setsecheadstyle{\sffamily\bfseries\Large}
-\setsubsecheadstyle{\sffamily\bfseries\normal}
-
-\makepagestyle{myruledpagestyle}
-\makeevenhead{myruledpagestyle}{\thepage}{}{\leftmark}
-\makeoddhead{myruledpagestyle}{\rightmark}{}{\thepage}
-\makeatletter
-\makepsmarks{myruledpagestyle}{
- \def\chaptermark##1{\markboth{%
- \ifnum \value{secnumdepth} > -1
- \if@mainmatter
- \chaptername\ \thechapter\ --- %
- \fi
- \fi
- ##1}{}}
- \def\sectionmark##1{\markright{%
- \ifnum \value{secnumdepth} > 0
- \thesection. \ %
- \fi
- ##1}}
-}
-\makeatother
-
-\makerunningwidth{myruledpagestyle}{1.1\textwidth}
-\makeheadposition{myruledpagestyle}{flushright}{flushleft}{flushright}{flushleft}
-\makeheadrule{myruledpagestyle}{1.1\textwidth}{\normalrulethickness}
-
-\makeglossary
-\makeindex
-
-\def\mychapterstyle{BlueBox}
-\def\mypagestyle{myruledpagestyle}
-\def\revision{}
-
-
-
-
-
\ No newline at end of file
diff --git a/lib/tasks/database.rake b/lib/tasks/database.rake
new file mode 100644
index 00000000..eb02a45f
--- /dev/null
+++ b/lib/tasks/database.rake
@@ -0,0 +1,27 @@
+require 'rake'
+
+namespace :db do
+ desc "Dump the current SQLite3 or MySQL database to a sql file"
+ task :dump_sql do
+ load 'config/environment.rb'
+ abcs = ActiveRecord::Base.configurations
+ case abcs[RAILS_ENV]["adapter"]
+ when 'mysql'
+ ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
+ File.open("db/#{RAILS_ENV}_data.sql", "w+") do |f|
+ if abcs[RAILS_ENV]["password"].blank?
+ f << `mysqldump -h #{abcs[RAILS_ENV]["host"]} -u #{abcs[RAILS_ENV]["username"]} #{abcs[RAILS_ENV]["database"]}`
+ else
+ f << `mysqldump -h #{abcs[RAILS_ENV]["host"]} -u #{abcs[RAILS_ENV]["username"]} -p#{abcs[RAILS_ENV]["password"]} #{abcs[RAILS_ENV]["database"]}`
+ end
+ end
+ when 'sqlite3'
+ ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
+ File.open("db/#{RAILS_ENV}_data.sql", "w+") do |f|
+ f << `sqlite3 #{abcs[RAILS_ENV]["database"]} .dump`
+ end
+ else
+ raise "Task not supported by '#{abcs[RAILS_ENV]['adapter']}'"
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 5cc542f0..d2ec9263 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -170,7 +170,7 @@ describe Todo do
it 'is starred if tag is "starred"' do
todo = create_todo
todo.should_not be_starred
- todo.add_tag('starred')
+ todo._add_tags('starred')
todo.reload
todo.should be_starred
end
@@ -178,7 +178,7 @@ describe Todo do
describe 'when toggling star flag' do
it 'toggles to not starred when starred' do
todo = create_todo
- todo.add_tag('starred')
+ todo._add_tags('starred')
todo.should be_starred
todo.toggle_star!
todo.reload
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 7b4eb222..7ca452fc 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -68,14 +68,6 @@ describe User do
with_dependent(:delete_all)
end
- it 'has many taggings' do
- User.should have_many(:taggings)
- end
-
- it 'has many tags through taggings' do
- User.should have_many(:tags).through(:taggings).with_select('DISTINCT tags.*')
- end
-
it 'has one preference' do
User.should have_one(:preference)
end
diff --git a/spec/views/login/login_mobile.html.erb_spec.rb b/spec/views/login/login_mobile.html.erb_spec.rb
new file mode 100644
index 00000000..5abca9f6
--- /dev/null
+++ b/spec/views/login/login_mobile.html.erb_spec.rb
@@ -0,0 +1,8 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+describe "/login.m" do
+ it "should render without an error" do
+ render :action => 'login/login_mobile.html.erb', :layout => 'mobile.m.erb'
+ response.should_not have_tag("div#Application-Trace")
+ end
+end
diff --git a/test/fixtures/taggings.yml b/test/fixtures/taggings.yml
index 2d1b1de7..880ba6b4 100644
--- a/test/fixtures/taggings.yml
+++ b/test/fixtures/taggings.yml
@@ -4,14 +4,12 @@ foo_bar1:
tag_id: 1
taggable_id: 1 # Call Bill Gates
taggable_type: Todo
- user_id: 1
foo_bar2:
id: 2
tag_id: 2
taggable_id: 1 # Call Bill Gates
taggable_type: Todo
- user_id: 1
# Todo 2 should be tagged with foo
foo1:
@@ -19,11 +17,9 @@ foo1:
tag_id: 1
taggable_id: 2 # Call dinosaur exterminator
taggable_type: Todo
- user_id: 1
foo2:
id: 4
tag_id: 1
taggable_id: 3 # Buy milk - completed
- taggable_type: Todo
- user_id: 1
+ taggable_type: Todo
\ No newline at end of file
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
index 7b2ad866..db82051e 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 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
+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(:moremoney).position
+ assert_equal 2, projects(:gardenclean).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/stats_controller_test.rb b/test/functional/stats_controller_test.rb
index f3f2c64a..41e0048f 100755
--- a/test/functional/stats_controller_test.rb
+++ b/test/functional/stats_controller_test.rb
@@ -57,8 +57,8 @@ class StatsControllerTest < Test::Unit::TestCase
assert_equal 3, assigns['projects'].count(:conditions => "state = 'active'")
assert_equal 10, assigns['contexts'].count
assert_equal 16, assigns['actions'].count
- assert_equal 4, assigns['tags'].count
- assert_equal 2, assigns['unique_tags'].size
+ assert_equal 4, assigns['tags_count']
+ assert_equal 2, assigns['unique_tags_count']
assert_equal 2.week.ago.utc.beginning_of_day, assigns['first_action'].created_at
end
@@ -90,5 +90,21 @@ class StatsControllerTest < Test::Unit::TestCase
assert_response :success
assert_template "stats/show_selection_from_chart"
end
-
+
+ def test_stats_render_when_tasks_have_no_taggings
+ login_as(:admin_user)
+
+ # using the default fixtures, todos have tags
+ get :index
+ assert_response :success
+
+ # clear taggings table and render again
+ taggings = Tagging.find(:all)
+ taggings.each do |t|
+ t.delete
+ end
+ get :index
+ assert_response :success
+
+ end
end
diff --git a/test/functional/todos_controller_test.rb b/test/functional/todos_controller_test.rb
index 624a702d..9bc355e1 100644
--- a/test/functional/todos_controller_test.rb
+++ b/test/functional/todos_controller_test.rb
@@ -1,488 +1,508 @@
-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
+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_create_todo_via_xml_show_from
+ login_as(:admin_user)
+
+ assert_difference Todo, :count do
+ xml = "Call Warren Buffet to find out how much he makes per day#{projects(:timemachine).id}#{contexts(:agenda).id}#{1.week.from_now.xmlschema}"
+
+ #p parse_xml_body(xml)
+ post :create, parse_xml_body(xml).update(:format => "xml")
+ assert_response :created
+ end
+ end
+
+ def parse_xml_body(body)
+ env = { 'rack.input' => StringIO.new(body),
+ 'HTTP_X_POST_DATA_FORMAT' => 'xml',
+ 'CONTENT_LENGTH' => body.size.to_s }
+ ActionController::RackRequest.new(env).request_parameters
+ 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.count
+ 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/unit/recurring_todo_test.rb b/test/unit/recurring_todo_test.rb
index e7f5ef25..b223b2a1 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")
- @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/todo_test.rb b/test/unit/todo_test.rb
index 3e0fdf4d..6b82340b 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_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
+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/plugins/webrat/lib/webrat/core.rb b/vendor/plugins/webrat/lib/webrat/core.rb
index 32743d16..185a00d5 100644
--- a/vendor/plugins/webrat/lib/webrat/core.rb
+++ b/vendor/plugins/webrat/lib/webrat/core.rb
@@ -1,3 +1,3 @@
-Dir[File.join(File.dirname(__FILE__), "core", "*.rb")].each do |file|
- require File.expand_path(file)
+%w{field form label link logging page select_option session}.each do |file|
+ require File.dirname(__FILE__) + "/core/#{file}"
end
\ No newline at end of file