Author: assaf
Date: Wed May 28 16:02:04 2008
New Revision: 661134

URL: http://svn.apache.org/viewvc?rev=661134&view=rev
Log:
Specs for task state transitions (active, completed, etc).

Modified:
    ode/sandbox/singleshot/app/controllers/tasks_controller.rb
    ode/sandbox/singleshot/app/models/task.rb
    ode/sandbox/singleshot/spec/models/task_spec.rb

Modified: ode/sandbox/singleshot/app/controllers/tasks_controller.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/controllers/tasks_controller.rb?rev=661134&r1=661133&r2=661134&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/controllers/tasks_controller.rb (original)
+++ ode/sandbox/singleshot/app/controllers/tasks_controller.rb Wed May 28 
16:02:04 2008
@@ -54,16 +54,12 @@
     end
   end
 
-
-
-
-
   def show
     @alternate = { Mime::ICS=>formatted_tasks_url(:format=>:ics, 
:access_key=>authenticated.access_key) }
     respond_to do |format|
       format.html { render :layout=>'head' }
-      format.xml  { render :xml=>@task }
-      format.json { render :json=>@task }
+      # TODO: format.xml
+      # TODO: format.json
       format.ics  do
         @title = @task.title
         @tasks = [EMAIL PROTECTED]
@@ -72,6 +68,29 @@
     end
   end
 
+  def update
+    # TODO: rescue ActiveRecord::ReadOnlyRecord
+    @task.modified_by(authenticated).update_attributes(params)
+
+    # TODO: conditional put
+    raise ActiveRecord::StaleObjectError, 'This task already completed.' if 
@task.completed?
+    input = params[:task]
+    input[:outcome_type] ||= suggested_outcome_type unless @task.outcome_type
+    filter = @task.filter_update_for(authenticated)
+    raise NotAuthorized, 'You are not allowed to change this task.' unless 
filter
+    input = filter[input]
+    raise NotAuthorized, 'You cannot make this change.' unless input
+
+    @task.update_attributes! input
+    respond_to do |format|
+      format.html { redirect_to task_url }
+      format.xml  { render :xml=>@task }
+      format.json { render :json=>@task }
+    end
+  end
+
+
+
   def new
     @task = Task.new(:creator=>authenticated)
     respond_to :html

Modified: ode/sandbox/singleshot/app/models/task.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/models/task.rb?rev=661134&r1=661133&r2=661134&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/models/task.rb (original)
+++ ode/sandbox/singleshot/app/models/task.rb Wed May 28 16:02:04 2008
@@ -28,7 +28,6 @@
   def initialize(attributes = {}) #:nodoc:
     super
     self.description ||= ''
-    self.status = attributes[:status] == 'reserved' ? 'reserved' : 'ready'
     self.data ||= {}
     self.access_key = MD5.hexdigest(OpenSSL::Random.random_bytes(128))
   end
@@ -69,7 +68,6 @@
   STATUSES = ['reserved', 'ready', 'active', 'suspended', 'completed', 
'cancelled']
 
   # Cannot change in mass update.
-  attr_protected :status
   validates_inclusion_of :status, :in=>STATUSES
 
   # Check method for each status (active?, completed?, etc).
@@ -79,23 +77,22 @@
     end
   end
 
-  before_validation_on_update do |task|
-    task.status = 'ready' if task.status == 'reserved'
-  end
-
   before_validation do |task|
+    # Default status is ready.
+    task.status ||= 'ready' if task.new_record?
     case task.status
     when 'ready'
       task.owner = task.potential_owners.first unless task.owner || 
task.potential_owners.size > 1
       task.status = 'active' if task.owner
     when 'active'
       task.status = 'ready' unless task.owner
+    when 'completed', 'cancelled'
+      task.readonly! unless task.status_changed?
     end
   end
 
   validate do |task|
-    changes = task.changes['status']
-    from, to = changes.first, changes.last if changes
+    from, to = task.status_change
     if from == 'completed' 
       task.errors.add :status, 'Cannot change status of completed task.'
     elsif from == 'cancelled'

Modified: ode/sandbox/singleshot/spec/models/task_spec.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/spec/models/task_spec.rb?rev=661134&r1=661133&r2=661134&view=diff
==============================================================================
--- ode/sandbox/singleshot/spec/models/task_spec.rb (original)
+++ ode/sandbox/singleshot/spec/models/task_spec.rb Wed May 28 16:02:04 2008
@@ -9,197 +9,203 @@
   end
 
   it 'should begin with task ID' do
-    Task.create default_task
-    Task.first.to_param.to_i.should == Task.first.id
+    Task.create!(default_task).to_param.to_i.should == Task.last.id
   end
 
   it 'should include task title' do
-    Task.create default_task.merge(:title=>'Task Title')
-    Task.first.to_param[/^\d+-(.*)/, 1].should == 'Task-Title'
+    Task.create!(default_task.merge(:title=>'Task 
Title')).to_param[/^\d+-(.*)/, 1].should == 'Task-Title'
   end
 
   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'
+    Task.create!(default_task.merge(:title=>'Test:encoding, ignoring 
"unprintable" characters')).
+      to_param[/^\d+-(.*)/, 1].should == 
'Test-encoding-ignoring-unprintable-characters'
   end
 
   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'
+    Task.create!(default_task.merge(:title=>'-Test  
redundant--hyphens--')).to_param[/^\d+-(.*)/, 1].should == 
'Test-redundant-hyphens'
   end
 
   it 'should deal gracefully with missing title' do
-    Task.create default_task.merge(:title=>'')
-    Task.first.to_param.should =~ /^\d+$/
+    Task.create!(default_task.merge(:title=>'--')).to_param.should =~ /^\d+$/
   end
 
   it 'should leave UTF-8 text alone' do
-    Task.create default_task.merge(:title=>'josé')
-    Task.first.to_param[/^\d+-(.*)/, 1].should == 'josé'
+    Task.create!(default_task.merge(:title=>'jösé')).to_param[/^\d+-(.*)/, 
1].should == 'jösé'
   end
 
 end
 
 
-describe Task, 'etag' do
+describe Task, 'version' do
   include Specs::Tasks
 
-  before do
-    @task = Task.create(default_task)
+  it 'should begin at zero' do
+    Task.create!(default_task).version.should == 0 
   end
 
+  it 'should increment each time task is updated' do
+    Task.create! default_task
+    lambda { Task.last.update_attributes :priority=>1 }.should change { 
Task.last.version }.from(0).to(1)
+    lambda { Task.last.update_attributes :due_on=>Time.now }.should change { 
Task.last.version }.from(1).to(2)
+  end
+end
+
+
+describe Task, 'etag' do
+  include Specs::Tasks
+
   it 'should be hex digest' do
-    Task.create default_task
-    Task.first.etag.should =~ /^[0-9a-f]{32}$/
+    Task.create!(default_task).etag.should =~ /^[0-9a-f]{32}$/
   end
 
   it 'should remain the same if task not modified' do
-    Task.create default_task
-    Task.first.etag.should == Task.first.etag
+    Task.create! default_task
+    Task.last.etag.should == Task.last.etag
   end
 
   it 'should be different for two different tasks' do
-    Task.create(default_task).etag.should_not == Task.create(default_task).etag
+    Task.create!(default_task).etag.should_not == 
Task.create(default_task).etag
   end
 
   it 'should change whenever task is saved' do
-    Task.create default_task
-    lambda { Task.first.update_attributes! :priority=>2 }.should change { 
Task.first.etag }
+    Task.create! default_task
+    lambda { Task.last.update_attributes! :priority=>1 }.should change { 
Task.last.etag }
+    lambda { Task.last.update_attributes! :due_on=>Time.now }.should change { 
Task.last.etag }
   end
 
 end
 
 
-describe Task, 'state' do
+describe Task, 'status' do
   include Specs::Tasks
 
-  # Returns a task in the specified state.  No validation checks are made.
-  # Applies optional attributes to task at creation.
-  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)
+  def task_with_status(status, attributes = nil)
+    attributes ||= {}
+    task = case status
+    when 'active'
+      Task.create!(default_task.merge(attributes).merge(:status=>status, 
:owner=>person('owner')))
+    when 'completed'
+      active = 
Task.create!(default_task.merge(attributes).merge(:status=>'active', 
:owner=>person('owner')))
+      active.update_attributes :status=>'completed'
+      active
+    else
+      Task.create!(default_task.merge(attributes).merge(:status=>status))
+    end
+
+    def task.transition_to(status, attributes = nil)
+      attributes ||= {}
+      update_attributes attributes.merge(:status=>status)
+      self
+    end
+    def task.can_transition?(status, attributes = nil)
+      transition_to(status, attributes).errors_on(:status).empty?
+    end
+    task
   end
 
-  # Returns true if task can transition to specified state.
-  def can_transition?(task, state)
-    task.state = state
-    task.save && task.state == state
+  it 'should start as ready' do
+    Task.create!(default_task).status.should == 'ready'
   end
 
-  it 'should not allow mass assignment' do
-    task = Task.create(default_task.merge(:state=>'active'))
-    task.state.should == 'reserved'
-    lambda { task.update_attributes :state=>'active' }.should change(task, 
:state).to('ready')
+  it 'should only accept supported value' do
+    Task.create(:status=>'unsupported').should have(1).error_on(:status)
   end
 
-  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)
+  it 'should allow starting in reserved' do
+    Task.create!(default_task.merge(:status=>'reserved')).status.should == 
'reserved'
   end
 
-  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)
+  it 'should not transition to reserved from any other status' do
+    for status in Task::STATUSES - ['reserved']
+      task_with_status(status).can_transition?('reserved').should be_false
+    end
   end
 
-  it 'should begin as reserved' do
-    Task.create default_task
-    Task.first.state.should == 'reserved'
+  it 'should start as ready if started as active but not associated with 
owner' do
+    Task.create!(default_task.merge(:status=>'active')).status.should == 
'ready'
   end
 
-  it 'should not transition to reserved from any other state' do
-    Task::STATES.each do |from|
-      can_transition?(task_in_state(from), 'reserved').should be_false
-    end
+  it 'should start as active if started as ready and associated with owner' do
+    Task.create!(default_task.merge(:owner=>person('owner'))).status.should == 
'active'
   end
 
-  it 'should transition to ready on first update' do
-    task = task_in_state('reserved')
-    lambda { task.save }.should change(task, :state).to('ready')
+  it 'should start as active if started as ready and associated with one 
potential owner' do
+    
Task.create!(default_task.merge(:potential_owners=>people('owner'))).status.should
 == 'active'
   end
 
-  it 'should transition from reserved to active if associated with owner' do
-    task = task_in_state('reserved')
-    lambda { task.owner = person('owner') ; task.save }.should change(task, 
:state).to('active')
+  it 'should start as ready if started as ready and associated with several 
potential owners' do
+    Task.create!(default_task.merge(:potential_owners=>people('foo', 
'bar'))).status.should == 'ready'
   end
 
-  it 'should not transition to ready from active' do
-    task = task_in_state('active', :owner=>person('owner'))
-    lambda { task.state = 'ready' ; task.save }.should_not change(task, :state)
+  it 'should transition from ready to active when associated with owner' do
+    task = task_with_status('ready')
+    lambda { task.update_attributes :owner=>person('owner') }.should 
change(task, :status).to('active')
   end
 
-  it 'should transition from ready to active if owner specified' do
-    task = task_in_state('ready')
-    lambda { task.owner = person('owner') ; task.save }.should change(task, 
:state).to('active')
+  it 'should transition from active to ready when owner removed' do
+    task = task_with_status('active')
+    lambda { task.update_attributes :owner=>nil }.should change(task, 
:status).to('ready')
   end
 
-  it 'should transition to ready from active if owner removed' do
-    task = task_in_state('active', :owner=>person('owner'))
-    lambda { task.owner = nil ; task.save }.should change(task, 
:state).to('ready')
+  it 'should accept suspended as initial value' do
+    Task.create!(default_task.merge(:status=>'suspended')).status.should == 
'suspended'
   end
 
-  it 'should transition from ready to suspended and back' do
-    task = task_in_state('ready')
-    can_transition?(task, 'suspended').should be_true
-    can_transition?(task, 'ready').should be_true
+  it 'should transition from ready to suspended' do
+    task_with_status('ready').can_transition?('suspended').should be_true
   end
 
-  it 'should transition from active to suspended and back' do
-    task = task_in_state('active', :owner=>person('owner'))
-    can_transition?(task, 'suspended').should be_true
-    can_transition?(task, 'active').should be_true
+  it 'should transition from suspended back to ready' do
+    task_with_status('suspended').transition_to('ready').status.should == 
'ready'
+    task_with_status('suspended').transition_to('active').status.should == 
'ready'
   end
 
-  it 'should transition from ready to active if has one potential owner' do
-    task = task_in_state('ready')
-    lambda { task.potential_owners = person('owner') ; task.save }.should 
change(task, :state).to('active')
-    task.owner.should == person('owner')
+  it 'should transition from active to suspended' do
+    task_with_status('active').can_transition?('suspended').should be_true
   end
 
-  it 'should not transition from ready to active if more than one potential 
owner' do
-    task = task_in_state('ready', :potential_owners=>people('owner', 'other'))
-    task.state.should == 'ready'
-    task.owner.should be_nil
+  it 'should transition from suspended back to active' do
+    task_with_status('suspended', 
:owner=>person('owner')).transition_to('active').status.should == 'active'
+    task_with_status('suspended', 
:owner=>person('owner')).transition_to('ready').status.should == 'active'
   end
 
-  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')
+  it 'should only transition to completed from active' do
+    for status in Task::STATUSES - ['completed']
+      task_with_status(status).can_transition?('completed').should == (status 
=='active')
     end
   end
 
   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
+    task_with_status('active').can_transition?('completed', 
:owner=>nil).should be_false
   end
 
-  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')
+  it 'should not transition from completed to any other status' do
+    for status in Task::STATUSES - ['completed']
+      task_with_status('completed').can_transition?(status).should be_false
     end
   end
 
-  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')
+  it 'should transition to cancelled from any other status but completed' do
+    for status in Task::STATUSES - ['cancelled']
+      task_with_status(status).can_transition?('cancelled').should == (status 
!='completed')
     end
   end
 
-  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')
+  it 'should not transition from cancelled to any other status' do
+    for status in Task::STATUSES - ['cancelled']
+      task_with_status('cancelled').can_transition?(status).should be_false
     end
   end
 
+  it 'should not allow changing of completed or cancelled tasks' do
+    lambda { task_with_status('completed').update_attributes :title=>'never 
mind' }.should raise_error(ActiveRecord::ReadOnlyRecord)
+    lambda { task_with_status('cancelled').update_attributes :title=>'never 
mind' }.should raise_error(ActiveRecord::ReadOnlyRecord)
+  end
+
 end
 
 
+=begin
 describe Task, 'priority' do
   include Specs::Tasks
 
@@ -611,3 +617,4 @@
   end
 
 end
+=end


Reply via email to