Author: assaf
Date: Wed May 14 02:39:23 2008
New Revision: 656180
URL: http://svn.apache.org/viewvc?rev=656180&view=rev
Log:
Task state replaces status and is now string instead of int.
Modified:
ode/sandbox/singleshot/app/models/stakeholder.rb
ode/sandbox/singleshot/app/models/task.rb
ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb
ode/sandbox/singleshot/db/schema.rb
ode/sandbox/singleshot/spec/models/task_spec.rb
Modified: ode/sandbox/singleshot/app/models/stakeholder.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/models/stakeholder.rb?rev=656180&r1=656179&r2=656180&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/models/stakeholder.rb (original)
+++ ode/sandbox/singleshot/app/models/stakeholder.rb Wed May 14 02:39:23 2008
@@ -61,8 +61,7 @@
set_role 'creator', identity
end
- ACCESSORS = {'potential_owner'=>'potential', 'excluded_owner'=>'excluded',
'observer'=>'observer', 'admin'=>'admin'}
-
+ ACCESSORS = { 'potential_owner'=>'potential',
'excluded_owner'=>'excluded', 'observer'=>'observer', 'admin'=>'admin' }
ACCESSORS.each do |accessor, role|
plural = accessor.pluralize
define_method(plural) { in_role(role) }
@@ -89,7 +88,7 @@
old_set = stakeholders.select { |sh| sh.role == role }
stakeholders.delete old_set.reject { |sh| new_set.include?(sh.person) }
(new_set - old_set.map(&:person)).each { |person| stakeholders.build
:person=>person, :role=>role }
- changed_attributes[role.to_sym] = old_set unless
changed_attributes.has_key?(role.to_sym)
+ #changed_attributes[role] = old_set unless
changed_attributes.has_key?(role)
end
end
Modified: ode/sandbox/singleshot/app/models/task.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/models/task.rb?rev=656180&r1=656179&r2=656180&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/models/task.rb (original)
+++ ode/sandbox/singleshot/app/models/task.rb Wed May 14 02:39:23 2008
@@ -4,15 +4,15 @@
# Table name: tasks
#
# id :integer not null, primary key
-# title :string(255)
-# priority :integer(1) default(1)
+# title :string(255) not null
+# description :string(255) not null
+# priority :integer(1) default(1), not null
# due_on :date
-# status :integer(2) default(0), not null
+# state :string(255) not null
# frame_url :string(255)
# outcome_url :string(255)
# outcome_type :string(255)
-# cancellation :integer(1)
-# access_key :string(32) not null
+# access_key :string(32)
# data :text not null
# version :integer default(0), not null
# created_at :datetime
@@ -24,30 +24,16 @@
class Task < ActiveRecord::Base
- class << self
-
- # Create a reserved task, used as a place holder when creating a task
exactly once.
- # A reserve task has no attributes and does not show up. The first update
changes its status to active.
- def reserve!(admin)
- create! admin
- end
-
- end
-
def initialize(attributes = {}) #:nodoc:
- if attributes.is_a?(Person)
- super :admins=>attributes
- self.status = :reserved
- else
- super attributes
- self.status = :active
- end
+ super
+ self.description ||= ''
+ self.state = 'ready'
self.access_key = MD5.hexdigest(OpenSSL::Random.random_bytes(128))
self.data ||= {}
end
def to_param #:nodoc:
- title.blank? ? id.to_s : "#{id}-#{title.gsub(/[^\w]+/, '-').gsub(/-*$/,
'')}" unless new_record?
+ id && id.to_s + ('-' + title).gsub(/[^\w]+/, '-').gsub(/-{2,}/,
'-').sub(/-+$/, '')
end
# --- Task state ---
@@ -55,20 +41,54 @@
# Locking column used for versioning and detecting update conflicts.
set_locking_column 'version'
- enumerable :status, [:reserved, :active, :suspended, :completed,
:cancelled], :check_methods=>true
- attr_protected :status
- validates_presence_of :status
+ # A task can be in one of these states:
+ # - ready -- Ready but not yet available/active, the task will not show
on anyone's list.
+ # - active -- Task performed by owner.
+ # - suspended -- Cannot be claimed.
+ # - completed -- Task has completed.
+ # - cancelled -- Task was cancelled before completion.
+ STATES = ['ready', 'active', 'suspended', 'completed', 'cancelled']
+
+ # Cannot change in mass update.
+ attr_protected :state
+ validates_inclusion_of :state, :in=>STATES
+
+ before_validation do |task|
+ if task.state == 'ready'
+ task.owner = task.potential_owners.first unless task.owner ||
task.potential_owners.size > 1
+ task.state = 'active' if task.owner
+ end
+ end
+
+ validate do |task|
+ changes = task.changes['state']
+ from, to = changes.first, changes.last if changes
+ if from == 'completed'
+ task.errors.add :state, 'Cannot change state of completed task.'
+ elsif from == 'cancelled'
+ task.errors.add :state, 'Cannot change state of cancelled task.'
+ elsif to == 'ready'
+ task.errors.add :state, 'Cannot change state back to ready.' unless
from.nil? || from == 'ready'
+ elsif to == 'completed'
+ if from == 'active'
+ task.errors.add :state, 'Only owner can complete task.' unless
task.owner
+ else
+ task.errors.add :state, 'Cannot change to completed from any state but
active.'
+ end
+ end
+ end
+
# Status starts as reserved or active, and changes from reserved to active
on first update.
- validates_inclusion_of :status, :on=>:create, :in=>[:reserved, :active]
- before_validation_on_update { |task| task.status = :active if task.reserved?
}
+ #validates_inclusion_of :status, :on=>:create, :in=>[:reserved, :active]
+ #before_validation_on_update { |task| task.status = :active if
task.reserved? }
# Only assigned task can change to completed.
- validate { |task| task.errors.add :status, 'Only owner can complete task' if
task.completed? && task.owner.nil? }
+ #validate { |task| task.errors.add :status, 'Only owner can complete task'
if task.completed? && task.owner.nil? }
# Set task status to suspended and back to active.
- def suspended=(suspended)
- self.status = suspended ? :suspended : :active if active? || suspended?
- end
+ #def suspended=(suspended)
+ # self.status = suspended ? :suspended : :active if active? || suspended?
+ #end
# Task priority: 1 is the lowest (and default) priority.
PRIORITIES = 1..3
@@ -84,8 +104,8 @@
# -- View and perform ---
- validates_presence_of :title, :frame_url, :unless=>:reserved?
- validates_url :frame_url, :if=>:frame_url
+ #validates_presence_of :title, :frame_url, :unless=>:reserved?
+ #validates_url :frame_url, :if=>:frame_url
# --- Completion and cancellation ---
Modified: ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb?rev=656180&r1=656179&r2=656180&view=diff
==============================================================================
--- ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb (original)
+++ ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb Wed May 14
02:39:23 2008
@@ -1,20 +1,20 @@
class Tasks < ActiveRecord::Migration
def self.up
create_table :tasks do |t|
- t.string :title, :null=>true
- t.integer :priority, :null=>true, :default=>1, :limit=>1
+ t.string :title, :null=>false
+ t.string :description, :null=>false
+ t.integer :priority, :null=>false, :default=>1, :limit=>1
t.date :due_on, :null=>true
- t.integer :status, :null=>false, :default=>0, :limit=>2
+ t.string :state, :null=>false
t.string :frame_url, :null=>true
t.string :outcome_url, :null=>true
t.string :outcome_type, :null=>true
- t.integer :cancellation, :null=>true, :limit=>1
- t.string :access_key, :null=>false, :limit=>32
+ t.string :access_key, :null=>true, :limit=>32
t.text :data, :null=>false
t.integer :version, :null=>false, :default=>0
t.timestamps
end
- add_index :tasks, [:status, :updated_at]
+ add_index :tasks, [:state, :updated_at]
end
def self.down
Modified: ode/sandbox/singleshot/db/schema.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/db/schema.rb?rev=656180&r1=656179&r2=656180&view=diff
==============================================================================
--- ode/sandbox/singleshot/db/schema.rb (original)
+++ ode/sandbox/singleshot/db/schema.rb Wed May 14 02:39:23 2008
@@ -48,21 +48,21 @@
add_index "stakeholders", ["task_id", "person_id", "role"], :name =>
"index_stakeholders_on_task_id_and_person_id_and_role", :unique => true
create_table "tasks", :force => true do |t|
- t.string "title"
- t.integer "priority", :limit => 1, :default => 1
+ t.string "title", :null => false
+ t.string "description", :null => false
+ t.integer "priority", :limit => 1, :default => 1, :null => false
t.date "due_on"
- t.integer "status", :limit => 2, :default => 0, :null => false
+ t.string "state", :null => false
t.string "frame_url"
t.string "outcome_url"
t.string "outcome_type"
- t.integer "cancellation", :limit => 1
- t.string "access_key", :limit => 32, :null => false
+ t.string "access_key", :limit => 32
t.text "data", :null => false
t.integer "version", :default => 0, :null => false
t.datetime "created_at"
t.datetime "updated_at"
end
- add_index "tasks", ["status", "updated_at"], :name =>
"index_tasks_on_status_and_updated_at"
+ add_index "tasks", ["state", "updated_at"], :name =>
"index_tasks_on_state_and_updated_at"
end
Modified: ode/sandbox/singleshot/spec/models/task_spec.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/spec/models/task_spec.rb?rev=656180&r1=656179&r2=656180&view=diff
==============================================================================
--- ode/sandbox/singleshot/spec/models/task_spec.rb (original)
+++ ode/sandbox/singleshot/spec/models/task_spec.rb Wed May 14 02:39:23 2008
@@ -1,144 +1,151 @@
require File.dirname(__FILE__) + '/../spec_helper'
-describe Task, 'reserve!' do
+describe Task, 'to_param' do
include Specs::Tasks
- before :all do
- @admin = person('admin')
- @task = Task.reserve!(@admin)
+ it 'should return nil unless task exists in database' do
+ Task.new.to_param.should be_nil
end
- it 'should create new task' do
- Task.should have(1).record
+ it 'should begin with task ID' do
+ Task.create default_task
+ Task.first.to_param.to_i.should == Task.first.id
end
- it 'should be reserved' do
- @task.should be_reserved
+ it 'should include task title' do
+ Task.create default_task.merge(:title=>'Task Title')
+ Task.first.to_param[/^\d+-(.*)/, 1].should == 'Task-Title'
end
- it 'should include argument as admin' do
- @task.admins.should eql([EMAIL PROTECTED])
+ it 'should properly encode task title' do
+ Task.create default_task.merge(:title=>'Test:encoding, ignoring
"unprintable" characters')
+ Task.first.to_param[/^\d+-(.*)/, 1].should ==
'Test-encoding-ignoring-unprintable-characters'
end
- it 'should destroy stakeholder when deleted' do
- lambda { @task.destroy }.should change(Stakeholder, :count).to(0)
+ it 'should remove redundant hyphens' do
+ Task.create default_task.merge(:title=>'-Test redundant--hyphens--')
+ Task.first.to_param[/^\d+-(.*)/, 1].should == 'Test-redundant-hyphens'
end
-end
-
-
-describe Task, 'to_param' do
- include Specs::Tasks
-
- it 'should return nil unless task saved' do
- Task.new.to_param.should be_nil
+ it 'should deal gracefully with missing title' do
+ Task.create default_task.merge(:title=>'')
+ Task.first.to_param.should =~ /^\d+$/
end
- it 'should resolve to task ID' do
- task = Task.create!(default_task)
- task.to_param.to_i.should eql(task.id)
- task.update_attributes! :title=>'whatever'
- task.to_param.to_i.should eql(task.id)
+ it 'should leave UTF-8 text alone' do
+ Task.create default_task.merge(:title=>'josé')
+ Task.first.to_param[/^\d+-(.*)/, 1].should == 'josé'
end
- it 'should only contain ID if title is empty' do
- task = Task.reserve!(person('admin'))
- task.to_param.should eql(task.id.to_s)
- end
+end
- it 'should contain title if specified' do
- task = Task.create(default_task.merge(:title=>'whatever'))
- task.to_param.should eql("#{task.id}-whatever")
- end
- it 'should change all non-word characters to dashes' do
- task = Task.create(default_task.merge(:title=>'foo*bar+baz faz_'))
- task.to_param[/^\d+-(.*)/, 1].should eql('foo-bar-baz-faz_')
- end
+describe Task, 'state' do
+ include Specs::Tasks
- it 'should consolidate multiple dashes' do
- task = Task.create(default_task.merge(:title=>'foo**bar--baz -faz'))
- task.to_param[/^\d+-(.*)/, 1].should eql('foo-bar-baz-faz')
+ def task_in_state(state, attributes = {})
+ task = Task.create(default_task.merge(attributes))
+ Task.update_all ["state = ?", state], ["id=?", task.id]
+ Task.find(task.id)
end
- it 'should remove dashes at end' do
- task = Task.create(default_task.merge(:title=>'foo**'))
- task.to_param[/^\d+-(.*)/, 1].should eql('foo')
+ def can_transition?(task, state)
+ task.state = state
+ task.save
end
- it 'should leave UTF-8 text along' do
- task = Task.create(default_task.merge(:title=>'josé'))
- CGI.escape(task.to_param[/^\d+-(.*)/, 1]).should eql('jos%C3%A9')
+ it 'should begin as ready' do
+ Task.create default_task
+ Task.first.state.should == 'ready'
end
-end
-
-
-describe Task, 'status' do
- include Specs::Tasks
-
- before :each do
- @task = Task.create!(default_task)
+ it 'should not allow mass assignment' do
+ Task.create default_task.merge(:state=>'active')
+ Task.first.state.should == 'ready'
+ lambda { Task.first.update_attributes :state=>'active' }.should_not change
{ Task.first.state }
end
- it 'should default to :active' do
- @task.status.should eql(:active)
+ it 'should be required to save task' do
+ task = Task.new(default_task)
+ task.state = nil ; task.save
+ task.should have(1).error_on(:state)
end
- it 'should be required to save' do
- @task.status = nil
- @task.should have(1).error_on(:status)
+ it 'should be one of enumerated values' do
+ task = Task.new(default_task)
+ task.state = 'active' ; task.save
+ task.should have(:no).errors
+ task.state = 'unsupported' ; task.save
+ task.should have(1).error_on(:state)
end
- it 'should not change to completed without owner' do
- @task.status = :completed
- @task.should have(1).error_on(:status)
+ it 'should not transition to ready from any other state' do
+ Task::STATES.each do |from|
+ can_transition?(task_in_state(from), 'ready').should be(from == 'ready')
+ end
end
- it 'should change to completed if owner specified' do
- @task.owner = person('assaf')
- @task.status = :completed
- @task.should have(:no).errors_on(:status)
+ it 'should transition to active only from ready or suspended' do
+ Task::STATES.each do |from|
+ can_transition?(task_in_state(from), 'active').should be(['ready',
'active', 'suspended'].include?(from))
+ end
end
- it 'should switch to cancelled if previoulsy active' do
- lambda { @task.status = :cancelled ; @task.save! }.should change(@task,
:status).to(:cancelled)
+ it 'should transition from ready to active if owner specified' do
+ Task::STATES.each do |from|
+ task = task_in_state(from)
+ if from == 'ready'
+ lambda { task.owner = person('owner') ; task.save }.should
change(task, :state).to('active')
+ lambda { task.owner = person('owner') ; task.save }.should_not
change(task, :state)
+ end
+ end
end
- it 'should switch to suspended if previously active' do
- lambda { @task.status = :suspended ; @task.save! }.should change(@task,
:status).to(:suspended)
+ it 'should transition to active if owner picked from potential owners' do
+ Task::STATES.each do |from|
+ task = task_in_state(from)
+ if from == 'ready'
+ lambda { task.potential_owners = person('owner') ; task.save }.should
change(task, :state).to('active')
+ lambda { task.potential_owners = person('owner') ; task.save
}.should_not change(task, :state)
+ end
+ end
end
- it 'should switch to active if previously suspended' do
- @task.status = :suspended ; @task.save!
- lambda { @task.status = :active ; @task.save! }.should change(@task,
:status).to(:active)
+ it 'should transition to suspended only from ready or active' do
+ Task::STATES.each do |from|
+ can_transition?(task_in_state(from), 'suspended').should be(['ready',
'active', 'suspended'].include?(from))
+ end
end
- it 'should be protected' do
- lambda { @task.update_attributes! :status=>:suspended }.should_not
change(@task, :status)
+ it 'should transition to completed only from active' do
+ Task::STATES.each do |from|
+ can_transition?(task_in_state(from, :owner=>person('owner')),
'completed').should be(from == 'active' || from == 'completed')
+ end
end
- it 'should use suspended = to flip between active and suspended' do
- lambda { @task.update_attributes! :suspended=>true }.should change(@task,
:status).to(:suspended)
- lambda { @task.update_attributes! :suspended=>false }.should change(@task,
:status).to(:active)
+ it 'should not transition to completed without owner' do
+ [nil, person('owner')].each do |owner|
+ can_transition?(task_in_state('active', :owner=>owner),
'completed').should be(!owner.nil?)
+ end
end
- it 'should resolve symbol to index value' do
- Task::STATUSES.each_with_index do |status, i|
- Task.status(status).should be(i)
+ it 'should not change from completed to any other state' do
+ Task::STATES.each do |to|
+ can_transition?(task_in_state('completed', :owner=>person('owner')),
to).should be(to == 'completed')
end
end
- it 'should not allow any other value from constructor' do
- (Task::STATUSES - [:reserved, :active]).each do |status|
- Task.new(:owner=>person('owner'), :status=>status).should be_active
+ it 'should transition to cancelled from any other state but completed' do
+ Task::STATES.each do |from|
+ can_transition?(task_in_state(from), 'cancelled').should be(from !=
'completed')
end
end
- it 'should change from reserved to active when doing mass assignment' do
- task = Task.reserve!(person('admin'))
- lambda { task.update_attributes(:title=>'Change to active') }.should
change(task, :status).to(:active)
+ it 'should not change from cancelled to any other state' do
+ Task::STATES.each do |to|
+ can_transition?(task_in_state('cancelled'), to).should be(to ==
'cancelled')
+ end
end
end
@@ -152,21 +159,22 @@
end
it 'should default to 1' do
- @task.priority.should be(1)
+ Task.new.priority.should be(1)
end
it 'should allow values from 1 to 3' do
priorities = Array.new(5) { |i| i }
- priorities.map { |p| @task.update_attributes :priority=>p }.should
eql([false] + [true] * 3 + [false])
+ priorities.map { |p| Task.new(default_task.merge(:priority=>p)).valid?
}.should eql([false] + [true] * 3 + [false])
end
it 'should accept string value' do
- lambda { @task.update_attributes! :priority=>'2' }.should change(@task,
:priority).to(2)
+ Task.create default_task
+ lambda { Task.first.update_attributes! :priority=>'2' }.should change {
Task.first.priority }.to(2)
end
it 'should accept nil and reset to default' do
- @task.priority = 2
- lambda { @task.update_attributes! :priority=>nil }.should change(@task,
:priority).to(1)
+ Task.create default_task.merge(:priority=>2)
+ lambda { Task.first.update_attributes! :priority=>nil }.should change {
Task.first.priority }.to(1)
end
end