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


Reply via email to