diff --git a/tracks/app/controllers/todo_controller.rb b/tracks/app/controllers/todo_controller.rb
index af6eecd1..d87db7d6 100644
--- a/tracks/app/controllers/todo_controller.rb
+++ b/tracks/app/controllers/todo_controller.rb
@@ -33,6 +33,7 @@ class TodoController < ApplicationController
def create
@item = @user.todos.build
p = params['request'] || params
+ # @item.tag_with(params[:tag_list])
@item.attributes = p['todo']
if p['todo']['project_id'].blank? && !p['project_name'].blank? && p['project_name'] != 'None'
@@ -69,6 +70,7 @@ class TodoController < ApplicationController
@item.show_from = parse_date_per_user_prefs(p['todo']['show_from'])
end
+ @item.tag_with(params[:tag_list], @user)
@saved = @item.save
respond_to do |wants|
@@ -124,6 +126,7 @@ class TodoController < ApplicationController
def update
@item = check_user_return_item
+ @item.tag_with(params[:tag_list], @user)
@original_item_context_id = @item.context_id
@original_item_project_id = @item.project_id
@original_item_was_deferred = @item.deferred?
@@ -159,6 +162,8 @@ class TodoController < ApplicationController
params['item']['show_from'] = parse_date_per_user_prefs(params['item']['show_from'])
end
+
+
@saved = @item.update_attributes params["item"]
@context_changed = @original_item_context_id != @item.context_id
@item_was_activated_from_deferred_state = @original_item_was_deferred && @item.active?
@@ -234,6 +239,19 @@ class TodoController < ApplicationController
end
end
+ # /todo/tag/[tag_name] shows all the actions tagged with tag_name
+ #
+ def tag
+ @tag = tag_name = params[:id]
+ if Tag.find_by_name(tag_name)
+ @todos = Todo.find_tagged_with(tag_name, @user)
+ else
+ @todos = []
+ end
+
+ @count = @todos.size unless @todos.empty?
+ end
+
private
def check_user_return_item
diff --git a/tracks/app/models/todo.rb b/tracks/app/models/todo.rb
index e11c3807..9e525edb 100644
--- a/tracks/app/models/todo.rb
+++ b/tracks/app/models/todo.rb
@@ -5,6 +5,7 @@ class Todo < ActiveRecord::Base
belongs_to :project
belongs_to :user
+ acts_as_taggable
acts_as_state_machine :initial => :active, :column => 'state'
state :active, :enter => Proc.new { |t| t[:show_from] = nil }
diff --git a/tracks/app/models/user.rb b/tracks/app/models/user.rb
index 98717aa4..8a0f5d93 100644
--- a/tracks/app/models/user.rb
+++ b/tracks/app/models/user.rb
@@ -33,6 +33,8 @@ 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
diff --git a/tracks/app/views/shared/_add_new_item_form.rhtml b/tracks/app/views/shared/_add_new_item_form.rhtml
index a1da6b83..30c58da4 100644
--- a/tracks/app/views/shared/_add_new_item_form.rhtml
+++ b/tracks/app/views/shared/_add_new_item_form.rhtml
@@ -43,14 +43,17 @@ Event.observe($('todo_project_name'), "focus", projectAutoCompleter.activate.bin
Event.observe($('todo_project_name'), "click", projectAutoCompleter.activate.bind(projectAutoCompleter));
+
+<%= text_field_tag "tag_list", nil, :size => 40, :tabindex => 5 %>
+
-<%= text_field("todo", "due", "size" => 10, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 5, "autocomplete" => "off") %>
+<%= text_field("todo", "due", "size" => 10, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 6, "autocomplete" => "off") %>
- <%= text_field("todo", "show_from", "size" => 10, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 6, "autocomplete" => "off") %>
+ <%= text_field("todo", "show_from", "size" => 10, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 7, "autocomplete" => "off") %>
<%= source_view_tag( @source_view ) %>
-
+
<% end -%>
diff --git a/tracks/app/views/todo/_edit_form.rhtml b/tracks/app/views/todo/_edit_form.rhtml
index 632ab413..90bb49e6 100644
--- a/tracks/app/views/todo/_edit_form.rhtml
+++ b/tracks/app/views/todo/_edit_form.rhtml
@@ -34,6 +34,10 @@
Event.observe($('<%= dom_id(@item, 'project_name') %>'), "click", editFormProjectAutoCompleter.activate.bind(editFormProjectAutoCompleter));
+
+ |
+ <%= text_field_tag "tag_list", @item.tags.collect{|t| t.name}.join(" "), :size => 40 %> |
+
|
|
diff --git a/tracks/app/views/todo/_item.rhtml b/tracks/app/views/todo/_item.rhtml
index 155e837d..b985d3e5 100644
--- a/tracks/app/views/todo/_item.rhtml
+++ b/tracks/app/views/todo/_item.rhtml
@@ -18,6 +18,17 @@
<% end -%>
<%= sanitize(item.description) %>
+
+ <%= if item.tags.blank?
+ ""
+ else
+ tag_string = ""
+ item.tags.each do |t|
+ tag_string << "" + link_to(t.name, :action => "tag", :id => t.name) + ""
+ end
+ tag_string
+ end
+ %>
<% if item.deferred? && item.due -%>
(action due on <%= format_date(item.due) %>)
@@ -32,6 +43,10 @@
<% if (parent_container_type == "context" || parent_container_type == "tickler") && item.project_id -%>
<%= item_link_to_project( item ) %>
<% end -%>
+ <% if (parent_container_type == "tag") -%>
+ <%= item_link_to_context( item ) %>
+ <%= item_link_to_project( item ) if item.project_id %>
+ <% end -%>
<% end -%>
<% if item.notes? -%>
diff --git a/tracks/app/views/todo/tag.rhtml b/tracks/app/views/todo/tag.rhtml
new file mode 100644
index 00000000..2a5b76d5
--- /dev/null
+++ b/tracks/app/views/todo/tag.rhtml
@@ -0,0 +1,22 @@
+
+
+
+
+ All actions tagged with '<%= @tag %>'
+
+
+
+
Currently there are no actions tagged with <%= @tag %>
+
+ <%= render :partial => "todo/item", :collection => @todos, :locals => { :parent_container_type => "tag" } %>
+
+
+
+
+
+
+
+
+ <%= render :partial => "shared/add_new_item_form" %>
+ <%= render "sidebar/sidebar" %>
+
\ No newline at end of file
diff --git a/tracks/db/migrate/024_add_tag_support.rb b/tracks/db/migrate/024_add_tag_support.rb
new file mode 100644
index 00000000..9028fbd1
--- /dev/null
+++ b/tracks/db/migrate/024_add_tag_support.rb
@@ -0,0 +1,23 @@
+class AddTagSupport < ActiveRecord::Migration
+ def self.up
+ create_table :taggings do |t|
+ t.column :taggable_id, :integer
+ t.column :tag_id, :integer
+ t.column :taggable_type, :string
+ t.column :user_id, :integer
+ end
+ create_table :tags do |t|
+ t.column :name, :string
+ t.column :created_at, :datetime
+ t.column :updated_at, :datetime
+ end
+
+ add_index :tags, :name
+ add_index :taggings, [:tag_id, :taggable_id, :taggable_type]
+ end
+
+ def self.down
+ drop_table :taggings
+ drop_table :tags
+ end
+end
diff --git a/tracks/db/schema.rb b/tracks/db/schema.rb
index ece273c4..6aa2ff83 100644
--- a/tracks/db/schema.rb
+++ b/tracks/db/schema.rb
@@ -2,13 +2,13 @@
# migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition.
-ActiveRecord::Schema.define(:version => 23) do
+ActiveRecord::Schema.define(:version => 24) 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 "position", :integer, :default => 0, :null => false
+ t.column "hide", :boolean, :default => false
+ t.column "user_id", :integer, :default => 1
end
add_index "contexts", ["user_id"], :name => "index_contexts_on_user_id"
@@ -61,7 +61,7 @@ ActiveRecord::Schema.define(:version => 23) do
create_table "projects", :force => true do |t|
t.column "name", :string, :default => "", :null => false
t.column "position", :integer, :default => 0, :null => false
- t.column "user_id", :integer, :default => 0, :null => false
+ t.column "user_id", :integer, :default => 1
t.column "description", :text
t.column "state", :string, :limit => 20, :default => "active", :null => false
end
@@ -76,17 +76,34 @@ ActiveRecord::Schema.define(:version => 23) do
add_index "sessions", ["session_id"], :name => "sessions_session_id_index"
+ create_table "taggings", :force => true do |t|
+ t.column "taggable_id", :integer
+ t.column "tag_id", :integer
+ t.column "taggable_type", :string
+ t.column "user_id", :integer
+ end
+
+ add_index "taggings", ["tag_id", "taggable_id", "taggable_type"], :name => "index_taggings_on_tag_id_and_taggable_id_and_taggable_type"
+
+ create_table "tags", :force => true do |t|
+ t.column "name", :string
+ t.column "created_at", :datetime
+ t.column "updated_at", :datetime
+ end
+
+ add_index "tags", ["name"], :name => "index_tags_on_name"
+
create_table "todos", :force => true do |t|
- t.column "context_id", :integer, :default => 0, :null => false
- t.column "description", :string, :limit => 100, :default => "", :null => false
+ t.column "context_id", :integer, :default => 0, :null => false
+ t.column "project_id", :integer
+ t.column "description", :string, :default => "", :null => false
t.column "notes", :text
t.column "created_at", :datetime
t.column "due", :date
t.column "completed_at", :datetime
- t.column "project_id", :integer
- t.column "user_id", :integer, :default => 0, :null => false
+ t.column "user_id", :integer, :default => 1
t.column "show_from", :date
- t.column "state", :string, :limit => 20, :default => "immediate", :null => false
+ t.column "state", :string, :limit => 20, :default => "immediate", :null => false
end
add_index "todos", ["user_id", "state"], :name => "index_todos_on_user_id_and_state"
@@ -96,10 +113,10 @@ ActiveRecord::Schema.define(:version => 23) do
add_index "todos", ["user_id", "context_id"], :name => "index_todos_on_user_id_and_context_id"
create_table "users", :force => true do |t|
- t.column "login", :string, :limit => 80
- t.column "password", :string, :limit => 40
+ t.column "login", :string, :limit => 80, :default => "", :null => false
+ t.column "password", :string, :limit => 40, :default => "", :null => false
t.column "word", :string
- t.column "is_admin", :integer, :limit => 4, :default => 0, :null => false
+ t.column "is_admin", :boolean, :default => false, :null => false
t.column "first_name", :string
t.column "last_name", :string
t.column "auth_type", :string, :default => "database", :null => false
diff --git a/tracks/public/stylesheets/standard.css b/tracks/public/stylesheets/standard.css
index 86ff73a2..7394c717 100644
--- a/tracks/public/stylesheets/standard.css
+++ b/tracks/public/stylesheets/standard.css
@@ -206,7 +206,7 @@ div#input_box {
}
div.item-container {
- padding: 2px;
+ padding: 3px;
clear: both;
}
@@ -317,6 +317,28 @@ div#project_status > div {
a.footer_link {color: #cc3334; font-style: normal;}
a.footer_link:hover {color: #fff; background-color: #cc3334 !important;}
+/* Tag formatting */
+
+span.tag {
+ font-size: 0.8em;
+ background-color: #CCE7FF;
+ color: #000;
+ padding: 1px;
+ margin-right: 2px;
+}
+
+span.tag a,
+span.tag a:link,
+span.tag a:active,
+span.tag a:visited {
+ color: #000;
+}
+
+span.tag a:hover {
+ background-color: #99CCFF;
+ color: #333;
+}
+
/* Flash box styling */
div#message_holder {
diff --git a/tracks/test/fixtures/taggings.yml b/tracks/test/fixtures/taggings.yml
new file mode 100644
index 00000000..bc444fb1
--- /dev/null
+++ b/tracks/test/fixtures/taggings.yml
@@ -0,0 +1,22 @@
+# Todo 1 should be tagged with foo and bar
+foo_bar1:
+ id: 1
+ 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
+foo:
+ id: 3
+ tag_id: 1
+ taggable_id: 2 # Call dinosaur exterminator
+ taggable_type: Todo
+ user_id: 1
diff --git a/tracks/test/fixtures/tags.yml b/tracks/test/fixtures/tags.yml
new file mode 100644
index 00000000..1bd59ebc
--- /dev/null
+++ b/tracks/test/fixtures/tags.yml
@@ -0,0 +1,17 @@
+foo:
+ id: 1
+ name: foo
+ created_at: <%= Time.now.utc.to_s(:db) %>
+ updated_at: <%= Time.now.utc.to_s(:db) %>
+
+bar:
+ id: 2
+ name: bar
+ created_at: <%= Time.now.utc.to_s(:db) %>
+ updated_at: <%= Time.now.utc.to_s(:db) %>
+
+baz:
+ id: 3
+ name: baz
+ created_at: <%= Time.now.utc.to_s(:db) %>
+ updated_at: <%= Time.now.utc.to_s(:db) %>
diff --git a/tracks/test/functional/todo_controller_test.rb b/tracks/test/functional/todo_controller_test.rb
index 5b958c47..f628cf8c 100644
--- a/tracks/test/functional/todo_controller_test.rb
+++ b/tracks/test/functional/todo_controller_test.rb
@@ -5,7 +5,7 @@ require 'todo_controller'
class TodoController; def rescue_action(e) raise e end; end
class TodoControllerTest < Test::Unit::TestCase
- fixtures :users, :preferences, :projects, :contexts, :todos
+ fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings
def setup
@controller = TodoController.new
@@ -67,7 +67,7 @@ class TodoControllerTest < Test::Unit::TestCase
def test_update_item
t = Todo.find(1)
@request.session['user_id'] = users(:admin_user).id
- xhr :post, :update, :id => 1, :_source_view => 'todo', "item"=>{"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"}
+ xhr :post, :update, :id => 1, :_source_view => 'todo', "item"=>{"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"
#assert_rjs :page, "todo_1", :visual_effect, :highlight, :duration => '1'
t = Todo.find(1)
assert_equal "Call Warren Buffet to find out how much he makes per day", t.description
@@ -76,5 +76,14 @@ class TodoControllerTest < Test::Unit::TestCase
assert_equal expected, actual, "Expected #{expected.to_s(:db)}, was #{actual.to_s(:db)}"
end
+ def test_tag
+ @request.session['user_id'] = users(:admin_user).id
+ @user = User.find(@request.session['user_id'])
+ @tagged = Todo.find_tagged_with('foo', @user).size
+ get :tag, :id => 'foo'
+ assert_success
+ assert_equal 2, @tagged
+ end
+
end
diff --git a/tracks/vendor/plugins/acts_as_taggable/init.rb b/tracks/vendor/plugins/acts_as_taggable/init.rb
new file mode 100644
index 00000000..e795ee50
--- /dev/null
+++ b/tracks/vendor/plugins/acts_as_taggable/init.rb
@@ -0,0 +1,5 @@
+require 'acts_as_taggable'
+ActiveRecord::Base.send(:include, ActiveRecord::Acts::Taggable)
+
+require File.dirname(__FILE__) + '/lib/tagging'
+require File.dirname(__FILE__) + '/lib/tag'
\ No newline at end of file
diff --git a/tracks/vendor/plugins/acts_as_taggable/lib/README b/tracks/vendor/plugins/acts_as_taggable/lib/README
new file mode 100644
index 00000000..8d2f9082
--- /dev/null
+++ b/tracks/vendor/plugins/acts_as_taggable/lib/README
@@ -0,0 +1,4 @@
+Acts As Taggable
+=================
+
+Allows for tags to be added to multiple classes.
\ No newline at end of file
diff --git a/tracks/vendor/plugins/acts_as_taggable/lib/acts_as_taggable.rb b/tracks/vendor/plugins/acts_as_taggable/lib/acts_as_taggable.rb
new file mode 100644
index 00000000..21570f2f
--- /dev/null
+++ b/tracks/vendor/plugins/acts_as_taggable/lib/acts_as_taggable.rb
@@ -0,0 +1,59 @@
+module ActiveRecord
+ module Acts #:nodoc:
+ module Taggable #:nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def acts_as_taggable(options = {})
+ write_inheritable_attribute(:acts_as_taggable_options, {
+ :taggable_type => ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s,
+ :from => options[:from]
+ })
+
+ class_inheritable_reader :acts_as_taggable_options
+
+ has_many :taggings, :as => :taggable, :dependent => true
+ has_many :tags, :through => :taggings
+
+ include ActiveRecord::Acts::Taggable::InstanceMethods
+ extend ActiveRecord::Acts::Taggable::SingletonMethods
+ end
+ end
+
+ module SingletonMethods
+ def find_tagged_with(list, user)
+ find_by_sql([
+ "SELECT #{table_name}.* FROM #{table_name}, tags, taggings " +
+ "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " +
+ "AND taggings.user_id = ? " +
+ "AND taggings.taggable_type = ? " +
+ "AND taggings.tag_id = tags.id AND tags.name IN (?)",
+ user.id, acts_as_taggable_options[:taggable_type], list
+ ])
+ end
+ end
+
+ module InstanceMethods
+ def tag_with(list, user)
+ Tag.transaction do
+ taggings.destroy_all
+
+ Tag.parse(list).each do |name|
+ if acts_as_taggable_options[:from]
+ send(acts_as_taggable_options[:from]).tags.find_or_create_by_name(name).on(self, user)
+ else
+ Tag.find_or_create_by_name(name).on(self, user)
+ end
+ end
+ end
+ end
+
+ def tag_list
+ tags.collect { |tag| tag.name.include?(" ") ? '"' + tag.name + '"' : tag.name }.join(" ")
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/tracks/vendor/plugins/acts_as_taggable/lib/tag.rb b/tracks/vendor/plugins/acts_as_taggable/lib/tag.rb
new file mode 100644
index 00000000..fce3266c
--- /dev/null
+++ b/tracks/vendor/plugins/acts_as_taggable/lib/tag.rb
@@ -0,0 +1,40 @@
+class Tag < ActiveRecord::Base
+ has_many :taggings
+
+ def self.parse(list)
+ tag_names = []
+
+ # first, pull out the quoted tags
+ list.gsub!(/\"(.*?)\"\s*/ ) { tag_names << $1; "" }
+
+ # then, replace all commas with a space
+ list.gsub!(/,/, " ")
+
+ # then, get whatever's left
+ tag_names.concat list.split(/\s/)
+
+ # strip whitespace from the names
+ tag_names = tag_names.map { |t| t.strip }
+
+ # delete any blank tag names
+ tag_names = tag_names.delete_if { |t| t.empty? }
+
+ return tag_names
+ end
+
+ def tagged
+ @tagged ||= taggings.collect { |tagging| tagging.taggable }
+ end
+
+ def on(taggable, user)
+ tagging = taggings.create :taggable => taggable, :user => user
+ end
+
+ def ==(comparison_object)
+ super || name == comparison_object.to_s
+ end
+
+ def to_s
+ name
+ end
+end
\ No newline at end of file
diff --git a/tracks/vendor/plugins/acts_as_taggable/lib/tagging.rb b/tracks/vendor/plugins/acts_as_taggable/lib/tagging.rb
new file mode 100644
index 00000000..f806c439
--- /dev/null
+++ b/tracks/vendor/plugins/acts_as_taggable/lib/tagging.rb
@@ -0,0 +1,13 @@
+class Tagging < ActiveRecord::Base
+ belongs_to :tag
+ belongs_to :taggable, :polymorphic => true
+ belongs_to :user
+
+ def self.tagged_class(taggable)
+ ActiveRecord::Base.send(:class_name_of_active_record_descendant, taggable.class).to_s
+ end
+
+ def self.find_taggable(tagged_class, tagged_id)
+ tagged_class.constantize.find(tagged_id)
+ end
+end
\ No newline at end of file
diff --git a/tracks/vendor/plugins/acts_as_taggable/test/acts_as_taggable_test.rb b/tracks/vendor/plugins/acts_as_taggable/test/acts_as_taggable_test.rb
new file mode 100644
index 00000000..fe6be2e3
--- /dev/null
+++ b/tracks/vendor/plugins/acts_as_taggable/test/acts_as_taggable_test.rb
@@ -0,0 +1 @@
+# Testing goes here
\ No newline at end of file