From 106d5ee448fdbc9410a258dcaa594ef860fd488a Mon Sep 17 00:00:00 2001 From: lukemelia Date: Sun, 18 Mar 2007 00:38:05 +0000 Subject: [PATCH] Made TodosController more RESTful and use it to fulfill all feeds, eliminating the need for the FeedController and it's helper and views. Also added an ATOM feed (not linked in the UI anywhere, just substitute .atom for .rss). I also ran rcov on unit tests and added tests to improve test coverage, uncovering a couple of bugs along the way. git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@476 a4c988fc-2ded-0310-b66e-134b36920a42 --- tracks/app/controllers/application.rb | 33 ++- tracks/app/controllers/feed_controller.rb | 133 --------- tracks/app/controllers/projects_controller.rb | 14 +- tracks/app/controllers/todos_controller.rb | 210 ++++++++++++-- tracks/app/helpers/application_helper.rb | 22 -- tracks/app/helpers/feed_helper.rb | 43 --- tracks/app/helpers/feedlist_helper.rb | 21 +- tracks/app/helpers/login_helper.rb | 15 - tracks/app/helpers/todos_helper.rb | 5 + tracks/app/models/context.rb | 4 +- tracks/app/models/project.rb | 40 +-- tracks/app/models/todo.rb | 16 +- tracks/app/models/user.rb | 13 +- tracks/app/views/contexts/_text_context.rhtml | 8 + tracks/app/views/feed/rss.rxml | 25 -- tracks/app/views/feed/text.rhtml | 3 - tracks/app/views/feedlist/index.rhtml | 40 +-- tracks/app/views/projects/index_text.rhtml | 3 +- tracks/app/views/todos/_text_todo.rhtml | 19 ++ .../ical.rhtml => todos/index_ical.rhtml} | 48 ++-- tracks/app/views/todos/index_text.rhtml | 1 + tracks/config/routes.rb | 23 +- tracks/db/schema.rb | 272 +++++++++--------- tracks/lib/login_system.rb | 2 +- tracks/lib/validations.rb | 28 -- tracks/test/fixtures/projects.yml | 2 +- tracks/test/fixtures/todos.yml | 12 +- .../functional/contexts_controller_test.rb | 42 +-- .../test/functional/feed_controller_test.rb | 17 -- .../functional/projects_controller_test.rb | 42 +-- .../test/functional/todos_controller_test.rb | 112 +++++++- .../test/integration/context_xml_api_test.rb | 20 +- tracks/test/integration/feed_smoke_test.rb | 66 +++-- tracks/test/test_helper.rb | 4 +- tracks/test/unit/context_test.rb | 21 +- tracks/test/unit/project_test.rb | 54 +++- tracks/test/unit/todo_test.rb | 70 ++++- tracks/test/unit/user_test.rb | 184 +++++++++--- 38 files changed, 1007 insertions(+), 680 deletions(-) delete mode 100644 tracks/app/controllers/feed_controller.rb delete mode 100644 tracks/app/helpers/feed_helper.rb create mode 100644 tracks/app/views/contexts/_text_context.rhtml delete mode 100644 tracks/app/views/feed/rss.rxml delete mode 100644 tracks/app/views/feed/text.rhtml create mode 100644 tracks/app/views/todos/_text_todo.rhtml rename tracks/app/views/{feed/ical.rhtml => todos/index_ical.rhtml} (92%) create mode 100644 tracks/app/views/todos/index_text.rhtml delete mode 100644 tracks/lib/validations.rb delete mode 100644 tracks/test/functional/feed_controller_test.rb diff --git a/tracks/app/controllers/application.rb b/tracks/app/controllers/application.rb index 9fb16a6f..6b42a528 100644 --- a/tracks/app/controllers/application.rb +++ b/tracks/app/controllers/application.rb @@ -13,7 +13,7 @@ class ApplicationController < ActionController::Base helper :application include LoginSystem - + layout 'standard' before_filter :set_session_expiration @@ -21,6 +21,9 @@ class ApplicationController < ActionController::Base after_filter :set_charset + include ActionView::Helpers::TextHelper + helper_method :format_date, :markdown + # By default, sets the charset to UTF-8 if it isn't already set def set_charset headers["Content-Type"] ||= "text/html; charset=UTF-8" @@ -78,13 +81,35 @@ class ApplicationController < ActionController::Base end def count_undone_todos(todos_parent) - if (todos_parent.is_a?(Project) && todos_parent.hidden?) + if todos_parent.nil? + count = 0 + elsif (todos_parent.is_a?(Project) && todos_parent.hidden?) count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]" else count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]" end - count = 0 if count == nil - count + count || 0 + end + + # Convert a date object to the format specified + # in config/settings.yml + # + def format_date(date) + if date + date_format = @user.prefs.date_format + formatted_date = date.strftime("#{date_format}") + else + formatted_date = '' + end + formatted_date + end + + # Uses RedCloth to transform text using either Textile or Markdown + # Need to require redcloth above + # RedCloth 3.0 or greater is needed to use Markdown, otherwise it only handles Textile + # + def markdown(text) + RedCloth.new(text).to_html end protected diff --git a/tracks/app/controllers/feed_controller.rb b/tracks/app/controllers/feed_controller.rb deleted file mode 100644 index badce178..00000000 --- a/tracks/app/controllers/feed_controller.rb +++ /dev/null @@ -1,133 +0,0 @@ -# Produces an feeds of the next actions, both RSS and plain text -# -class FeedController < ApplicationController - - helper :feed - layout nil - - session :disabled => true # Prevents session control from interfering with feed - - skip_before_filter :login_required - before_filter :check_token_against_user_word - before_filter :prepare_for_feed, :only => [:rss, :text, :ical] - before_filter :identify_contexts, :only => [:text, :ical] - - # Build an RSS feed - def rss - headers["Content-Type"] = "text/xml; charset=utf-8" - end - - # Builds a plain text page listing incomplete next actions, - # grouped by context (contexts are sorted by position, as on the home page). - # Showing notes doesn't make much sense here so they are omitted. - # Hidden contexts are also hidden in the text view - # You can use this with GeekTool to get your next actions - # on the desktop: - # curl [url from "TXT" link on todo/list] - # - def text - headers["Content-Type"] = "text/plain; charset=utf-8" - end - - # Builds an iCal compatible export of incomplete todos - # so that each action forms a VTODO in your iCal calendar. - # Due dates are supported, and notes are included. - # - def ical - headers["Content-Type"] = "text/calendar" - end - -protected - - # Check whether the token in the URL matches the word in the User's table - def check_token_against_user_word - @user = User.find_by_login( params['login'] ) - unless ( params['token'] == @user.word) - render :text => "Sorry, you don't have permission to view this page." - return false - end - end - - def identify_contexts - if params.key?('context') - @contexts = [ @user.contexts.find_by_params(params) ] - else - @contexts = @user.contexts.find_all_by_hide(false, "position ASC") - end - end - - def prepare_for_feed - condition_builder = FindConditionBuilder.new - options = Hash.new - - if params.key?('done') - condition_builder.add 'todos.state = ?', 'completed' - else - condition_builder.add 'todos.state = ?', 'active' - end - - if params.key?('limit') - options[:limit] = limit = params['limit'] - @description = limit ? "Lists the last #{limit} incomplete next actions" : "Lists incomplete next actions" - end - @title = "Tracks - Next Actions" - @description = "Filter: " - - if params.key?('due') - due_within = params['due'].to_i - due_within_when = @user.time + 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 >= ?', @user.time - done_in_last.days) - @title << " actions completed" - @description << " in the last #{done_in_last.to_s} days" - end - - if params.key?('context') - context = @user.contexts.find_by_params(params) - condition_builder.add('todos.context_id = ?', context.id) - @title << " in #{context.name}" - @description << " in context '#{context.name}'" - end - - if params.key?('project') - project = @user.projects.find_by_params(params) - condition_builder.add('todos.project_id = ?', project.id) - @title << " for #{project.name}" - @description << " for project '#{project.name}'" - end - - options[:conditions] = condition_builder.to_conditions - options[:order] = "todos.due IS NULL, todos.due ASC, todos.created_at ASC" - options[:include] = :project - - @todos = @user.todos.find(:all, options ) - - 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 - - -end diff --git a/tracks/app/controllers/projects_controller.rb b/tracks/app/controllers/projects_controller.rb index df042b5e..5ed1149c 100644 --- a/tracks/app/controllers/projects_controller.rb +++ b/tracks/app/controllers/projects_controller.rb @@ -138,14 +138,15 @@ class ProjectsController < ApplicationController def render_rss_feed lambda do render_rss_feed_for @projects, :feed => Project.feed_options(@user), - :item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) } } + :item => { :title => :name, :description => lambda { |p| summary(p) } } end end def render_atom_feed lambda do render_atom_feed_for @projects, :feed => Project.feed_options(@user), - :item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) }, + :item => { :description => lambda { |p| summary(p) }, + :title => :name, :author => lambda { |p| nil } } end end @@ -181,4 +182,13 @@ class ProjectsController < ApplicationController init_data_for_sidebar end + def summary(project) + project_description = '' + project_description += sanitize(markdown( project.description )) unless project.description.blank? + project_description += "

#{count_undone_todos_phrase(p)}. " + project_description += "Project is #{project.state}." + project_description += "

" + project_description + end + end diff --git a/tracks/app/controllers/todos_controller.rb b/tracks/app/controllers/todos_controller.rb index e900b3bb..20e508de 100644 --- a/tracks/app/controllers/todos_controller.rb +++ b/tracks/app/controllers/todos_controller.rb @@ -3,33 +3,28 @@ class TodosController < ApplicationController helper :todos append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred ] + skip_before_filter :login_required, :only => [:index] + prepend_before_filter :login_or_feed_token_required, :only => [:index] + session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) } + layout 'standard' - # Main method for listing tasks - # Set page title, and fill variables with contexts and done and not-done tasks - # Number of completed actions to show is determined by a setting in settings.yml def index @projects = @user.projects.find(:all, :include => [ :todos ]) @contexts = @user.contexts.find(:all, :include => [ :todos ]) - - @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 = @user.prefs.show_number_completed - @done = @user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0 - + @contexts_to_show = @contexts.reject {|x| x.hide? } - # Set count badge to number of not-done, not hidden context items - @count = @todos.reject { |x| !x.active? || x.context.hide? }.size - - respond_to do |wants| - wants.html - wants.xml { render :action => 'list.rxml', :layout => false } + respond_to do |format| + format.html &render_todos_html + format.xml { render :action => 'list.rxml', :layout => false } + format.rss &render_rss_feed + format.atom &render_atom_feed + format.text &render_text_feed + format.ics &render_ical_feed end end - + def create @todo = @user.todos.build p = params['request'] || params @@ -100,7 +95,7 @@ class TodosController < ApplicationController # def toggle_check @todo = check_user_return_todo - @todo.toggle_completion() + @todo.toggle_completion! @saved = @todo.save respond_to do |format| format.js do @@ -279,13 +274,89 @@ class TodosController < ApplicationController init_data_for_sidebar init_todos end - - def init_todos - # Exclude hidden projects from count on home page - @todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ]) - # Exclude hidden projects from the home page - @not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ]) + def with_feed_query_scope(&block) + unless TodosController.is_feed_request(request) + yield + return + end + + condition_builder = FindConditionBuilder.new + options = Hash.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 = @user.time + 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 >= ?', @user.time - done_in_last.days) + @title << " actions completed" + @description << " in the last #{done_in_last.to_s} days" + end + + Todo.with_scope :find => {:conditions => condition_builder.to_conditions} do + yield + end + + end + + def with_parent_resource_scope(&block) + if (params[:context_id]) + context = @user.contexts.find_by_params(params) + Todo.with_scope :find => {:conditions => ['todos.context_id = ?', context.id]} do + yield + end + elsif (params[:project_id]) + project = @user.projects.find_by_params(params) + Todo.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.with_scope :find => {:limit => params['limit']} do + yield + end + #@description = limit ? "Lists the last #{limit} incomplete next actions" : "Lists incomplete next actions" + else + yield + end + end + + def init_todos + with_feed_query_scope do + with_parent_resource_scope do + with_limit_scope do + + # Exclude hidden projects from count on home page + @todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ]) + + # Exclude hidden projects from the home page + @not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ]) + + end + end + end end def determine_down_count @@ -323,5 +394,92 @@ class TodosController < ApplicationController 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 = @user.prefs.show_number_completed + @done = @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 = @todos.reject { |x| !x.active? || x.context.hide? }.size + + render + end + end + + def render_rss_feed + lambda do + render_rss_feed_for @todos, :feed => Todo.feed_options(@user), + :item => { + :title => :description, + :link => lambda { |t| context_url(t.context) }, + :description => todo_feed_content + } + end + 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(@user), + :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_text', :layout => false, :content_type => Mime::TEXT + end + end + + def render_ical_feed + lambda do + render :action => 'index_ical', :layout => false, :content_type => Mime::ICS + end + end + + def self.is_feed_request(req) + ['rss','atom','txt','ics'].include?(req.parameters[:format]) + 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 + end diff --git a/tracks/app/helpers/application_helper.rb b/tracks/app/helpers/application_helper.rb index 826ddf88..ab3a91da 100644 --- a/tracks/app/helpers/application_helper.rb +++ b/tracks/app/helpers/application_helper.rb @@ -1,32 +1,10 @@ # The methods added to this helper will be available to all templates in the application. module ApplicationHelper - - # Convert a date object to the format specified - # in config/settings.yml - # - def format_date(date) - if date - date_format = @user.prefs.date_format - formatted_date = date.strftime("#{date_format}") - else - formatted_date = '' - end - formatted_date - end def user_time @user.time end - - # Uses RedCloth to transform text using either Textile or Markdown - # Need to require redcloth above - # RedCloth 3.0 or greater is needed to use Markdown, otherwise it only handles Textile - # - def markdown(text) - RedCloth.new(text).to_html - end - # Replicates the link_to method but also checks request.request_uri to find # current page. If that matches the url, the link is marked # id = "current" diff --git a/tracks/app/helpers/feed_helper.rb b/tracks/app/helpers/feed_helper.rb deleted file mode 100644 index 18cb2791..00000000 --- a/tracks/app/helpers/feed_helper.rb +++ /dev/null @@ -1,43 +0,0 @@ -module FeedHelper - - # Build a nicely formatted text string for display - # Context forms the heading, then the items are - # indented underneath. If there is a due date - # and the item is in a project, these are also displayed - # - def build_text_page(list,context) - result_string = "" - list.each do |item| - if item.context_id == context.id - result_string << "\n" + context.name.upcase + ":\n" if result_string.empty? - - if (item.completed?) && item.completed_at - result_string << " [Completed: " + format_date(item.completed_at) + "] " - end - - if item.due - result_string << " [Due: " + format_date(item.due) + "] " - result_string << item.description + " " - else - result_string << " " + item.description + " " - end - - if item.project_id - result_string << "(" + item.project.name + ")" - end - result_string << "\n" - end - end - return result_string - end - - def format_ical_notes(notes) - split_notes = notes.split(/\n/) - joined_notes = split_notes.join("\\n") - end - - def format_ical_uid(todo) - sprintf("%s%s%s%s", request.protocol, request.host, request.port_string, todo_url(todo)) - end - -end diff --git a/tracks/app/helpers/feedlist_helper.rb b/tracks/app/helpers/feedlist_helper.rb index ebbfd6b4..562e6521 100644 --- a/tracks/app/helpers/feedlist_helper.rb +++ b/tracks/app/helpers/feedlist_helper.rb @@ -1,24 +1,11 @@ module FeedlistHelper - def rss_feed_link(options = {}) - image_tag = image_tag("feed-icon.png", :size => "16X16", :border => 0, :class => "rss-icon") - linkoptions = {:controller => 'feed', :action => 'rss', :login => "#{@user.login}", :token => "#{@user.word}"} - linkoptions.merge!(options) - link_to(image_tag, linkoptions, :title => "RSS feed") - end - def rss_formatted_link(options = {}) image_tag = image_tag("feed-icon.png", :size => "16X16", :border => 0, :class => "rss-icon") linkoptions = { :token => @user.word, :format => 'rss' } linkoptions.merge!(options) link_to(image_tag, linkoptions, :title => "RSS feed") end - - def text_feed_link(options = {}) - linkoptions = {:controller => 'feed', :action => 'text', :login => "#{@user.login}", :token => "#{@user.word}"} - linkoptions.merge!(options) - link_to('TXT', linkoptions, :title => "Plain text feed" ) - end def text_formatted_link(options = {}) linkoptions = { :token => @user.word, :format => 'txt' } @@ -26,12 +13,10 @@ module FeedlistHelper link_to('TXT', linkoptions, :title => "Plain text feed" ) end - - def ical_feed_link(options = {}) - linkoptions = {:controller => 'feed', :action => 'ical', :login => "#{@user.login}", :token => "#{@user.word}"} + def ical_formatted_link(options = {}) + linkoptions = { :token => @user.word, :format => 'ics' } linkoptions.merge!(options) - link_to('iCal', linkoptions, :title => "iCal feed") + link_to('iCal', linkoptions, :title => "iCal feed" ) end - end diff --git a/tracks/app/helpers/login_helper.rb b/tracks/app/helpers/login_helper.rb index bffb636c..b0ac7165 100644 --- a/tracks/app/helpers/login_helper.rb +++ b/tracks/app/helpers/login_helper.rb @@ -1,18 +1,3 @@ module LoginHelper - - def render_errors(obj) - return "" unless obj - return "" unless request.post? - tag = String.new - - unless obj.valid? - tag << %{} - end - tag - end - - end \ No newline at end of file diff --git a/tracks/app/helpers/todos_helper.rb b/tracks/app/helpers/todos_helper.rb index 17a06855..7c26ed62 100644 --- a/tracks/app/helpers/todos_helper.rb +++ b/tracks/app/helpers/todos_helper.rb @@ -180,6 +180,11 @@ module TodosHelper return array_or_string_for_javascript(['Create a new context']) if @contexts.empty? array_or_string_for_javascript( @contexts.collect{|c| escape_javascript(c.name) } ) end + + def format_ical_notes(notes) + split_notes = notes.split(/\n/) + joined_notes = split_notes.join("\\n") + end private diff --git a/tracks/app/models/context.rb b/tracks/app/models/context.rb index f6b88dc1..b4d29f08 100644 --- a/tracks/app/models/context.rb +++ b/tracks/app/models/context.rb @@ -19,7 +19,7 @@ class Context < ActiveRecord::Base def self.feed_options(user) { :title => 'Tracks Contexts', - :description => "Lists all the contexts for #{user.display_name}." + :description => "Lists all the contexts for #{user.display_name}" } end @@ -37,7 +37,7 @@ class Context < ActiveRecord::Base def summary(undone_todo_count) s = "

#{undone_todo_count}. " - s += "Context is #{hidden? ? 'Hidden' : 'Active'}. " + s += "Context is #{hidden? ? 'Hidden' : 'Active'}." s += "

" s end diff --git a/tracks/app/models/project.rb b/tracks/app/models/project.rb index 84022914..46519b37 100644 --- a/tracks/app/models/project.rb +++ b/tracks/app/models/project.rb @@ -40,50 +40,34 @@ class Project < ActiveRecord::Base def self.feed_options(user) { :title => 'Tracks Projects', - :description => "Lists all the projects for #{user.display_name}." + :description => "Lists all the projects for #{user.display_name}" } end def to_param url_friendly_name end - - def description_present? - attribute_present?("description") - end - - def linkurl_present? - attribute_present?("linkurl") - end - - def title - name - end - - def summary(undone_todo_count) - project_description = '' - project_description += sanitize(markdown( description )) if description_present? - project_description += "

#{undone_todo_count}. " - project_description += "Project is #{state}. " - project_description += "#{linkurl}" if linkurl_present? - project_description += "

" - project_description - end def hide_todos todos.each do |t| - t.hide! unless t.completed? || t.deferred? - t.save + unless t.completed? || t.deferred? + t.hide! + t.save + end end end def unhide_todos todos.each do |t| - t.unhide! if t.project_hidden? - t.save + if t.project_hidden? + t.unhide! + t.save + end end end - + + # would prefer to call this method state=(), but that causes an endless loop + # as a result of acts_as_state_machine calling state=() to update the attribute def transition_to(candidate_state) case candidate_state.to_sym when current_state diff --git a/tracks/app/models/todo.rb b/tracks/app/models/todo.rb index eb87de24..b0080439 100644 --- a/tracks/app/models/todo.rb +++ b/tracks/app/models/todo.rb @@ -1,5 +1,4 @@ class Todo < ActiveRecord::Base - require 'validations' belongs_to :context, :order => 'name' belongs_to :project @@ -45,12 +44,12 @@ class Todo < ActiveRecord::Base validates_presence_of :context def validate - if deferred? && !show_from.blank? && show_from < user.date - errors.add("Show From", "must be a date in the future.") + if !show_from.blank? && show_from < user.date + errors.add("show_from", "must be a date in the future") end end - def toggle_completion + def toggle_completion! if completed? activate! else @@ -78,11 +77,18 @@ class Todo < ActiveRecord::Base alias_method :original_set_initial_state, :set_initial_state def set_initial_state - if show_from && (show_from > Time.now.utc.to_date) + if show_from && (show_from > user.date) write_attribute self.class.state_column, 'deferred' else original_set_initial_state end end + def self.feed_options(user) + { + :title => 'Tracks Actions', + :description => "Actions for #{user.display_name}" + } + end + end \ No newline at end of file diff --git a/tracks/app/models/user.rb b/tracks/app/models/user.rb index b944351b..43dac6f9 100644 --- a/tracks/app/models/user.rb +++ b/tracks/app/models/user.rb @@ -13,6 +13,8 @@ class User < ActiveRecord::Base find_by_url_friendly_name(params['id']) elsif params['context'] find_by_url_friendly_name(params['context']) + elsif params['context_id'] + find_by_url_friendly_name(params['context_id']) end end end @@ -28,6 +30,8 @@ class User < ActiveRecord::Base find_by_url_friendly_name(params['id']) elsif params['project'] find_by_url_friendly_name(params['project']) + elsif params['project_id'] + find_by_url_friendly_name(params['project_id']) end end def update_positions(project_ids) @@ -61,7 +65,7 @@ class User < ActiveRecord::Base :conditions => [ 'state = ?', 'deferred' ], :order => 'show_from ASC, todos.created_at DESC' do def find_and_activate_ready - find(:all, :conditions => ['show_from <= ?', Time.now.utc.to_date ]).collect { |t| t.activate_and_save! } + find(:all, :conditions => ['show_from <= ?', Time.now.utc.to_date.to_time ]).collect { |t| t.activate_and_save! } end end has_many :completed_todos, @@ -90,9 +94,14 @@ class User < ActiveRecord::Base validates_confirmation_of :password validates_length_of :login, :within => 3..80 validates_uniqueness_of :login, :on => :create - validates_inclusion_of :auth_type, :in => Tracks::Config.auth_schemes, :message=>"not a valid authentication type" validates_presence_of :open_id_url, :if => Proc.new{|user| user.auth_type == 'open_id'} + def validate + unless Tracks::Config.auth_schemes.include?(auth_type) + errors.add("auth_type", "not a valid authentication type") + end + end + alias_method :prefs, :preference def self.authenticate(login, pass) diff --git a/tracks/app/views/contexts/_text_context.rhtml b/tracks/app/views/contexts/_text_context.rhtml new file mode 100644 index 00000000..bfc5babb --- /dev/null +++ b/tracks/app/views/contexts/_text_context.rhtml @@ -0,0 +1,8 @@ +<% +context = text_context +todos_in_context = todos.select { |t| t.context_id == context.id } +if todos_in_context.length > 0 + %> +<%= context.name.upcase %>: +<% end -%> +<%= render :partial => "todos/text_todo", :collection => todos_in_context -%> \ No newline at end of file diff --git a/tracks/app/views/feed/rss.rxml b/tracks/app/views/feed/rss.rxml deleted file mode 100644 index 39ce9069..00000000 --- a/tracks/app/views/feed/rss.rxml +++ /dev/null @@ -1,25 +0,0 @@ -xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do - xml.channel do - xml.title(@title) - xml.link("http://#{request.host}:#{request.port}/todos/list") - xml.description(@description) - @todos.each do |i| - xml.item do - xml.title(i.description) - xml.link(context_url(i.context)) - item_notes = sanitize(markdown( i.notes )) if i.notes? - due = "
Due: #{format_date(i.due)}
\n" if i.due? - toggle_link = link_to( "mark as done", {:only_path => false, :controller => "todos", :action => "toggle_check", :id => i.id}) - done = "
#{toggle_link}
" unless i.completed? - done = "
Completed: #{format_date(i.completed_at)}
\n" if i.completed? - context_link = link_to( i.context.name, context_url(i.context) ) - if i.project_id? - project_link = link_to(i.project.name, project_url(i.project) ) - else - project_link = "none" - end - xml.description("#{done||''}#{due||''}#{item_notes||''}\n
Project: #{project_link}
\n
Context: #{context_link}
") - end - end - end -end \ No newline at end of file diff --git a/tracks/app/views/feed/text.rhtml b/tracks/app/views/feed/text.rhtml deleted file mode 100644 index 1efef1bc..00000000 --- a/tracks/app/views/feed/text.rhtml +++ /dev/null @@ -1,3 +0,0 @@ -<% for @context in @contexts -%> - <%= build_text_page( @todos, @context ) -%> -<% end -%> diff --git a/tracks/app/views/feedlist/index.rhtml b/tracks/app/views/feedlist/index.rhtml index 46ea8006..0b37ea82 100644 --- a/tracks/app/views/feedlist/index.rhtml +++ b/tracks/app/views/feedlist/index.rhtml @@ -11,32 +11,32 @@