mirror of
https://github.com/TracksApp/tracks.git
synced 2026-01-06 01:08:50 +01:00
Merge branches 'aasm_update' and 'master'
This commit is contained in:
commit
626edb478d
29 changed files with 115 additions and 860 deletions
|
|
@ -89,9 +89,6 @@ class TodosController < ApplicationController
|
|||
specified_state = @todo.state
|
||||
@saved = @todo.save
|
||||
|
||||
# Fix for #977 because AASM overrides @state on creation
|
||||
@todo.update_attribute('state', specified_state) unless specified_state == "immediate" || specified_state.nil? || !@saved
|
||||
@saved = @todo.save
|
||||
@todo.update_state_from_project if @saved
|
||||
|
||||
unless (@saved == false) || tag_list.blank?
|
||||
|
|
|
|||
|
|
@ -40,23 +40,27 @@ class Project < ActiveRecord::Base
|
|||
validates_does_not_contain :name, :string => ','
|
||||
|
||||
acts_as_list :scope => 'user_id = #{user_id} AND state = \'#{state}\''
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
|
||||
include AASM
|
||||
aasm_column :state
|
||||
aasm_initial_state :active
|
||||
|
||||
extend NamePartFinder
|
||||
#include Tracks::TodoList
|
||||
|
||||
state :active
|
||||
state :hidden, :enter => :hide_todos, :exit => :unhide_todos
|
||||
state :completed, :enter => Proc.new { |p| p.completed_at = Time.zone.now }, :exit => Proc.new { |p| p.completed_at = nil }
|
||||
aasm_state :active
|
||||
aasm_state :hidden, :enter => :hide_todos, :exit => :unhide_todos
|
||||
aasm_state :completed, :enter => :set_completed_at_date, :exit => :clear_completed_at_date
|
||||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:hidden, :completed]
|
||||
aasm_event :activate do
|
||||
transitions :to => :active, :from => [:active, :hidden, :completed]
|
||||
end
|
||||
|
||||
event :hide do
|
||||
aasm_event :hide do
|
||||
transitions :to => :hidden, :from => [:active, :completed]
|
||||
end
|
||||
|
||||
event :complete do
|
||||
aasm_event :complete do
|
||||
transitions :to => :completed, :from => [:active, :hidden]
|
||||
end
|
||||
|
||||
|
|
@ -92,6 +96,14 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def set_completed_at_date
|
||||
self.completed_at = Time.zone.now
|
||||
end
|
||||
|
||||
def clear_completed_at_date
|
||||
self.completed_at = nil
|
||||
end
|
||||
|
||||
def note_count
|
||||
cached_note_count || notes.count
|
||||
end
|
||||
|
|
@ -106,7 +118,7 @@ class Project < ActiveRecord::Base
|
|||
# as a result of acts_as_state_machine calling state=() to update the attribute
|
||||
def transition_to(candidate_state)
|
||||
case candidate_state.to_sym
|
||||
when current_state
|
||||
when aasm_current_state
|
||||
return
|
||||
when :hidden
|
||||
hide!
|
||||
|
|
|
|||
|
|
@ -10,20 +10,19 @@ class RecurringTodo < ActiveRecord::Base
|
|||
named_scope :completed, :conditions => { :state => 'completed'}
|
||||
|
||||
attr_protected :user
|
||||
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
|
||||
state :active, :enter => Proc.new { |t|
|
||||
t[:show_from], t.completed_at = nil, nil
|
||||
t.occurences_count = 0
|
||||
}
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
include AASM
|
||||
aasm_column :state
|
||||
aasm_initial_state :active
|
||||
|
||||
aasm_state :active, :enter => Proc.new { |t| t.occurences_count = 0 }
|
||||
aasm_state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
|
||||
event :complete do
|
||||
aasm_event :complete do
|
||||
transitions :to => :completed, :from => [:active]
|
||||
end
|
||||
|
||||
event :activate do
|
||||
aasm_event :activate do
|
||||
transitions :to => :active, :from => [:completed]
|
||||
end
|
||||
|
||||
|
|
@ -41,8 +40,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
validate :set_recurrence_on_validations
|
||||
|
||||
def period_specific_validations
|
||||
periods = %W[daily weekly monthly yearly]
|
||||
if periods.include?(recurring_period)
|
||||
if %W[daily weekly monthly yearly].include?(recurring_period)
|
||||
self.send("validate_#{recurring_period}")
|
||||
else
|
||||
errors.add(:recurring_period, "is an unknown recurrence pattern: '#{self.recurring_period}'")
|
||||
|
|
@ -94,7 +92,6 @@ class RecurringTodo < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def starts_and_ends_on_validations
|
||||
errors.add_to_base("The start date needs to be filled in") if start_from.nil? || start_from.blank?
|
||||
case self.ends_on
|
||||
|
|
|
|||
|
|
@ -45,40 +45,42 @@ class Todo < ActiveRecord::Base
|
|||
RE_PARTS = /'(#{RE_TODO})'\s<'(#{RE_CONTEXT})';\s'(#{RE_PROJECT})'>/ # results in array
|
||||
RE_SPEC = /'#{RE_TODO}'\s<'#{RE_CONTEXT}';\s'#{RE_PROJECT}'>/ # results in string
|
||||
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
include AASM
|
||||
aasm_column :state
|
||||
aasm_initial_state Proc.new { |todo| (todo.show_from && (todo.show_from > todo.user.date)) ? :deferred : :active}
|
||||
|
||||
# when entering active state, also remove completed_at date. Looks like :exit
|
||||
# of state completed is not run, see #679
|
||||
state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil }
|
||||
state :project_hidden
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
state :deferred
|
||||
state :pending
|
||||
aasm_state :active
|
||||
aasm_state :project_hidden, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
aasm_state :completed, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
aasm_state :deferred, :exit => Proc.new { |t| t[:show_from] = nil }
|
||||
aasm_state :pending
|
||||
|
||||
event :defer do
|
||||
aasm_event :defer do
|
||||
transitions :to => :deferred, :from => [:active]
|
||||
end
|
||||
|
||||
event :complete do
|
||||
aasm_event :complete do
|
||||
transitions :to => :completed, :from => [:active, :project_hidden, :deferred]
|
||||
end
|
||||
|
||||
event :activate do
|
||||
aasm_event :activate do
|
||||
transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
|
||||
transitions :to => :active, :from => [:pending], :guard => :no_uncompleted_predecessors_or_deferral?
|
||||
transitions :to => :deferred, :from => [:pending], :guard => :no_uncompleted_predecessors?
|
||||
end
|
||||
|
||||
event :hide do
|
||||
aasm_event :hide do
|
||||
transitions :to => :project_hidden, :from => [:active, :deferred]
|
||||
end
|
||||
|
||||
event :unhide do
|
||||
aasm_event :unhide do
|
||||
transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? }
|
||||
transitions :to => :active, :from => [:project_hidden]
|
||||
end
|
||||
|
||||
event :block do
|
||||
aasm_event :block do
|
||||
transitions :to => :pending, :from => [:active, :deferred]
|
||||
end
|
||||
|
||||
|
|
@ -97,7 +99,7 @@ class Todo < ActiveRecord::Base
|
|||
@predecessor_array = nil # Used for deferred save of predecessors
|
||||
@removed_predecessors = nil
|
||||
end
|
||||
|
||||
|
||||
def no_uncompleted_predecessors_or_deferral?
|
||||
return (show_from.blank? or Time.zone.now > show_from and uncompleted_predecessors.empty?)
|
||||
end
|
||||
|
|
@ -153,10 +155,8 @@ class Todo < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def remove_predecessor(predecessor)
|
||||
puts "@@@ before delete"
|
||||
# remove predecessor and activate myself
|
||||
self.predecessors.delete(predecessor)
|
||||
puts "@@@ before activate"
|
||||
self.activate!
|
||||
end
|
||||
|
||||
|
|
@ -214,9 +214,14 @@ class Todo < ActiveRecord::Base
|
|||
def show_from=(date)
|
||||
# parse Date objects into the proper timezone
|
||||
date = user.at_midnight(date) if (date.is_a? Date)
|
||||
|
||||
# show_from needs to be set before state_change because of "bug" in aasm.
|
||||
# If show_From is not set, the todo will not validate and thus aasm will not save
|
||||
# (see http://stackoverflow.com/questions/682920/persisting-the-state-column-on-transition-using-rubyist-aasm-acts-as-state-machi)
|
||||
self[:show_from] = date
|
||||
|
||||
activate! if deferred? && date.blank?
|
||||
defer! if active? && !date.blank? && date > user.date
|
||||
self[:show_from] = date
|
||||
end
|
||||
|
||||
alias_method :original_project, :project
|
||||
|
|
@ -224,27 +229,7 @@ class Todo < ActiveRecord::Base
|
|||
def project
|
||||
original_project.nil? ? Project.null_object : original_project
|
||||
end
|
||||
|
||||
alias_method :original_set_initial_state, :set_initial_state
|
||||
|
||||
def set_initial_state
|
||||
if show_from && (show_from > user.date)
|
||||
write_attribute self.class.state_column, 'deferred'
|
||||
else
|
||||
original_set_initial_state
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :original_run_initial_state_actions, :run_initial_state_actions
|
||||
|
||||
def run_initial_state_actions
|
||||
# only run the initial state actions if the standard initial state hasn't
|
||||
# been changed
|
||||
if self.class.initial_state.to_sym == current_state
|
||||
original_run_initial_state_actions
|
||||
end
|
||||
end
|
||||
|
||||
def self.feed_options(user)
|
||||
{
|
||||
:title => 'Tracks Actions',
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ suppress_edit_button ||= false
|
|||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<span class="grey"><%= project.current_state.to_s.upcase %></span>
|
||||
<span class="grey"><%= project.aasm_current_state.to_s.upcase %></span>
|
||||
<%= link_to_delete_project(project, image_tag( "blank.png", :title => t('projects.delete_project_title'), :class=>"delete_item")) %>
|
||||
<%= suppress_edit_button ? "" : link_to_edit_project(project, image_tag( "blank.png", :title => t('projects.edit_project_title'), :class=>"edit_item")) %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,4 +24,4 @@
|
|||
<% else -%><%= render :partial => "notes/mobile_notes_summary", :collection => @project.notes %>
|
||||
<% end -%>
|
||||
<h2><%= t('projects.settings') %></h2>
|
||||
<%= t('projects.state', :state => project.current_state.to_s) %>. <%= @project_default_context %>
|
||||
<%= t('projects.state', :state => project.aasm_current_state.to_s) %>. <%= @project_default_context %>
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
# TODO: is this dead code?
|
||||
page.select('#project_status .active span').each do |element|
|
||||
element.className = @project.current_state == :active ? 'active_state' : 'inactive_state'
|
||||
element.className = @project.aasm_current_state == :active ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.select('#project_status .hidden span').each do |element|
|
||||
element.className = @project.current_state == :hidden ? 'active_state' : 'inactive_state'
|
||||
element.className = @project.aasm_current_state == :hidden ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.select('#project_status .completed span').each do |element|
|
||||
element.className = @project.current_state == :completed ? 'active_state' : 'inactive_state'
|
||||
element.className = @project.aasm_current_state == :completed ? 'active_state' : 'inactive_state'
|
||||
end
|
||||
page.notify :notice, "Set project status to #{@project.current_state}", 5.0
|
||||
page.notify :notice, "Set project status to #{@project.aasm_current_state}", 5.0
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ Rails::Initializer.run do |config|
|
|||
config.gem 'rack', :version => '1.1.0'
|
||||
config.gem 'will_paginate', :version => '~> 2.3.15'
|
||||
config.gem 'has_many_polymorphs'
|
||||
config.gem 'aasm', :version => '2.2.0'
|
||||
|
||||
config.action_controller.use_accept_header = true
|
||||
|
||||
|
|
|
|||
13
db/migrate/20110526192008_adapt_to_new_aasm.rb
Normal file
13
db/migrate/20110526192008_adapt_to_new_aasm.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class AdaptToNewAasm < ActiveRecord::Migration
|
||||
def self.up
|
||||
change_column_default :todos, :state, nil
|
||||
change_column_default :projects, :state, nil
|
||||
change_column_default :recurring_todos, :state, nil
|
||||
end
|
||||
|
||||
def self.down
|
||||
change_column :todos, :state, :string, :limit => 20, :default => "immediate", :null => false
|
||||
change_column :projects, :state, :string, :limit => 20, :default => "active", :null => false
|
||||
change_column :recurring_todos, :state, :string, :limit => 20, :default => "active", :null => false
|
||||
end
|
||||
end
|
||||
2
test/fixtures/recurring_todos.yml
vendored
2
test/fixtures/recurring_todos.yml
vendored
|
|
@ -41,7 +41,7 @@ call_bill_gates_every_day:
|
|||
show_from_delta: ~
|
||||
recurring_period: daily
|
||||
recurrence_selector: ~
|
||||
show_always: 0
|
||||
show_always: 1
|
||||
every_other1: 1
|
||||
every_other2: ~
|
||||
every_other3: ~
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
|
|||
login_as(:admin_user)
|
||||
xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"}
|
||||
todos.each do |t|
|
||||
assert_equal :project_hidden, t.reload().current_state
|
||||
assert_equal :project_hidden, t.reload().aasm_current_state
|
||||
end
|
||||
assert p.reload().hidden?
|
||||
end
|
||||
|
|
@ -70,7 +70,7 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
|
|||
xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"}
|
||||
xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"active"}
|
||||
todos.each do |t|
|
||||
assert_equal :active, t.reload().current_state
|
||||
assert_equal :active, t.reload().aasm_current_state
|
||||
end
|
||||
assert p.reload().active?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ class TodosControllerTest < ActionController::TestCase
|
|||
assert_equal 2, t.tags.count
|
||||
end
|
||||
|
||||
|
||||
def test_not_done_counts_after_hiding_project
|
||||
p = Project.find(1)
|
||||
p.hide!
|
||||
|
|
|
|||
|
|
@ -53,26 +53,26 @@ class ProjectTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_project_initial_state_is_active
|
||||
assert_equal :active, @timemachine.current_state
|
||||
assert_equal :active, @timemachine.aasm_current_state
|
||||
assert @timemachine.active?
|
||||
end
|
||||
|
||||
def test_hide_project
|
||||
@timemachine.hide!
|
||||
assert_equal :hidden, @timemachine.current_state
|
||||
assert_equal :hidden, @timemachine.aasm_current_state
|
||||
assert @timemachine.hidden?
|
||||
end
|
||||
|
||||
def test_activate_project
|
||||
@timemachine.activate!
|
||||
assert_equal :active, @timemachine.current_state
|
||||
assert_equal :active, @timemachine.aasm_current_state
|
||||
assert @timemachine.active?
|
||||
end
|
||||
|
||||
def test_complete_project
|
||||
assert_nil @timemachine.completed_at
|
||||
@timemachine.complete!
|
||||
assert_equal :completed, @timemachine.current_state
|
||||
assert_equal :completed, @timemachine.aasm_current_state
|
||||
assert @timemachine.completed?
|
||||
assert_not_nil @timemachine.completed_at, "completed_at not expected to be nil"
|
||||
assert_in_delta Time.now, @timemachine.completed_at, 1
|
||||
|
|
@ -141,25 +141,26 @@ class ProjectTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_transition_to_another_state
|
||||
assert_equal :active, @timemachine.current_state
|
||||
assert_equal :active, @timemachine.aasm_current_state
|
||||
@timemachine.transition_to(:hidden)
|
||||
assert_equal :hidden, @timemachine.current_state
|
||||
assert_equal :hidden, @timemachine.aasm_current_state
|
||||
@timemachine.transition_to(:completed)
|
||||
assert_equal :completed, @timemachine.current_state
|
||||
assert_equal :completed, @timemachine.aasm_current_state
|
||||
@timemachine.transition_to(:active)
|
||||
assert_equal :active, @timemachine.current_state
|
||||
assert_equal :active, @timemachine.aasm_current_state
|
||||
end
|
||||
|
||||
def test_transition_to_same_state
|
||||
assert_equal :active, @timemachine.current_state
|
||||
assert_equal :active, @timemachine.aasm_current_state
|
||||
@timemachine.transition_to(:active)
|
||||
assert_equal :active, @timemachine.current_state
|
||||
assert_equal :active, @timemachine.aasm_current_state
|
||||
end
|
||||
|
||||
def test_deferred_todo_count
|
||||
assert_equal 1, @timemachine.deferred_todos.count
|
||||
assert_equal 0, @moremoney.deferred_todos.count
|
||||
@moremoney.todos[0].show_from = next_week
|
||||
assert_equal :deferred, @moremoney.todos[0].aasm_current_state
|
||||
assert_equal 1, @moremoney.deferred_todos.count
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -275,25 +275,31 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_toggle_completion
|
||||
t = @yearly
|
||||
assert_equal :active, t.current_state
|
||||
t.toggle_completion!
|
||||
assert_equal :completed, t.current_state
|
||||
t.toggle_completion!
|
||||
assert_equal :active, t.current_state
|
||||
assert @yearly.active?
|
||||
assert @yearly.toggle_completion!
|
||||
assert @yearly.completed?
|
||||
|
||||
# entering completed state should set completed_at
|
||||
assert !@yearly.completed_at.nil?
|
||||
|
||||
assert @yearly.toggle_completion!
|
||||
assert @yearly.active?
|
||||
|
||||
# re-entering active state should clear completed_at
|
||||
assert @yearly.completed_at.nil?
|
||||
end
|
||||
|
||||
def test_starred
|
||||
@yearly.tag_with("1, 2, starred")
|
||||
@yearly.tags.reload
|
||||
|
||||
assert_equal true, @yearly.starred?
|
||||
assert_equal false, @weekly_every_day.starred?
|
||||
assert @yearly.starred?
|
||||
assert !@weekly_every_day.starred?
|
||||
|
||||
@yearly.toggle_star!
|
||||
assert_equal false, @yearly.starred?
|
||||
assert !@yearly.starred?
|
||||
@yearly.toggle_star!
|
||||
assert_equal true, @yearly.starred?
|
||||
assert @yearly.starred?
|
||||
end
|
||||
|
||||
def test_occurence_count
|
||||
|
|
@ -307,8 +313,9 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
|||
# after completion, when you reactivate the recurring todo, the occurences
|
||||
# count should be reset
|
||||
assert_equal 2, @every_day.occurences_count
|
||||
@every_day.toggle_completion!
|
||||
@every_day.toggle_completion!
|
||||
assert @every_day.toggle_completion!
|
||||
assert @every_day.toggle_completion!
|
||||
|
||||
assert_equal true, @every_day.has_next_todo(@in_three_days)
|
||||
assert_equal 0, @every_day.occurences_count
|
||||
end
|
||||
|
|
|
|||
|
|
@ -77,10 +77,10 @@ class TodoTest < ActiveSupport::TestCase
|
|||
|
||||
def test_defer_an_existing_todo
|
||||
@not_completed2
|
||||
assert_equal :active, @not_completed2.current_state
|
||||
assert_equal :active, @not_completed2.aasm_current_state
|
||||
@not_completed2.show_from = next_week
|
||||
assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml
|
||||
assert_equal :deferred, @not_completed2.current_state
|
||||
assert_equal :deferred, @not_completed2.aasm_current_state
|
||||
end
|
||||
|
||||
def test_create_a_new_deferred_todo
|
||||
|
|
@ -88,16 +88,16 @@ class TodoTest < ActiveSupport::TestCase
|
|||
todo = user.todos.build
|
||||
todo.show_from = next_week
|
||||
todo.context_id = 1
|
||||
todo.description = 'foo'
|
||||
todo.description = 'foo'
|
||||
assert todo.save, "should have saved successfully" + todo.errors.to_xml
|
||||
assert_equal :deferred, todo.current_state
|
||||
assert_equal :deferred, todo.aasm_current_state
|
||||
end
|
||||
|
||||
def test_create_a_new_deferred_todo_by_passing_attributes
|
||||
user = users(:other_user)
|
||||
todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo')
|
||||
todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo')
|
||||
assert todo.save, "should have saved successfully" + todo.errors.to_xml
|
||||
assert_equal :deferred, todo.current_state
|
||||
assert_equal :deferred, todo.aasm_current_state
|
||||
end
|
||||
|
||||
def test_feed_options
|
||||
|
|
@ -108,11 +108,11 @@ class TodoTest < ActiveSupport::TestCase
|
|||
|
||||
def test_toggle_completion
|
||||
t = @not_completed1
|
||||
assert_equal :active, t.current_state
|
||||
assert_equal :active, t.aasm_current_state
|
||||
t.toggle_completion!
|
||||
assert_equal :completed, t.current_state
|
||||
assert_equal :completed, t.aasm_current_state
|
||||
t.toggle_completion!
|
||||
assert_equal :active, t.current_state
|
||||
assert_equal :active, t.aasm_current_state
|
||||
end
|
||||
|
||||
def test_activate_also_saves
|
||||
|
|
@ -154,7 +154,7 @@ class TodoTest < ActiveSupport::TestCase
|
|||
t.context_id = 1
|
||||
t.save!
|
||||
t.reload
|
||||
assert_equal :active, t.current_state
|
||||
assert_equal :active, t.aasm_current_state
|
||||
end
|
||||
|
||||
def test_initial_state_is_deferred_when_show_from_in_future
|
||||
|
|
@ -165,7 +165,7 @@ class TodoTest < ActiveSupport::TestCase
|
|||
t.show_from = 1.week.from_now.to_date
|
||||
t.save!
|
||||
t.reload
|
||||
assert_equal :deferred, t.current_state
|
||||
assert_equal :deferred, t.aasm_current_state
|
||||
end
|
||||
|
||||
def test_todo_is_not_starred
|
||||
|
|
|
|||
13
vendor/plugins/acts_as_state_machine/CHANGELOG
vendored
13
vendor/plugins/acts_as_state_machine/CHANGELOG
vendored
|
|
@ -1,13 +0,0 @@
|
|||
* trunk *
|
||||
break with true value [Kaspar Schiess]
|
||||
|
||||
* 2.1 *
|
||||
After actions [Saimon Moore]
|
||||
|
||||
* 2.0 * (2006-01-20 15:26:28 -0500)
|
||||
Enter / Exit actions
|
||||
Transition guards
|
||||
Guards and actions can be a symbol pointing to a method or a Proc
|
||||
|
||||
* 1.0 * (2006-01-15 12:16:55 -0500)
|
||||
Initial Release
|
||||
20
vendor/plugins/acts_as_state_machine/MIT-LICENSE
vendored
20
vendor/plugins/acts_as_state_machine/MIT-LICENSE
vendored
|
|
@ -1,20 +0,0 @@
|
|||
Copyright (c) 2006 Scott Barron
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
33
vendor/plugins/acts_as_state_machine/README
vendored
33
vendor/plugins/acts_as_state_machine/README
vendored
|
|
@ -1,33 +0,0 @@
|
|||
= Acts As State Machine
|
||||
|
||||
This act gives an Active Record model the ability to act as a finite state
|
||||
machine (FSM).
|
||||
|
||||
Acquire via subversion at:
|
||||
|
||||
http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk
|
||||
|
||||
If prompted, use the user/pass anonymous/anonymous.
|
||||
|
||||
== Example
|
||||
|
||||
class Order < ActiveRecord::Base
|
||||
acts_as_state_machine :initial => :opened
|
||||
|
||||
state :opened
|
||||
state :closed, :enter => Proc.new {|o| Mailer.send_notice(o)}
|
||||
state :returned
|
||||
|
||||
event :close do
|
||||
transitions :to => :closed, :from => :opened
|
||||
end
|
||||
|
||||
event :return do
|
||||
transitions :to => :returned, :from => :closed
|
||||
end
|
||||
end
|
||||
|
||||
o = Order.create
|
||||
o.close! # notice is sent by mailer
|
||||
o.return!
|
||||
|
||||
28
vendor/plugins/acts_as_state_machine/Rakefile
vendored
28
vendor/plugins/acts_as_state_machine/Rakefile
vendored
|
|
@ -1,28 +0,0 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => [:clean_db, :test]
|
||||
|
||||
desc 'Remove the stale db file'
|
||||
task :clean_db do
|
||||
`rm -f #{File.dirname(__FILE__)}/test/state_machine.sqlite.db`
|
||||
end
|
||||
|
||||
desc 'Test the acts as state machine plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the acts as state machine plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'Acts As State Machine'
|
||||
rdoc.options << '--line-numbers --inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('TODO')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
11
vendor/plugins/acts_as_state_machine/TODO
vendored
11
vendor/plugins/acts_as_state_machine/TODO
vendored
|
|
@ -1,11 +0,0 @@
|
|||
* Currently invalid events are ignored, create an option so that they can be
|
||||
ignored or raise an exception.
|
||||
|
||||
* Query for a list of possible next states.
|
||||
|
||||
* Make listing states optional since they can be inferred from the events.
|
||||
Only required to list a state if you want to define a transition block for it.
|
||||
|
||||
* Real transition actions
|
||||
|
||||
* Default states
|
||||
5
vendor/plugins/acts_as_state_machine/init.rb
vendored
5
vendor/plugins/acts_as_state_machine/init.rb
vendored
|
|
@ -1,5 +0,0 @@
|
|||
require 'acts_as_state_machine'
|
||||
|
||||
ActiveRecord::Base.class_eval do
|
||||
include ScottBarron::Acts::StateMachine
|
||||
end
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
module ScottBarron #:nodoc:
|
||||
module Acts #:nodoc:
|
||||
module StateMachine #:nodoc:
|
||||
class InvalidState < Exception #:nodoc:
|
||||
end
|
||||
class NoInitialState < Exception #:nodoc:
|
||||
end
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend ActMacro
|
||||
end
|
||||
|
||||
module SupportingClasses
|
||||
class State
|
||||
attr_reader :name
|
||||
|
||||
def initialize(name, opts)
|
||||
@name, @opts = name, opts
|
||||
end
|
||||
|
||||
def entering(record)
|
||||
enteract = @opts[:enter]
|
||||
record.send(:run_transition_action, enteract) if enteract
|
||||
end
|
||||
|
||||
def entered(record)
|
||||
afteractions = @opts[:after]
|
||||
return unless afteractions
|
||||
Array(afteractions).each do |afteract|
|
||||
record.send(:run_transition_action, afteract)
|
||||
end
|
||||
end
|
||||
|
||||
def exited(record)
|
||||
exitact = @opts[:exit]
|
||||
record.send(:run_transition_action, exitact) if exitact
|
||||
end
|
||||
end
|
||||
|
||||
class StateTransition
|
||||
attr_reader :from, :to, :opts
|
||||
|
||||
def initialize(opts)
|
||||
@from, @to, @guard = opts[:from], opts[:to], opts[:guard]
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def guard(obj)
|
||||
@guard ? obj.send(:run_transition_action, @guard) : true
|
||||
end
|
||||
|
||||
def perform(record)
|
||||
return false unless guard(record)
|
||||
loopback = record.current_state == to
|
||||
states = record.class.read_inheritable_attribute(:states)
|
||||
next_state = states[to]
|
||||
old_state = states[record.current_state]
|
||||
|
||||
next_state.entering(record) unless loopback
|
||||
if record.new_record?
|
||||
record.send(record.class.state_column.to_s + '=', to.to_s)
|
||||
else
|
||||
record.update_attribute(record.class.state_column, to.to_s)
|
||||
end
|
||||
|
||||
next_state.entered(record) unless loopback
|
||||
old_state.exited(record) unless loopback
|
||||
true
|
||||
end
|
||||
|
||||
def ==(obj)
|
||||
@from == obj.from && @to == obj.to
|
||||
end
|
||||
end
|
||||
|
||||
class Event
|
||||
attr_reader :name
|
||||
attr_reader :transitions
|
||||
attr_reader :opts
|
||||
|
||||
def initialize(name, opts, transition_table, &block)
|
||||
@name = name.to_sym
|
||||
@transitions = transition_table[@name] = []
|
||||
instance_eval(&block) if block
|
||||
@opts = opts
|
||||
@opts.freeze
|
||||
@transitions.freeze
|
||||
freeze
|
||||
end
|
||||
|
||||
def next_states(record)
|
||||
@transitions.select { |t| t.from == record.current_state }
|
||||
end
|
||||
|
||||
def fire(record)
|
||||
next_states(record).each do |transition|
|
||||
break true if transition.perform(record)
|
||||
end
|
||||
end
|
||||
|
||||
def transitions(trans_opts)
|
||||
Array(trans_opts[:from]).each do |s|
|
||||
@transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActMacro
|
||||
# Configuration options are
|
||||
#
|
||||
# * +column+ - specifies the column name to use for keeping the state (default: state)
|
||||
# * +initial+ - specifies an initial state for newly created objects (required)
|
||||
def acts_as_state_machine(opts)
|
||||
self.extend(ClassMethods)
|
||||
raise NoInitialState unless opts[:initial]
|
||||
|
||||
write_inheritable_attribute :states, {}
|
||||
write_inheritable_attribute :initial_state, opts[:initial]
|
||||
write_inheritable_attribute :transition_table, {}
|
||||
write_inheritable_attribute :event_table, {}
|
||||
write_inheritable_attribute :state_column, opts[:column] || 'state'
|
||||
|
||||
class_inheritable_reader :initial_state
|
||||
class_inheritable_reader :state_column
|
||||
class_inheritable_reader :transition_table
|
||||
class_inheritable_reader :event_table
|
||||
|
||||
self.send(:include, ScottBarron::Acts::StateMachine::InstanceMethods)
|
||||
|
||||
before_create :set_initial_state
|
||||
after_create :run_initial_state_actions
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def set_initial_state #:nodoc:
|
||||
write_attribute self.class.state_column, self.class.initial_state.to_s
|
||||
end
|
||||
|
||||
def run_initial_state_actions
|
||||
initial = self.class.read_inheritable_attribute(:states)[self.class.initial_state.to_sym]
|
||||
initial.entering(self)
|
||||
initial.entered(self)
|
||||
end
|
||||
|
||||
# Returns the current state the object is in, as a Ruby symbol.
|
||||
def current_state
|
||||
x = self.send(self.class.state_column)
|
||||
return x.to_sym if not x.nil?
|
||||
|
||||
# if current state is not yet set, set it
|
||||
self.set_initial_state
|
||||
return self.current_state
|
||||
end
|
||||
|
||||
# Returns what the next state for a given event would be, as a Ruby symbol.
|
||||
def next_state_for_event(event)
|
||||
ns = next_states_for_event(event)
|
||||
ns.empty? ? nil : ns.first.to
|
||||
end
|
||||
|
||||
def next_states_for_event(event)
|
||||
self.class.read_inheritable_attribute(:transition_table)[event.to_sym].select do |s|
|
||||
s.from == current_state
|
||||
end
|
||||
end
|
||||
|
||||
def run_transition_action(action)
|
||||
Symbol === action ? self.method(action).call : action.call(self)
|
||||
end
|
||||
private :run_transition_action
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Returns an array of all known states.
|
||||
def states
|
||||
read_inheritable_attribute(:states).keys
|
||||
end
|
||||
|
||||
# Define an event. This takes a block which describes all valid transitions
|
||||
# for this event.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class Order < ActiveRecord::Base
|
||||
# acts_as_state_machine :initial => :open
|
||||
#
|
||||
# state :open
|
||||
# state :closed
|
||||
#
|
||||
# event :close_order do
|
||||
# transitions :to => :closed, :from => :open
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# +transitions+ takes a hash where <tt>:to</tt> is the state to transition
|
||||
# to and <tt>:from</tt> is a state (or Array of states) from which this
|
||||
# event can be fired.
|
||||
#
|
||||
# This creates an instance method used for firing the event. The method
|
||||
# created is the name of the event followed by an exclamation point (!).
|
||||
# Example: <tt>order.close_order!</tt>.
|
||||
def event(event, opts={}, &block)
|
||||
tt = read_inheritable_attribute(:transition_table)
|
||||
|
||||
et = read_inheritable_attribute(:event_table)
|
||||
e = et[event.to_sym] = SupportingClasses::Event.new(event, opts, tt, &block)
|
||||
define_method("#{event.to_s}!") { e.fire(self) }
|
||||
end
|
||||
|
||||
# Define a state of the system. +state+ can take an optional Proc object
|
||||
# which will be executed every time the system transitions into that
|
||||
# state. The proc will be passed the current object.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class Order < ActiveRecord::Base
|
||||
# acts_as_state_machine :initial => :open
|
||||
#
|
||||
# state :open
|
||||
# state :closed, Proc.new { |o| Mailer.send_notice(o) }
|
||||
# end
|
||||
def state(name, opts={})
|
||||
state = SupportingClasses::State.new(name.to_sym, opts)
|
||||
read_inheritable_attribute(:states)[name.to_sym] = state
|
||||
|
||||
define_method("#{state.name}?") { current_state == state.name }
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.find to conveniently find all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +number+ - This is just :first or :all from ActiveRecord +find+
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +find+
|
||||
def find_in_state(number, state, *args)
|
||||
with_state_scope state do
|
||||
find(number, *args)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.count to conveniently count all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +find+
|
||||
def count_in_state(state, *args)
|
||||
with_state_scope state do
|
||||
count(*args)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.calculate to conveniently calculate all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +calculate+
|
||||
def calculate_in_state(state, *args)
|
||||
with_state_scope state do
|
||||
calculate(*args)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def with_state_scope(state)
|
||||
raise InvalidState unless states.include?(state)
|
||||
|
||||
with_scope :find => {:conditions => ["#{table_name}.#{state_column} = ?", state.to_s]} do
|
||||
yield if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
|
||||
include ScottBarron::Acts::StateMachine
|
||||
|
||||
class ActsAsStateMachineTest < Test::Unit::TestCase
|
||||
fixtures :conversations
|
||||
|
||||
def test_no_initial_value_raises_exception
|
||||
assert_raise(NoInitialState) {
|
||||
Person.acts_as_state_machine({})
|
||||
}
|
||||
end
|
||||
|
||||
def test_initial_state_value
|
||||
assert_equal :needs_attention, Conversation.initial_state
|
||||
end
|
||||
|
||||
def test_column_was_set
|
||||
assert_equal 'state_machine', Conversation.state_column
|
||||
end
|
||||
|
||||
def test_initial_state
|
||||
c = Conversation.create
|
||||
assert_equal :needs_attention, c.current_state
|
||||
assert c.needs_attention?
|
||||
end
|
||||
|
||||
def test_states_were_set
|
||||
[:needs_attention, :read, :closed, :awaiting_response, :junk].each do |s|
|
||||
assert Conversation.states.include?(s)
|
||||
end
|
||||
end
|
||||
|
||||
def test_event_methods_created
|
||||
c = Conversation.create
|
||||
%w(new_message! view! reply! close! junk! unjunk!).each do |event|
|
||||
assert c.respond_to?(event)
|
||||
end
|
||||
end
|
||||
|
||||
def test_query_methods_created
|
||||
c = Conversation.create
|
||||
%w(needs_attention? read? closed? awaiting_response? junk?).each do |event|
|
||||
assert c.respond_to?(event)
|
||||
end
|
||||
end
|
||||
|
||||
def test_transition_table
|
||||
tt = Conversation.transition_table
|
||||
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :read, :to => :needs_attention))
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :closed, :to => :needs_attention))
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :awaiting_response, :to => :needs_attention))
|
||||
end
|
||||
|
||||
def test_next_state_for_event
|
||||
c = Conversation.create
|
||||
assert_equal :read, c.next_state_for_event(:view)
|
||||
end
|
||||
|
||||
def test_change_state
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
assert c.read?
|
||||
end
|
||||
|
||||
def test_can_go_from_read_to_closed_because_guard_passes
|
||||
c = Conversation.create
|
||||
c.can_close = true
|
||||
c.view!
|
||||
c.reply!
|
||||
c.close!
|
||||
assert_equal :closed, c.current_state
|
||||
end
|
||||
|
||||
def test_cannot_go_from_read_to_closed_because_of_guard
|
||||
c = Conversation.create
|
||||
c.can_close = false
|
||||
c.view!
|
||||
c.reply!
|
||||
c.close!
|
||||
assert_equal :read, c.current_state
|
||||
end
|
||||
|
||||
def test_ignore_invalid_events
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
c.junk!
|
||||
|
||||
# This is the invalid event
|
||||
c.new_message!
|
||||
assert_equal :junk, c.current_state
|
||||
end
|
||||
|
||||
def test_entry_action_executed
|
||||
c = Conversation.create
|
||||
c.read_enter = false
|
||||
c.view!
|
||||
assert c.read_enter
|
||||
end
|
||||
|
||||
def test_after_actions_executed
|
||||
c = Conversation.create
|
||||
|
||||
c.read_after_first = false
|
||||
c.read_after_second = false
|
||||
c.closed_after = false
|
||||
|
||||
c.view!
|
||||
assert c.read_after_first
|
||||
assert c.read_after_second
|
||||
|
||||
c.can_close = true
|
||||
c.close!
|
||||
|
||||
assert c.closed_after
|
||||
assert_equal :closed, c.current_state
|
||||
end
|
||||
|
||||
def test_after_actions_not_run_on_loopback_transition
|
||||
c = Conversation.create
|
||||
|
||||
c.view!
|
||||
c.read_after_first = false
|
||||
c.read_after_second = false
|
||||
c.view!
|
||||
|
||||
assert !c.read_after_first
|
||||
assert !c.read_after_second
|
||||
|
||||
c.can_close = true
|
||||
|
||||
c.close!
|
||||
c.closed_after = false
|
||||
c.close!
|
||||
|
||||
assert !c.closed_after
|
||||
end
|
||||
|
||||
def test_exit_action_executed
|
||||
c = Conversation.create
|
||||
c.read_exit = false
|
||||
c.view!
|
||||
c.junk!
|
||||
assert c.read_exit
|
||||
end
|
||||
|
||||
def test_entry_and_exit_not_run_on_loopback_transition
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
c.read_enter = false
|
||||
c.read_exit = false
|
||||
c.view!
|
||||
assert !c.read_enter
|
||||
assert !c.read_exit
|
||||
end
|
||||
|
||||
def test_entry_and_after_actions_called_for_initial_state
|
||||
c = Conversation.create
|
||||
assert c.needs_attention_enter
|
||||
assert c.needs_attention_after
|
||||
end
|
||||
|
||||
def test_run_transition_action_is_private
|
||||
c = Conversation.create
|
||||
assert_raise(NoMethodError) { c.run_transition_action :foo }
|
||||
end
|
||||
|
||||
def test_find_all_in_state
|
||||
cs = Conversation.find_in_state(:all, :read)
|
||||
|
||||
assert_equal 2, cs.size
|
||||
end
|
||||
|
||||
def test_find_first_in_state
|
||||
c = Conversation.find_in_state(:first, :read)
|
||||
|
||||
assert_equal conversations(:first).id, c.id
|
||||
end
|
||||
|
||||
def test_find_all_in_state_with_conditions
|
||||
cs = Conversation.find_in_state(:all, :read, :conditions => ['subject = ?', conversations(:second).subject])
|
||||
|
||||
assert_equal 1, cs.size
|
||||
assert_equal conversations(:second).id, cs.first.id
|
||||
end
|
||||
|
||||
def test_find_first_in_state_with_conditions
|
||||
c = Conversation.find_in_state(:first, :read, :conditions => ['subject = ?', conversations(:second).subject])
|
||||
assert_equal conversations(:second).id, c.id
|
||||
end
|
||||
|
||||
def test_count_in_state
|
||||
cnt0 = Conversation.count(['state_machine = ?', 'read'])
|
||||
cnt = Conversation.count_in_state(:read)
|
||||
|
||||
assert_equal cnt0, cnt
|
||||
end
|
||||
|
||||
def test_count_in_state_with_conditions
|
||||
cnt0 = Conversation.count(['state_machine = ? AND subject = ?', 'read', 'Foo'])
|
||||
cnt = Conversation.count_in_state(:read, ['subject = ?', 'Foo'])
|
||||
|
||||
assert_equal cnt0, cnt
|
||||
end
|
||||
|
||||
def test_find_in_invalid_state_raises_exception
|
||||
assert_raise(InvalidState) {
|
||||
Conversation.find_in_state(:all, :dead)
|
||||
}
|
||||
end
|
||||
|
||||
def test_count_in_invalid_state_raises_exception
|
||||
assert_raise(InvalidState) {
|
||||
Conversation.count_in_state(:dead)
|
||||
}
|
||||
end
|
||||
|
||||
def test_can_access_events_via_event_table
|
||||
event = Conversation.event_table[:junk]
|
||||
assert_equal :junk, event.name
|
||||
assert_equal "finished", event.opts[:note]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
sqlite:
|
||||
:adapter: sqlite
|
||||
:dbfile: state_machine.sqlite.db
|
||||
sqlite3:
|
||||
:adapter: sqlite3
|
||||
:dbfile: state_machine.sqlite3.db
|
||||
postgresql:
|
||||
:adapter: postgresql
|
||||
:username: postgres
|
||||
:password: postgres
|
||||
:database: state_machine_test
|
||||
:min_messages: ERROR
|
||||
mysql:
|
||||
:adapter: mysql
|
||||
:host: localhost
|
||||
:username: rails
|
||||
:password:
|
||||
:database: state_machine_test
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
class Conversation < ActiveRecord::Base
|
||||
attr_writer :can_close
|
||||
attr_accessor :read_enter, :read_exit, :read_after_first, :read_after_second,
|
||||
:closed_after, :needs_attention_enter, :needs_attention_after
|
||||
|
||||
acts_as_state_machine :initial => :needs_attention, :column => 'state_machine'
|
||||
|
||||
state :needs_attention, :enter => Proc.new { |o| o.needs_attention_enter = true },
|
||||
:after => Proc.new { |o| o.needs_attention_after = true }
|
||||
|
||||
state :read, :enter => :read_enter_action,
|
||||
:exit => Proc.new { |o| o.read_exit = true },
|
||||
:after => [:read_after_first_action, :read_after_second_action]
|
||||
|
||||
state :closed, :after => :closed_after_action
|
||||
state :awaiting_response
|
||||
state :junk
|
||||
|
||||
event :new_message do
|
||||
transitions :to => :needs_attention, :from => [:read, :closed, :awaiting_response]
|
||||
end
|
||||
|
||||
event :view do
|
||||
transitions :to => :read, :from => [:needs_attention, :read]
|
||||
end
|
||||
|
||||
event :reply do
|
||||
transitions :to => :awaiting_response, :from => [:read, :closed]
|
||||
end
|
||||
|
||||
event :close do
|
||||
transitions :to => :closed, :from => [:read, :awaiting_response], :guard => Proc.new {|o| o.can_close?}
|
||||
transitions :to => :read, :from => [:read, :awaiting_response], :guard => :always_true
|
||||
end
|
||||
|
||||
event :junk, :note => "finished" do
|
||||
transitions :to => :junk, :from => [:read, :closed, :awaiting_response]
|
||||
end
|
||||
|
||||
event :unjunk do
|
||||
transitions :to => :closed, :from => :junk
|
||||
end
|
||||
|
||||
def can_close?
|
||||
@can_close
|
||||
end
|
||||
|
||||
def read_enter_action
|
||||
self.read_enter = true
|
||||
end
|
||||
|
||||
def always_true
|
||||
true
|
||||
end
|
||||
|
||||
def read_after_first_action
|
||||
self.read_after_first = true
|
||||
end
|
||||
|
||||
def read_after_second_action
|
||||
self.read_after_second = true
|
||||
end
|
||||
|
||||
def closed_after_action
|
||||
self.closed_after = true
|
||||
end
|
||||
end
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
first:
|
||||
id: 1
|
||||
state_machine: read
|
||||
subject: This is a test
|
||||
closed: false
|
||||
|
||||
second:
|
||||
id: 2
|
||||
state_machine: read
|
||||
subject: Foo
|
||||
closed: false
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
class Person < ActiveRecord::Base
|
||||
end
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
ActiveRecord::Schema.define(:version => 1) do
|
||||
create_table :conversations, :force => true do |t|
|
||||
t.column :state_machine, :string
|
||||
t.column :subject, :string
|
||||
t.column :closed, :boolean
|
||||
end
|
||||
|
||||
create_table :people, :force => true do |t|
|
||||
t.column :name, :string
|
||||
end
|
||||
end
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
RAILS_ROOT = File.dirname(__FILE__)
|
||||
|
||||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'active_record'
|
||||
require 'active_record/fixtures'
|
||||
require 'active_support/binding_of_caller'
|
||||
require 'active_support/breakpoint'
|
||||
require "#{File.dirname(__FILE__)}/../init"
|
||||
|
||||
|
||||
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
||||
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
||||
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
|
||||
|
||||
load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb")
|
||||
|
||||
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
||||
$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
|
||||
|
||||
class Test::Unit::TestCase #:nodoc:
|
||||
def create_fixtures(*table_names)
|
||||
if block_given?
|
||||
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
|
||||
else
|
||||
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
|
||||
end
|
||||
end
|
||||
|
||||
# Turn off transactional fixtures if you're working with MyISAM tables in MySQL
|
||||
self.use_transactional_fixtures = true
|
||||
|
||||
# Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
|
||||
self.use_instantiated_fixtures = false
|
||||
|
||||
# Add more helper methods to be used by all tests here...
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue