From fcab16a5c22d7abebd7a81e4e8948ff5c3c5ba35 Mon Sep 17 00:00:00 2001 From: lukemelia Date: Sun, 4 Feb 2007 05:12:19 +0000 Subject: [PATCH] The contexts controller gets more RESTy. It now supports XML, RSS, ATOM, HTML and plain text views of the contexts list. Changes include: * Update the URL on the Feeds page to use /contexts.rss or /contexts.txt instead of FeedController? link * Add created_at and updated_at timestamps to contexts table to support ATOM feeds Notes: * This will break previous context listing feed subscriptions. git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@423 a4c988fc-2ded-0310-b66e-134b36920a42 --- tracks/app/controllers/contexts_controller.rb | 25 ++++- tracks/app/controllers/feed_controller.rb | 9 +- tracks/app/helpers/feed_helper.rb | 12 --- tracks/app/models/context.rb | 20 +++- tracks/app/views/contexts/index_text.rhtml | 5 + tracks/app/views/feed/contexts_text.rhtml | 2 - tracks/app/views/feedlist/index.rhtml | 4 +- tracks/config/routes.rb | 1 - .../db/migrate/027_add_context_timestamps.rb | 13 +++ tracks/db/schema.rb | 12 ++- tracks/test/fixtures/contexts.yml | 24 +++++ .../functional/contexts_controller_test.rb | 102 ++++++++++++++++++ .../test/integration/context_xml_api_test.rb | 4 +- tracks/test/integration/feed_smoke_test.rb | 4 +- tracks/test/unit/context_test.rb | 5 +- 15 files changed, 202 insertions(+), 40 deletions(-) create mode 100644 tracks/app/views/contexts/index_text.rhtml delete mode 100644 tracks/app/views/feed/contexts_text.rhtml create mode 100644 tracks/db/migrate/027_add_context_timestamps.rb diff --git a/tracks/app/controllers/contexts_controller.rb b/tracks/app/controllers/contexts_controller.rb index 9ee95aaf..efb3e297 100644 --- a/tracks/app/controllers/contexts_controller.rb +++ b/tracks/app/controllers/contexts_controller.rb @@ -4,12 +4,29 @@ class ContextsController < ApplicationController before_filter :init, :except => [:create, :destroy, :order] before_filter :init_todos, :only => :show + 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| ['rss','atom','txt'].include?(req.parameters[:format]) } def index - @page_title = "TRACKS::List Contexts" - respond_to do |wants| - wants.html - wants.xml { render :xml => @contexts.to_xml( :except => :user_id ) } + respond_to do |format| + format.html do + @page_title = "TRACKS::List Contexts" + render + end + format.xml { render :xml => @contexts.to_xml( :except => :user_id ) } + format.rss do + render_rss_feed_for @contexts, :feed => Context.feed_options(@user), + :item => { :description => lambda { |c| c.summary(count_undone_todos(c)) } } + end + format.atom do + render_atom_feed_for @contexts, :feed => Context.feed_options(@user), + :item => { :description => lambda { |c| c.summary(count_undone_todos(c)) }, + :author => lambda { |c| nil } } + end + format.text do + render :action => 'index_text', :layout => false, :content_type => Mime::TEXT + end end end diff --git a/tracks/app/controllers/feed_controller.rb b/tracks/app/controllers/feed_controller.rb index 33f6f9f4..7c977d2f 100644 --- a/tracks/app/controllers/feed_controller.rb +++ b/tracks/app/controllers/feed_controller.rb @@ -45,14 +45,7 @@ class FeedController < ApplicationController end headers["Content-Type"] = "text/calendar" end - - def list_contexts_only - init_not_done_counts('context') - @contexts = @user.contexts - @description = "Lists all the contexts for #{@user.login}." - render :action => 'contexts_' + params['feedtype'] - end - + protected # Check whether the token in the URL matches the word in the User's table diff --git a/tracks/app/helpers/feed_helper.rb b/tracks/app/helpers/feed_helper.rb index c38bc212..18cb2791 100644 --- a/tracks/app/helpers/feed_helper.rb +++ b/tracks/app/helpers/feed_helper.rb @@ -30,19 +30,7 @@ module FeedHelper end return result_string end - - def build_contexts_text_page(contexts) - result_string = "" - contexts.each do |c| - result_string << "\n" + c.name.upcase + "\n" - result_string << "#{count_undone_todos(c)}. Context is #{c.hidden? ? 'Hidden' : 'Active'}.\n" - result_string << "\n" - end - - return result_string - end - def format_ical_notes(notes) split_notes = notes.split(/\n/) joined_notes = split_notes.join("\\n") diff --git a/tracks/app/models/context.rb b/tracks/app/models/context.rb index 041abb11..2a0f5538 100644 --- a/tracks/app/models/context.rb +++ b/tracks/app/models/context.rb @@ -16,6 +16,13 @@ class Context < ActiveRecord::Base validates_does_not_contain :name, :string => '/', :message => "cannot contain the slash ('/') character" validates_does_not_contain :name, :string => ',', :message => "cannot contain the comma (',') character" + def self.feed_options(user) + { + :title => 'Tracks Contexts', + :description => "Lists all the contexts for #{user.display_name}." + } + end + def hidden? self.hide == true end @@ -23,5 +30,16 @@ class Context < ActiveRecord::Base def to_param url_friendly_name end - + + def title + name + end + + def summary(undone_todo_count) + s = "

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

" + s + end + end diff --git a/tracks/app/views/contexts/index_text.rhtml b/tracks/app/views/contexts/index_text.rhtml new file mode 100644 index 00000000..329e8a15 --- /dev/null +++ b/tracks/app/views/contexts/index_text.rhtml @@ -0,0 +1,5 @@ +<% @contexts.each do |c| -%> + +<%= c.name.upcase %> +<%= count_undone_todos(c)%>. Context is <%= c.hidden? ? "Hidden" : "Active" %>. +<% end -%> \ No newline at end of file diff --git a/tracks/app/views/feed/contexts_text.rhtml b/tracks/app/views/feed/contexts_text.rhtml deleted file mode 100644 index d3496d05..00000000 --- a/tracks/app/views/feed/contexts_text.rhtml +++ /dev/null @@ -1,2 +0,0 @@ -<% headers["Content-Type"] = "text/plain; charset=utf-8" -%> -<%= build_contexts_text_page( @contexts ) -%> diff --git a/tracks/app/views/feedlist/index.rhtml b/tracks/app/views/feedlist/index.rhtml index 37563476..7954bbf1 100644 --- a/tracks/app/views/feedlist/index.rhtml +++ b/tracks/app/views/feedlist/index.rhtml @@ -40,8 +40,8 @@ Actions completed in the last 7 days
  • - <%= rss_feed_link({ :action => 'list_contexts_only', :feedtype => 'rss' }) %> - <%= text_feed_link({ :action => 'list_contexts_only', :feedtype => 'text' }) %> + <%= rss_formatted_link({:controller => 'contexts', :action => 'index'}) %> + <%= text_formatted_link({:controller => 'contexts', :action => 'index'}) %> All Contexts
  • diff --git a/tracks/config/routes.rb b/tracks/config/routes.rb index 12908d19..83a2e19d 100644 --- a/tracks/config/routes.rb +++ b/tracks/config/routes.rb @@ -31,7 +31,6 @@ ActionController::Routing::Routes.draw do |map| # Context Routes map.resources :contexts, :collection => {:order => :post} map.connect 'context/:context/feed/:action/:login/:token', :controller => 'feed' - map.connect 'contexts/feed/:feedtype/:login/:token', :controller => 'feed', :action => 'list_contexts_only' # Projects Routes map.resources :projects, :collection => {:order => :post} diff --git a/tracks/db/migrate/027_add_context_timestamps.rb b/tracks/db/migrate/027_add_context_timestamps.rb new file mode 100644 index 00000000..8c2d8201 --- /dev/null +++ b/tracks/db/migrate/027_add_context_timestamps.rb @@ -0,0 +1,13 @@ +class AddContextTimestamps < ActiveRecord::Migration + + def self.up + add_column :contexts, :created_at, :timestamp + add_column :contexts, :updated_at, :timestamp + end + + def self.down + remove_column :contexts, :created_at + remove_column :contexts, :updated_at + end + +end diff --git a/tracks/db/schema.rb b/tracks/db/schema.rb index 388a2b41..5afc62ac 100644 --- a/tracks/db/schema.rb +++ b/tracks/db/schema.rb @@ -2,13 +2,15 @@ # migrations feature of ActiveRecord to incrementally modify your database, and # then regenerate this schema definition. -ActiveRecord::Schema.define(:version => 26) do +ActiveRecord::Schema.define(:version => 27) do create_table "contexts", :force => true do |t| - t.column "name", :string, :default => "", :null => false - t.column "hide", :integer, :limit => 4, :default => 0, :null => false - t.column "position", :integer, :default => 0, :null => false - t.column "user_id", :integer, :default => 0, :null => false + t.column "name", :string, :default => "", :null => false + t.column "hide", :integer, :limit => 4, :default => 0, :null => false + t.column "position", :integer, :default => 0, :null => false + t.column "user_id", :integer, :default => 0, :null => false + t.column "created_at", :datetime + t.column "updated_at", :datetime end add_index "contexts", ["user_id"], :name => "index_contexts_on_user_id" diff --git a/tracks/test/fixtures/contexts.yml b/tracks/test/fixtures/contexts.yml index 09cdea93..15d3af66 100644 --- a/tracks/test/fixtures/contexts.yml +++ b/tracks/test/fixtures/contexts.yml @@ -1,11 +1,19 @@ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +<% +def today + Time.now.utc.to_s(:db) +end + +%> agenda: id: 1 name: agenda position: 1 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> call: id: 2 @@ -13,6 +21,8 @@ call: position: 2 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> email: id: 3 @@ -20,6 +30,8 @@ email: position: 3 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> errand: id: 4 @@ -27,6 +39,8 @@ errand: position: 4 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> lab: id: 5 @@ -34,6 +48,8 @@ lab: position: 5 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> library: id: 6 @@ -41,6 +57,8 @@ library: position: 6 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> freetime: id: 7 @@ -48,6 +66,8 @@ freetime: position: 7 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> office: id: 8 @@ -55,6 +75,8 @@ office: position: 8 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> waitingfor: id: 9 @@ -62,3 +84,5 @@ waitingfor: position: 9 hide: false user_id: 1 + created_at: <%= today %> + updated_at: <%= today %> diff --git a/tracks/test/functional/contexts_controller_test.rb b/tracks/test/functional/contexts_controller_test.rb index 40724f0c..8804be75 100644 --- a/tracks/test/functional/contexts_controller_test.rb +++ b/tracks/test/functional/contexts_controller_test.rb @@ -39,5 +39,107 @@ class ContextsControllerTest < TodoContainerControllerTestBase assert_rjs :show, 'status' assert_rjs :update, 'status', "

    1 error prohibited this record from being saved

    There were problems with the following fields:

      Name cannot contain the slash ('/') character
    " end + + def test_rss_feed_content + @request.session['user_id'] = users(:admin_user).id + get :index, { :format => "rss" } + assert_equal 'application/rss+xml; charset=utf-8', @response.headers["Content-Type"] + #puts @response.body + + assert_xml_select 'rss[version="2.0"]' do + assert_xml_select 'channel' do + assert_xml_select '>title', 'Tracks Contexts' + assert_xml_select '>description', "Lists all the contexts for #{users(:admin_user).display_name}." + assert_xml_select 'language', 'en-us' + assert_xml_select 'ttl', '40' + end + assert_xml_select 'item', 9 do + assert_xml_select 'title', /.+/ + assert_xml_select 'description', /<p>\d+ actions. Context is (active|hidden). <\/p>/ + %w(guid link).each do |node| + assert_xml_select node, /http:\/\/test.host\/contexts\/.+/ + end + assert_xml_select 'pubDate', contexts(:agenda).created_at.to_s(:rfc822) + end + end + end + + def test_rss_feed_not_accessible_to_anonymous_user_without_token + @request.session['user_id'] = nil + get :index, { :format => "rss" } + assert_response 401 + end + + def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token + @request.session['user_id'] = nil + get :index, { :format => "rss", :token => 'foo' } + assert_response 401 + end + + def test_rss_feed_accessible_to_anonymous_user_with_valid_token + @request.session['user_id'] = nil + get :index, { :format => "rss", :token => users(:admin_user).word } + assert_response :ok + end + + def test_atom_feed_content + @request.session['user_id'] = users(:admin_user).id + get :index, { :format => "atom" } + assert_equal 'application/atom+xml; charset=utf-8', @response.headers["Content-Type"] + #puts @response.body + + assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do + assert_xml_select '>title', 'Tracks Contexts' + assert_xml_select '>subtitle', "Lists all the contexts for #{users(:admin_user).display_name}." + assert_xml_select 'entry', 3 do + assert_xml_select 'title', /.+/ + assert_xml_select 'content[type="html"]', /<p>\d+ actions. Context is (active|hidden). <\/p>/ + assert_xml_select 'published', contexts(:agenda).created_at.to_s(:rfc822) + end + end + end + + def test_atom_feed_not_accessible_to_anonymous_user_without_token + @request.session['user_id'] = nil + get :index, { :format => "atom" } + assert_response 401 + end + + def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token + @request.session['user_id'] = nil + get :index, { :format => "atom", :token => 'foo' } + assert_response 401 + end + + def test_atom_feed_accessible_to_anonymous_user_with_valid_token + @request.session['user_id'] = nil + get :index, { :format => "atom", :token => users(:admin_user).word } + assert_response :ok + end + + def test_text_feed_content + @request.session['user_id'] = users(:admin_user).id + get :index, { :format => "txt" } + assert_equal 'text/plain; charset=utf-8', @response.headers["Content-Type"] + #puts @response.body + end + + def test_text_feed_not_accessible_to_anonymous_user_without_token + @request.session['user_id'] = nil + get :index, { :format => "txt" } + assert_response 401 + end + + def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token + @request.session['user_id'] = nil + get :index, { :format => "txt", :token => 'foo' } + assert_response 401 + end + + def test_text_feed_accessible_to_anonymous_user_with_valid_token + @request.session['user_id'] = nil + get :index, { :format => "txt", :token => users(:admin_user).word } + assert_response :ok + end end diff --git a/tracks/test/integration/context_xml_api_test.rb b/tracks/test/integration/context_xml_api_test.rb index 685350e8..12cd23ab 100644 --- a/tracks/test/integration/context_xml_api_test.rb +++ b/tracks/test/integration/context_xml_api_test.rb @@ -4,7 +4,7 @@ require 'contexts_controller' # Re-raise errors caught by the controller. class ContextsController; def rescue_action(e) raise e end; end -class ContextsControllerXmlApiTest < ActionController::IntegrationTest +class ContextXmlApiTest < ActionController::IntegrationTest fixtures :users, :contexts @@context_name = "@newcontext" @@ -51,7 +51,7 @@ class ContextsControllerXmlApiTest < ActionController::IntegrationTest def test_creates_new_context initial_count = Context.count authenticated_post_xml_to_context_create - assert_response_and_body_matches 200, %r|^<\?xml version="1.0" encoding="UTF-8"\?>\n\n 0\n \d+\n #{@@context_name}\n 1\n\n$| + assert_response_and_body_matches 200, %r|^<\?xml version="1.0" encoding="UTF-8"\?>\n\n \d{4}+-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\n 0\n \d+\n #{@@context_name}\n 1\n \d{4}+-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\n\n$| assert_equal initial_count + 1, Context.count context1 = Context.find_by_name(@@context_name) assert_not_nil context1, "expected context '#{@@context_name}' to be created" diff --git a/tracks/test/integration/feed_smoke_test.rb b/tracks/test/integration/feed_smoke_test.rb index 438cd7f7..864bc841 100644 --- a/tracks/test/integration/feed_smoke_test.rb +++ b/tracks/test/integration/feed_smoke_test.rb @@ -72,11 +72,11 @@ class FeedSmokeTest < ActionController::IntegrationTest end def test_all_contexts_rss - assert_success "/contexts/feed/rss/admin/#{ users(:admin_user).word }" + assert_success "/contexts.rss?token=#{ users(:admin_user).word }" end def test_all_contexts_txt - assert_success "/contexts/feed/text/admin/#{ users(:admin_user).word }" + assert_success "/contexts.txt?token=#{ users(:admin_user).word }" end def test_all_projects_rss diff --git a/tracks/test/unit/context_test.rb b/tracks/test/unit/context_test.rb index a489a90b..3afdbe89 100644 --- a/tracks/test/unit/context_test.rb +++ b/tracks/test/unit/context_test.rb @@ -107,6 +107,9 @@ class ContextTest < Test::Unit::TestCase def test_to_param_returns_url_friendly_name assert_equal 'agenda', @agenda.to_param end - + + def test_title_reader_returns_name + assert_equal @agenda.name, @agenda.title + end end