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
This commit is contained in:
lukemelia 2007-03-18 00:38:05 +00:00
parent 84357b67d5
commit 106d5ee448
38 changed files with 1007 additions and 680 deletions

View file

@ -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

View file

@ -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

View file

@ -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 += "<p>#{count_undone_todos_phrase(p)}. "
project_description += "Project is #{project.state}."
project_description += "</p>"
project_description
end
end

View file

@ -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 = "<div>Due: #{format_date(i.due)}</div>\n" if i.due?
done = "<div>Completed: #{format_date(i.completed_at)}</div>\n" if i.completed?
context_link = "<a href=\"#{ context_url(i.context) }\">#{ i.context.name }</a>"
if i.project_id?
project_link = "<a href=\"#{ project_url(i.project) }\">#{ i.project.name }</a>"
else
project_link = "<em>none</em>"
end
"#{done||''}#{due||''}#{item_notes||''}\n<div>Project: #{project_link}</div>\n<div>Context: #{context_link}</div>"
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