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