Merge branch 'master' of git://github.com/bsag/tracks

This commit is contained in:
Reinier Balt 2008-12-23 12:11:40 +01:00
commit 1d0ed34cea
41 changed files with 3620 additions and 3331 deletions

View file

@ -175,7 +175,7 @@ class ApplicationController < ActionController::Base
saved = todo.save
if saved
todo.tag_with(rt.tag_list, current_user)
todo.tag_with(rt.tag_list)
todo.tags.reload
end

View file

@ -23,6 +23,7 @@ class ContextsController < ApplicationController
end
def show
@contexts = current_user.contexts(true)
if (@context.nil?)
respond_to do |format|
format.html { render :text => 'Context not found', :status => 404 }

View file

@ -30,7 +30,7 @@ class RecurringTodosController < ApplicationController
end
def update
@recurring_todo.tag_with(params[:tag_list], current_user) if params[:tag_list]
@recurring_todo.tag_with(params[:tag_list]) if params[:tag_list]
@original_item_context_id = @recurring_todo.context_id
@original_item_project_id = @recurring_todo.project_id
@ -106,7 +106,7 @@ class RecurringTodosController < ApplicationController
@recurring_saved = @recurring_todo.save
unless (@recurring_saved == false) || p.tag_list.blank?
@recurring_todo.tag_with(p.tag_list, current_user)
@recurring_todo.tag_with(p.tag_list)
@recurring_todo.tags.reload
end

View file

@ -67,7 +67,7 @@ class TodosController < ApplicationController
@saved = @todo.save
unless (@saved == false) || p.tag_list.blank?
@todo.tag_with(p.tag_list, current_user)
@todo.tag_with(p.tag_list)
@todo.tags.reload
end
@ -175,7 +175,7 @@ class TodosController < ApplicationController
def update
@source_view = params['_source_view'] || 'todo'
init_data_for_sidebar unless mobile?
@todo.tag_with(params[:tag_list], current_user) if params[:tag_list]
@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?

View file

@ -258,7 +258,7 @@ module TodosHelper
def formatted_pagination(total)
s = will_paginate(@todos)
(s.gsub /(<\/[^<]+>)/, '\1 ').chomp(' ')
(s.gsub(/(<\/[^<]+>)/, '\1 ')).chomp(' ')
end
def date_field_tag(name, id, value = nil, options = {})

View file

@ -369,7 +369,7 @@ class RecurringTodo < ActiveRecord::Base
case self.target
when 'due_date'
return get_next_date(previous)
when 'show_from'
when 'show_from_date'
# so leave due date empty
return nil
else
@ -623,10 +623,10 @@ class RecurringTodo < ActiveRecord::Base
def toggle_star!
if starred?
delete_tags Todo::STARRED_TAG_NAME
_remove_tags Todo::STARRED_TAG_NAME
tags.reload
else
add_tag Todo::STARRED_TAG_NAME
_add_tags(Todo::STARRED_TAG_NAME)
tags.reload
end
starred?

View file

@ -1,11 +1,48 @@
# The Tag model. This model is automatically generated and added to your app if
# you run the tagging generator included with has_many_polymorphs.
class Tag < ActiveRecord::Base
DELIMITER = "," # Controls how to split and join tagnames from strings. You may need to change the <tt>validates_format_of parameters</tt> if you change this.
JOIN_DELIMITER = ", "
# If database speed becomes an issue, you could remove these validations and
# rescue the ActiveRecord database constraint errors instead.
validates_presence_of :name
validates_uniqueness_of :name, :case_sensitive => false
# Change this validation if you need more complex tag names.
# validates_format_of :name, :with => /^[a-zA-Z0-9\_\-]+$/, :message => "can not contain special characters"
# Set up the polymorphic relationship.
has_many_polymorphs :taggables,
:from => [:todos, :recurring_todos],
:through => :taggings,
:dependent => :destroy
:dependent => :destroy,
:skip_duplicates => false,
:parent_extend => proc {
# Defined on the taggable models, not on Tag itself. Return the tagnames
# associated with this record as a string.
def to_s
self.map(&:name).sort.join(Tag::JOIN_DELIMITER)
end
}
# Callback to strip extra spaces from the tagname before saving it. If you
# allow tags to be renamed later, you might want to use the
# <tt>before_save</tt> callback instead.
def before_create
self.name = name.downcase.strip.squeeze(" ")
end
def on(taggable, user)
tagging = taggings.create :taggable => taggable, :user => user
taggings.create :taggable => taggable, :user => user
end
# Tag::Error class. Raised by ActiveRecord::Base::TaggingExtensions if
# something goes wrong.
class Error < StandardError
end
end

View file

@ -1,11 +1,17 @@
# The Tagging join model. This model is automatically generated and added to your app if you run the tagging generator included with has_many_polymorphs.
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :taggable, :polymorphic => true
belongs_to :user
# belongs_to :user
# def before_destroy
# # disallow orphaned tags
# # TODO: this doesn't seem to be working
# tag.destroy if tag.taggings.count < 2
# end
# If you also need to use <tt>acts_as_list</tt>, 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
# This callback makes sure that an orphaned <tt>Tag</tt> is deleted if it no longer tags anything.
def after_destroy
tag.destroy_without_callbacks if tag and tag.taggings.count == 0
end
end

View file

@ -118,10 +118,10 @@ class Todo < ActiveRecord::Base
def toggle_star!
if starred?
delete_tags STARRED_TAG_NAME
_remove_tags STARRED_TAG_NAME
tags.reload
else
add_tag STARRED_TAG_NAME
_add_tags(STARRED_TAG_NAME)
tags.reload
end
starred?
@ -134,7 +134,7 @@ class Todo < ActiveRecord::Base
# Rich Todo API
def self.from_rich_message(user, default_context_id, description, notes)
fields = description.match /([^>@]*)@?([^>]*)>?(.*)/
fields = description.match(/([^>@]*)@?([^>]*)>?(.*)/)
description = fields[1].strip
context = fields[2].strip
project = fields[3].strip

View file

@ -24,7 +24,21 @@
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' },
:before => "$('todo_new_action_submit').startWaiting()",
:complete => "$('todo_new_action_submit').stopWaiting()",
:condition => "!$('todo_new_action_submit').isWaiting()") do -%>
:condition => "!$('todo_new_action_submit').isWaiting() && askIfNewContextProvided()") do -%>
<script type="text/javascript" charset="utf-8">
function askIfNewContextProvided() {
var contexts = new Array(<%= @contexts.map{|c| '\'' + c.name + '\''}.join(", ") %>);
var givenContextName = $('todo_context_name').value;
if (givenContextName.length == 0) return true; // show rails validation error
for (var i = 0; i < contexts.length; ++i) {
if (contexts[i] == givenContextName) {
return true;
}
}
return confirm('New context "' + givenContextName + '" will be also created. Are you sure?');
}
</script>
<div id="status"><%= error_messages_for("item", :object_name => 'action') %></div>

View file

@ -14,12 +14,12 @@ end -%>
<% end -%>
<%= date_span -%> <%= link_to mobile_todo.description, formatted_todo_path(mobile_todo, :m) -%>
<% if parent_container_type == 'context' or parent_container_type == 'tag' -%>
<%= "<span class=prj> (" +
<%= "<span class=\"prj\"> (" +
link_to(mobile_todo.project.name, formatted_project_path(mobile_todo.project, :m)) +
")</span>" unless mobile_todo.project.nil? -%>
<% end
if parent_container_type == 'project' or parent_container_type == 'tag' -%>
<%= "<span class=ctx> (" +
<%= "<span class=\"ctx\"> (" +
link_to(mobile_todo.context.name, formatted_context_path(mobile_todo.context, :m)) +
")</span>" -%>
<% end -%>

View file

@ -26,6 +26,8 @@ Rails::Initializer.run do |config|
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
@ -80,7 +82,7 @@ end
require 'name_part_finder'
require 'tracks/todo_list'
require 'tracks/config'
require 'activerecord_base_tag_extensions' # Needed for tagging-specific extensions
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'

View file

@ -1,30 +0,0 @@
class ActiveRecord::Base
# These methods will work for any model instances
# Tag with deletes all the current tags before adding the new ones
# This makes the edit form more intiuitive:
# Whatever is in the tags text field is what gets set as the tags for that action
# If you submit an empty tags text field, all the tags are removed.
def tag_with(tags, user)
Tag.transaction do
Tagging.delete_all("taggable_id = #{self.id} and taggable_type = '#{self.class}' and user_id = #{user.id}")
tags.downcase.split(",").each do |tag|
Tag.find_or_create_by_name(tag.strip).on(self, user)
end
end
end
def tag_list
tags.map(&:name).join(', ')
end
def delete_tags tag_string
split = tag_string.downcase.split(",")
tags.delete tags.select{|t| split.include? t.name.strip}
end
def add_tag tag_name
Tag.find_or_create_by_name(tag_name.strip).on(self,user)
end
end

148
lib/tagging_extensions.rb Normal file
View file

@ -0,0 +1,148 @@
class ActiveRecord::Base #:nodoc:
# These extensions make models taggable. This file is automatically generated and required by your app if you run the tagging generator included with has_many_polymorphs.
module TaggingExtensions
# Add tags to <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
#
# We need to avoid name conflicts with the built-in ActiveRecord association methods, thus the underscores.
def _add_tags incoming
taggable?(true)
tag_cast_to_string(incoming).each do |tag_name|
begin
tag = Tag.find_or_create_by_name(tag_name)
raise Tag::Error, "tag could not be saved: #{tag_name}" if tag.new_record?
tag.taggables << self
rescue ActiveRecord::StatementInvalid => e
raise unless e.to_s =~ /duplicate/i
end
end
end
# Removes tags from <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
def _remove_tags outgoing
taggable?(true)
outgoing = tag_cast_to_string(outgoing)
tags.delete(*(tags.select do |tag|
outgoing.include? tag.name
end))
end
# Returns the tags on <tt>self</tt> as a string.
def tag_list
# Redefined later to avoid an RDoc parse error.
end
# Replace the existing tags on <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
def tag_with list
#:stopdoc:
taggable?(true)
list = tag_cast_to_string(list)
# Transactions may not be ideal for you here; be aware.
Tag.transaction do
current = tags.map(&:name)
_add_tags(list - current)
_remove_tags(current - list)
end
self
#:startdoc:
end
# Returns the tags on <tt>self</tt> as a string.
def tag_list #:nodoc:
#:stopdoc:
taggable?(true)
tags.reload
tags.to_s
#:startdoc:
end
private
def tag_cast_to_string obj #:nodoc:
case obj
when Array
obj.map! do |item|
case item
# removed next line: its prevents adding numbers as tags
# when /^\d+$/, Fixnum then Tag.find(item).name # This will be slow if you use ids a lot.
when Tag then item.name
when String then item
else
raise "Invalid type"
end
end
when String
obj = obj.split(Tag::DELIMITER).map do |tag_name|
tag_name.strip.squeeze(" ")
end
else
raise "Invalid object of class #{obj.class} as tagging method parameter"
end.flatten.compact.map(&:downcase).uniq
end
# Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model.
def taggable?(should_raise = false) #:nodoc:
unless flag = respond_to?(:tags)
raise "#{self.class} is not a taggable model" if should_raise
end
flag
end
end
module TaggingFinders
#
# Find all the objects tagged with the supplied list of tags
#
# Usage : Model.tagged_with("ruby")
# Model.tagged_with("hello", "world")
# Model.tagged_with("hello", "world", :limit => 10)
#
def tagged_with(*tag_list)
options = tag_list.last.is_a?(Hash) ? tag_list.pop : {}
tag_list = parse_tags(tag_list)
scope = scope(:find)
options[:select] ||= "#{table_name}.*"
options[:from] ||= "#{table_name}, tags, taggings"
sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} "
add_joins!(sql, options, scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "
sql << "AND taggings.tag_id = tags.id "
tag_list_condition = tag_list.map {|t| "'#{t}'"}.join(", ")
sql << "AND (tags.name IN (#{sanitize_sql(tag_list_condition)})) "
sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions]
sql << "GROUP BY #{table_name}.id "
sql << "HAVING COUNT(taggings.tag_id) = #{tag_list.size}"
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope)
add_lock!(sql, options, scope)
find_by_sql(sql)
end
def parse_tags(tags)
return [] if tags.blank?
tags = Array(tags).first
tags = tags.respond_to?(:flatten) ? tags.flatten : tags.split(Tag::DELIMITER)
tags.map { |tag| tag.strip.squeeze(" ") }.flatten.compact.map(&:downcase).uniq
end
end
include TaggingExtensions
extend TaggingFinders
end

View file

@ -223,8 +223,8 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
login_as :admin_user
u = users(:admin_user)
post :actionize, :state => "active", :format => 'js'
assert_equal 2, projects(:gardenclean).position
assert_equal 1, projects(:moremoney).position
assert_equal 1, projects(:gardenclean).position
assert_equal 2, projects(:moremoney).position
assert_equal 3, projects(:timemachine).position
end

View file

@ -135,7 +135,7 @@ class TodosControllerTest < Test::Rails::TestCase
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 "foo, bar", t.tag_list
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)}"
@ -163,7 +163,7 @@ class TodosControllerTest < Test::Rails::TestCase
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 "one, two, three, four, 8.1.2, version1.5", t.tag_list
assert_equal "8.1.2, four, one, three, two, version1.5", t.tag_list
end
def test_find_tagged_with

View file

@ -4,7 +4,7 @@ login :as => 'admin'
open '/m'
wait_for_text 'css=h1 span.count', '11'
click_and_wait "link=0-Add new action"
click_and_wait "link=0-New action"
type "todo_notes", "test notes"
type "todo_description", "test name"

View file

@ -251,7 +251,7 @@ class RecurringTodoTest < Test::Rails::TestCase
end
def test_starred
@yearly.tag_with("1, 2, starred", User.find(@yearly.user_id))
@yearly.tag_with("1, 2, starred")
@yearly.tags.reload
assert_equal true, @yearly.starred?

View file

@ -4,7 +4,26 @@ class TagTest < Test::Rails::TestCase
fixtures :tags
# Replace this with your real tests.
def test_truth
assert true
def test_find_or_create_with_single_word
tag = Tag.find_or_create_by_name("test")
assert !tag.new_record?
end
def test_find_or_create_with_space
tag = Tag.find_or_create_by_name("test test")
assert !tag.new_record?
end
def test_find_or_create_with_dot
tag = Tag.find_or_create_by_name("a.b.c")
assert !tag.new_record?
end
def test_find_or_create_with_number_as_string
tag = Tag.find_or_create_by_name("12343")
assert !tag.new_record?
tag = Tag.find_or_create_by_name("8.1.2")
assert !tag.new_record?
end
end

View file

@ -163,7 +163,7 @@ class TodoTest < Test::Rails::TestCase
end
def test_todo_is_starred_after_starred_tag_is_added
@not_completed1.add_tag('starred')
@not_completed1._add_tags('starred')
assert @not_completed1.starred?
end

View file

@ -0,0 +1,87 @@
--- !ruby/object:Gem::Specification
name: highline
version: !ruby/object:Gem::Version
version: 1.5.0
platform: ruby
authors:
- James Edward Gray II
autorequire:
bindir: bin
cert_chain: []
date: 2008-11-05 00:00:00 +01:00
default_executable:
dependencies: []
description: A high-level IO library that provides validation, type conversion, and more for command-line interfaces. HighLine also includes a complete menu system that can crank out anything from simple list selection to complete shells with just minutes of work.
email: james@grayproductions.net
executables: []
extensions: []
extra_rdoc_files:
- README
- INSTALL
- TODO
- CHANGELOG
- LICENSE
files:
- examples/ansi_colors.rb
- examples/asking_for_arrays.rb
- examples/basic_usage.rb
- examples/color_scheme.rb
- examples/menus.rb
- examples/overwrite.rb
- examples/page_and_wrap.rb
- examples/password.rb
- examples/trapping_eof.rb
- examples/using_readline.rb
- lib/highline/color_scheme.rb
- lib/highline/import.rb
- lib/highline/menu.rb
- lib/highline/question.rb
- lib/highline/system_extensions.rb
- lib/highline.rb
- test/tc_color_scheme.rb
- test/tc_highline.rb
- test/tc_import.rb
- test/tc_menu.rb
- test/ts_all.rb
- Rakefile
- setup.rb
- README
- INSTALL
- TODO
- CHANGELOG
- LICENSE
has_rdoc: true
homepage: http://highline.rubyforge.org
post_install_message:
rdoc_options:
- --title
- HighLine Documentation
- --main
- README
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
rubyforge_project: highline
rubygems_version: 1.3.1
signing_key:
specification_version: 2
summary: HighLine is a high-level command-line IO library.
test_files:
- test/ts_all.rb

View file

@ -8,7 +8,7 @@ authors:
autorequire: openid
bindir: bin
cert_chain:
date: 2008-06-27 00:00:00 -04:00
date: 2008-06-27 05:00:00 +01:00
default_executable:
dependencies: []
@ -265,6 +265,7 @@ rdoc_options:
- --main
- README
require_paths:
- bin
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
@ -281,7 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
requirements: []
rubyforge_project:
rubygems_version: 1.2.0
rubygems_version: 1.0.1
signing_key:
specification_version: 1
summary: A library for consuming and serving OpenID identities.

View file

@ -120,7 +120,7 @@ class ActiveRecord::Base #:nodoc:
sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} "
add_joins!(sql, options, scope)
add_joins!(sql, options[:joins], scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "

View file

@ -360,11 +360,15 @@ Be aware, however, that <tt>NULL != 'Spot'</tt> returns <tt>false</tt> due to SQ
begin
table = plural._as_class.table_name
rescue NameError => e
raise PolymorphicError, "Could not find a valid class for #{plural.inspect}. If it's namespaced, be sure to specify it as :\"module/#{plural}\" instead."
raise PolymorphicError, "Could not find a valid class for #{plural.inspect} (tried #{plural.to_s._classify}). If it's namespaced, be sure to specify it as :\"module/#{plural}\" instead."
end
begin
plural._as_class.columns.map(&:name).each_with_index do |field, f_index|
aliases["#{table}.#{field}"] = "t#{t_index}_r#{f_index}"
end
rescue ActiveRecord::StatementInvalid => e
_logger_warn "Looks like your table doesn't exist for #{plural.to_s._classify}.\nError #{e}\nSkipping..."
end
end
end
end

View file

@ -33,7 +33,7 @@ Inherits from ActiveRecord::Reflection::AssociationReflection.
=end
class PolymorphicReflection < AssociationReflection
class PolymorphicReflection < ThroughReflection
# Stub out the validity check. Has_many_polymorphs checks validity on macro creation, not on reflection.
def check_validity!
# nothing

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file