faasm 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ * In AR persistence, move state column from class level variables into the StateMachine object for the class
2
+
3
+ * allowed :to array and :on_transition callback [Kevin Triplett]
4
+
5
+ * Support enter and exit actions on states
6
+
7
+ * Use named_scope in AR persistence layer, if available [Jan De Poorter]
8
+
9
+ * Incremented version number
10
+
11
+ * Cleaned up aasm_states_for_select to return the value as a string
12
+
13
+ * Specs and bug fixes for the ActiveRecordPersistence, keeping persistence columns in sync
14
+ Allowing for nil values in states for active record
15
+ Only set state to default state before_validation_on_create
16
+ New rake task to uninstall, build and reinstall the gem (useful for development)
17
+ Changed scott's email address to protect it from spambots when publishing rdocs
18
+ New non-(!) methods that allow for firing events without persisting [Jeff Dean]
19
+
20
+ * Added aasm_states_for_select that will return a select friendly collection of states.
21
+
22
+ * Add some event callbacks, #aasm_event_fired(from, to), and #aasm_event_failed(event)
23
+ Based on transition logging suggestion [Artem Vasiliev] and timestamp column suggestion [Mike Ferrier]
24
+
25
+ * Add #aasm_events_for_state and #aasm_events_for_current_state [Joao Paulo Lins]
26
+
27
+ * Ensure that a state is written for a new record even if aasm_current_state or
28
+ {state}= are never called.
29
+
30
+ * Fix AR persistence so new records have their state set. [Joao Paulo Lins]
31
+
32
+ * Make #event! methods return a boolean [Joel Chippindale]
33
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Scott Barron
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,122 @@
1
+ = AASM - Ruby state machines
2
+
3
+ This package contains AASM, a library for adding finite state machines to Ruby classes.
4
+
5
+ AASM started as the acts_as_state_machine plugin but has evolved into a more generic library that no longer targets only ActiveRecord models.
6
+
7
+ AASM has the following features:
8
+
9
+ * States
10
+ * Machines
11
+ * Events
12
+ * Transitions
13
+
14
+ == New Callbacks
15
+
16
+ The callback chain & order on a successful event looks like:
17
+
18
+ oldstate:exit*
19
+ event:before
20
+ __find transition, if possible__
21
+ transition:on_transition*
22
+ oldstate:before_exit
23
+ newstate:before_enter
24
+ newstate:enter*
25
+ __update state__
26
+ event:success*
27
+ oldstate:after_exit
28
+ oldstate:after_enter
29
+ event:after
30
+ obj:aasm_event_fired*
31
+
32
+ (*) marks old callbacks
33
+
34
+
35
+ == Download
36
+
37
+ The latest AASM can currently be pulled from the git repository on github.
38
+
39
+ * http://github.com/ttilley/aasm/tree/master
40
+
41
+
42
+ == Installation
43
+
44
+ === From GitHub hosted gems
45
+
46
+ % sudo gem sources -a http://gems.github.com # (you only need to do this once)
47
+ % sudo gem install ttilley-aasm
48
+
49
+ === Building your own gems
50
+
51
+ % rake gem
52
+ % sudo gem install pkg/aasm-2.1.gem
53
+
54
+
55
+ == Simple Example
56
+
57
+ Here's a quick example highlighting some of the features.
58
+
59
+ class Conversation
60
+ include AASM
61
+
62
+ aasm_initial_state :unread
63
+
64
+ aasm_state :unread
65
+ aasm_state :read
66
+ aasm_state :closed
67
+
68
+
69
+ aasm_event :view do
70
+ transitions :to => :read, :from => [:unread]
71
+ end
72
+
73
+ aasm_event :close do
74
+ transitions :to => :closed, :from => [:read, :unread]
75
+ end
76
+ end
77
+
78
+ == A Slightly More Complex Example
79
+
80
+ This example uses a few of the more complex features available.
81
+
82
+ class Relationship
83
+ include AASM
84
+
85
+ aasm_initial_state Proc.new { |relationship| relationship.strictly_for_fun? ? :intimate : :dating }
86
+
87
+ aasm_state :dating, :enter => :make_happy, :exit => :make_depressed
88
+ aasm_state :intimate, :enter => :make_very_happy, :exit => :never_speak_again
89
+ aasm_state :married, :enter => :give_up_intimacy, :exit => :buy_exotic_car_and_wear_a_combover
90
+
91
+ aasm_event :get_intimate do
92
+ transitions :to => :intimate, :from => [:dating], :guard => :drunk?
93
+ end
94
+
95
+ aasm_event :get_married do
96
+ transitions :to => :married, :from => [:dating, :intimate], :guard => :willing_to_give_up_manhood?
97
+ end
98
+
99
+ def strictly_for_fun?; end
100
+ def drunk?; end
101
+ def willing_to_give_up_manhood?; end
102
+ def make_happy; end
103
+ def make_depressed; end
104
+ def make_very_happy; end
105
+ def never_speak_again; end
106
+ def give_up_intimacy; end
107
+ def buy_exotic_car_and_wear_a_combover; end
108
+ end
109
+
110
+ = Other Stuff
111
+
112
+ Author:: Scott Barron <scott at elitists dot net>
113
+ License:: Original code Copyright 2006, 2007, 2008 by Scott Barron.
114
+ Released under an MIT-style license. See the LICENSE file
115
+ included in the distribution.
116
+
117
+ == Warranty
118
+
119
+ This software is provided "as is" and without any express or
120
+ implied warranties, including, without limitation, the implied
121
+ warranties of merchantibility and fitness for a particular
122
+ purpose.
@@ -0,0 +1,95 @@
1
+ # Copyright 2008 Scott Barron (scott@elitists.net)
2
+ # All rights reserved
3
+
4
+ # This file may be distributed under an MIT style license.
5
+ # See MIT-LICENSE for details.
6
+
7
+ begin
8
+ require 'rubygems'
9
+ require 'rake/gempackagetask'
10
+ require 'rake/testtask'
11
+ require 'rake/rdoctask'
12
+ require 'spec/rake/spectask'
13
+ rescue Exception
14
+ nil
15
+ end
16
+
17
+ if `ruby -Ilib -raasm -e "print AASM.Version"` =~ /([0-9.]+)$/
18
+ CURRENT_VERSION = $1
19
+ else
20
+ CURRENT_VERSION = '0.0.0'
21
+ end
22
+ $package_version = CURRENT_VERSION
23
+
24
+ PKG_FILES = FileList['[A-Z]*',
25
+ 'lib/**/*.rb',
26
+ 'doc/**/*'
27
+ ]
28
+
29
+ desc 'Generate documentation for the acts as state machine plugin.'
30
+ rd = Rake::RDocTask.new(:rdoc) do |rdoc|
31
+ rdoc.rdoc_dir = 'html'
32
+ rdoc.template = 'doc/jamis.rb'
33
+ rdoc.rdoc_dir = 'rdoc'
34
+ rdoc.title = 'AASM'
35
+ rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README.rdoc' << '--title' << 'AASM'
36
+ rdoc.rdoc_files.include('README.rdoc', 'MIT-LICENSE', 'TODO', 'CHANGELOG')
37
+ rdoc.rdoc_files.include('lib/*.rb', 'lib/**/*.rb', 'doc/**/*.rdoc')
38
+ end
39
+
40
+ if !defined?(Gem)
41
+ puts "Package target requires RubyGEMs"
42
+ else
43
+ spec = Gem::Specification.new do |s|
44
+ s.name = 'aasm'
45
+ s.version = $package_version
46
+ s.summary = 'State machine mixin for Ruby objects'
47
+ s.description = <<EOF
48
+ AASM is a continuation of the acts as state machine rails plugin, built for plain Ruby objects.
49
+ EOF
50
+
51
+ s.files = PKG_FILES.to_a
52
+ s.require_path = 'lib'
53
+ s.has_rdoc = true
54
+ s.extra_rdoc_files = rd.rdoc_files.reject {|fn| fn =~ /\.rb$/}.to_a
55
+ s.rdoc_options = rd.options
56
+
57
+ s.authors = ['Scott Barron', 'Scott Petersen', 'Travis Tilley']
58
+ s.email = 'ttilley@gmail.com'
59
+ s.homepage = 'http://github.com/ttilley/aasm'
60
+ end
61
+
62
+ package_task = Rake::GemPackageTask.new(spec) do |pkg|
63
+ pkg.need_zip = true
64
+ pkg.need_tar = true
65
+ end
66
+ end
67
+
68
+ if !defined?(Spec)
69
+ puts "spec and cruise targets require RSpec"
70
+ else
71
+ desc "Run all examples with RCov"
72
+ Spec::Rake::SpecTask.new('cruise') do |t|
73
+ t.spec_files = FileList['spec/**/*.rb']
74
+ t.rcov = true
75
+ t.rcov_opts = ['--exclude', 'spec', '--exclude', 'Library', '--exclude', 'rcov.rb']
76
+ end
77
+
78
+ desc "Run all examples"
79
+ Spec::Rake::SpecTask.new('spec') do |t|
80
+ t.spec_files = FileList['spec/**/*.rb']
81
+ t.rcov = false
82
+ t.spec_opts = ['-cfs']
83
+ end
84
+ end
85
+
86
+ if !defined?(Gem)
87
+ puts "Package target requires RubyGEMs"
88
+ else
89
+ desc "sudo gem uninstall aasm && rake gem && sudo gem install pkg/aasm-3.0.0.gem"
90
+ task :reinstall do
91
+ puts `sudo gem uninstall aasm && rake gem && sudo gem install pkg/aasm-3.0.0.gem`
92
+ end
93
+ end
94
+
95
+ task :default => [:spec]
data/TODO ADDED
@@ -0,0 +1,9 @@
1
+ Before Next Release:
2
+
3
+ * Add #aasm_next_state_for_event
4
+ * Add #aasm_next_states_for_event
5
+
6
+ Cool ideas from users:
7
+
8
+ * Support multiple state machines on one object (Chris Nelson)
9
+ * http://justbarebones.blogspot.com/2007/11/actsasstatemachine-enhancements.html (Chetan Patil)
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'aasm', 'aasm')
@@ -0,0 +1,185 @@
1
+ require File.join(File.dirname(__FILE__), 'event')
2
+ require File.join(File.dirname(__FILE__), 'state')
3
+ require File.join(File.dirname(__FILE__), 'state_machine')
4
+ require File.join(File.dirname(__FILE__), 'persistence')
5
+
6
+ module AASM
7
+ def self.Version
8
+ '2.1.1'
9
+ end
10
+
11
+ class InvalidTransition < RuntimeError
12
+ end
13
+
14
+ class UndefinedState < RuntimeError
15
+ end
16
+
17
+ def self.included(base) #:nodoc:
18
+ base.extend AASM::ClassMethods
19
+ AASM::Persistence.set_persistence(base)
20
+ unless AASM::StateMachine[base]
21
+ AASM::StateMachine[base] = AASM::StateMachine.new('')
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def inherited(klass)
27
+ AASM::StateMachine[klass] = AASM::StateMachine[self].clone
28
+ super
29
+ end
30
+
31
+ def aasm_initial_state(set_state=nil)
32
+ if set_state
33
+ AASM::StateMachine[self].initial_state = set_state
34
+ else
35
+ AASM::StateMachine[self].initial_state
36
+ end
37
+ end
38
+
39
+ def aasm_initial_state=(state)
40
+ AASM::StateMachine[self].initial_state = state
41
+ end
42
+
43
+ def aasm_state(name, options={})
44
+ sm = AASM::StateMachine[self]
45
+ sm.create_state(name, options)
46
+ sm.initial_state = name unless sm.initial_state
47
+
48
+ define_method("#{name.to_s}?") do
49
+ aasm_current_state == name
50
+ end
51
+ end
52
+
53
+ def aasm_event(name, options = {}, &block)
54
+ sm = AASM::StateMachine[self]
55
+
56
+ unless sm.events.has_key?(name)
57
+ sm.events[name] = AASM::SupportingClasses::Event.new(name, options, &block)
58
+ end
59
+
60
+ define_method("#{name.to_s}!") do |*args|
61
+ aasm_fire_event(name, true, *args)
62
+ end
63
+
64
+ define_method("#{name.to_s}") do |*args|
65
+ aasm_fire_event(name, false, *args)
66
+ end
67
+ end
68
+
69
+ def aasm_states
70
+ AASM::StateMachine[self].states
71
+ end
72
+
73
+ def aasm_events
74
+ AASM::StateMachine[self].events
75
+ end
76
+
77
+ def aasm_states_for_select
78
+ AASM::StateMachine[self].states.map { |state| state.for_select }
79
+ end
80
+
81
+ end
82
+
83
+ # Instance methods
84
+ def aasm_current_state
85
+ return @aasm_current_state if @aasm_current_state
86
+
87
+ if self.respond_to?(:aasm_read_state) || self.private_methods.include?('aasm_read_state')
88
+ @aasm_current_state = aasm_read_state
89
+ end
90
+ return @aasm_current_state if @aasm_current_state
91
+ aasm_determine_state_name(self.class.aasm_initial_state)
92
+ end
93
+
94
+ def aasm_events_for_current_state
95
+ aasm_events_for_state(aasm_current_state)
96
+ end
97
+
98
+ def aasm_events_for_state(state)
99
+ events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
100
+ events.map {|event| event.name}
101
+ end
102
+
103
+ private
104
+ def set_aasm_current_state_with_persistence(state)
105
+ save_success = true
106
+ if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
107
+ save_success = aasm_write_state(state)
108
+ end
109
+ self.aasm_current_state = state if save_success
110
+
111
+ save_success
112
+ end
113
+
114
+ def aasm_current_state=(state)
115
+ if self.respond_to?(:aasm_write_state_without_persistence) || self.private_methods.include?('aasm_write_state_without_persistence')
116
+ aasm_write_state_without_persistence(state)
117
+ end
118
+ @aasm_current_state = state
119
+ end
120
+
121
+ def aasm_determine_state_name(state)
122
+ case state
123
+ when Symbol, String
124
+ state
125
+ when Proc
126
+ state.call(self)
127
+ else
128
+ raise NotImplementedError, "Unrecognized state-type given. Expected Symbol, String, or Proc."
129
+ end
130
+ end
131
+
132
+ def aasm_state_object_for_state(name)
133
+ obj = self.class.aasm_states.find {|s| s == name}
134
+ raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
135
+ obj
136
+ end
137
+
138
+ def aasm_fire_event(name, persist, *args)
139
+ old_state = aasm_state_object_for_state(aasm_current_state)
140
+ event = self.class.aasm_events[name]
141
+
142
+ old_state.call_action(:exit, self)
143
+
144
+ # new event before callback
145
+ event.call_action(:before, self)
146
+
147
+ new_state_name = event.fire(self, *args)
148
+
149
+ unless new_state_name.nil?
150
+ new_state = aasm_state_object_for_state(new_state_name)
151
+
152
+ # new before_ callbacks
153
+ old_state.call_action(:before_exit, self)
154
+ new_state.call_action(:before_enter, self)
155
+
156
+ new_state.call_action(:enter, self)
157
+
158
+ persist_successful = true
159
+ if persist
160
+ persist_successful = set_aasm_current_state_with_persistence(new_state_name)
161
+ event.execute_success_callback(self) if persist_successful
162
+ else
163
+ self.aasm_current_state = new_state_name
164
+ end
165
+
166
+ if persist_successful
167
+ old_state.call_action(:after_exit, self)
168
+ new_state.call_action(:after_enter, self)
169
+ event.call_action(:after, self)
170
+
171
+ self.aasm_event_fired(name, old_state.name, self.aasm_current_state) if self.respond_to?(:aasm_event_fired)
172
+ else
173
+ self.aasm_event_failed(name, old_state.name) if self.respond_to?(:aasm_event_failed)
174
+ end
175
+
176
+ persist_successful
177
+ else
178
+ if self.respond_to?(:aasm_event_failed)
179
+ self.aasm_event_failed(name, old_state.name)
180
+ end
181
+
182
+ false
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,76 @@
1
+ require File.join(File.dirname(__FILE__), 'state_transition')
2
+
3
+ module AASM
4
+ module SupportingClasses
5
+ class Event
6
+ attr_reader :name, :success, :options
7
+
8
+ def initialize(name, options = {}, &block)
9
+ @name = name
10
+ @success = options[:success]
11
+ @transitions = []
12
+ @options = options
13
+ instance_eval(&block) if block
14
+ end
15
+
16
+ def fire(obj, to_state=nil, *args)
17
+ transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
18
+ raise AASM::InvalidTransition, "Event '#{name}' cannot transition from '#{obj.aasm_current_state}'" if transitions.size == 0
19
+
20
+ next_state = nil
21
+ transitions.each do |transition|
22
+ next if to_state and !Array(transition.to).include?(to_state)
23
+ if transition.perform(obj)
24
+ next_state = to_state || Array(transition.to).first
25
+ transition.execute(obj, *args)
26
+ break
27
+ end
28
+ end
29
+ next_state
30
+ end
31
+
32
+ def transitions_from_state?(state)
33
+ @transitions.any? { |t| t.from == state }
34
+ end
35
+
36
+ def transitions_from_state(state)
37
+ @transitions.select { |t| t.from == state }
38
+ end
39
+
40
+ def execute_success_callback(obj, success = nil)
41
+ callback = success || @success
42
+ case(callback)
43
+ when String, Symbol
44
+ obj.send(callback)
45
+ when Proc
46
+ callback.call(obj)
47
+ when Array
48
+ callback.each{|meth|self.execute_success_callback(obj, meth)}
49
+ end
50
+ end
51
+
52
+ def call_action(action, record)
53
+ action = @options[action]
54
+ case action
55
+ when Symbol, String
56
+ record.send(action)
57
+ when Proc
58
+ action.call(record)
59
+ when Array
60
+ action.each { |a| record.send(a) }
61
+ end
62
+ end
63
+
64
+ def all_transitions
65
+ @transitions
66
+ end
67
+
68
+ private
69
+ def transitions(trans_opts)
70
+ Array(trans_opts[:from]).each do |s|
71
+ @transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,16 @@
1
+ module AASM
2
+ module Persistence
3
+
4
+ # Checks to see this class or any of it's superclasses inherit from
5
+ # ActiveRecord::Base and if so includes ActiveRecordPersistence
6
+ def self.set_persistence(base)
7
+ # Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
8
+ hierarchy = base.ancestors.map {|klass| klass.to_s}
9
+
10
+ if hierarchy.include?("ActiveRecord::Base")
11
+ require File.join(File.dirname(__FILE__), 'persistence', 'active_record_persistence')
12
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,251 @@
1
+ module AASM
2
+ module Persistence
3
+ module ActiveRecordPersistence
4
+ # This method:
5
+ #
6
+ # * extends the model with ClassMethods
7
+ # * includes InstanceMethods
8
+ #
9
+ # Unless the corresponding methods are already defined, it includes
10
+ # * ReadState
11
+ # * WriteState
12
+ # * WriteStateWithoutPersistence
13
+ #
14
+ # Adds
15
+ #
16
+ # before_validation_on_create :aasm_ensure_initial_state
17
+ #
18
+ # As a result, it doesn't matter when you define your methods - the following 2 are equivalent
19
+ #
20
+ # class Foo < ActiveRecord::Base
21
+ # def aasm_write_state(state)
22
+ # "bar"
23
+ # end
24
+ # include AASM
25
+ # end
26
+ #
27
+ # class Foo < ActiveRecord::Base
28
+ # include AASM
29
+ # def aasm_write_state(state)
30
+ # "bar"
31
+ # end
32
+ # end
33
+ #
34
+ def self.included(base)
35
+ base.extend AASM::Persistence::ActiveRecordPersistence::ClassMethods
36
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
37
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::ReadState) unless base.method_defined?(:aasm_read_state)
38
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::WriteState) unless base.method_defined?(:aasm_write_state)
39
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence) unless base.method_defined?(:aasm_write_state_without_persistence)
40
+
41
+ if base.respond_to?(:named_scope)
42
+ base.extend(AASM::Persistence::ActiveRecordPersistence::NamedScopeMethods)
43
+
44
+ base.class_eval do
45
+ class << self
46
+ unless method_defined?(:aasm_state_without_named_scope)
47
+ alias_method :aasm_state_without_named_scope, :aasm_state
48
+ alias_method :aasm_state, :aasm_state_with_named_scope
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ #base.before_validation_on_create :aasm_ensure_initial_state
55
+
56
+ base.before_validation(:on => :create){ :aasm_ensure_initial_state }
57
+
58
+ end
59
+
60
+ module ClassMethods
61
+ # Maps to the aasm_column in the database. Deafults to "aasm_state". You can write:
62
+ #
63
+ # create_table :foos do |t|
64
+ # t.string :name
65
+ # t.string :aasm_state
66
+ # end
67
+ #
68
+ # class Foo < ActiveRecord::Base
69
+ # include AASM
70
+ # end
71
+ #
72
+ # OR:
73
+ #
74
+ # create_table :foos do |t|
75
+ # t.string :name
76
+ # t.string :status
77
+ # end
78
+ #
79
+ # class Foo < ActiveRecord::Base
80
+ # include AASM
81
+ # aasm_column :status
82
+ # end
83
+ #
84
+ # This method is both a getter and a setter
85
+ def aasm_column(column_name=nil)
86
+ if column_name
87
+ AASM::StateMachine[self].config.column = column_name.to_sym
88
+ # @aasm_column = column_name.to_sym
89
+ else
90
+ AASM::StateMachine[self].config.column ||= :aasm_state
91
+ # @aasm_column ||= :aasm_state
92
+ end
93
+ # @aasm_column
94
+ AASM::StateMachine[self].config.column
95
+ end
96
+
97
+ def find_in_state(number, state, *args)
98
+ with_state_scope state do
99
+ find(number, *args)
100
+ end
101
+ end
102
+
103
+ def count_in_state(state, *args)
104
+ with_state_scope state do
105
+ count(*args)
106
+ end
107
+ end
108
+
109
+ def calculate_in_state(state, *args)
110
+ with_state_scope state do
111
+ calculate(*args)
112
+ end
113
+ end
114
+
115
+ protected
116
+ def with_state_scope(state)
117
+ with_scope :find => {:conditions => ["#{table_name}.#{aasm_column} = ?", state.to_s]} do
118
+ yield if block_given?
119
+ end
120
+ end
121
+ end
122
+
123
+ module InstanceMethods
124
+
125
+ # Returns the current aasm_state of the object. Respects reload and
126
+ # any changes made to the aasm_state field directly
127
+ #
128
+ # Internally just calls <tt>aasm_read_state</tt>
129
+ #
130
+ # foo = Foo.find(1)
131
+ # foo.aasm_current_state # => :pending
132
+ # foo.aasm_state = "opened"
133
+ # foo.aasm_current_state # => :opened
134
+ # foo.close # => calls aasm_write_state_without_persistence
135
+ # foo.aasm_current_state # => :closed
136
+ # foo.reload
137
+ # foo.aasm_current_state # => :pending
138
+ #
139
+ def aasm_current_state
140
+ @current_state = aasm_read_state
141
+ end
142
+
143
+ private
144
+
145
+ # Ensures that if the aasm_state column is nil and the record is new
146
+ # that the initial state gets populated before validation on create
147
+ #
148
+ # foo = Foo.new
149
+ # foo.aasm_state # => nil
150
+ # foo.valid?
151
+ # foo.aasm_state # => "open" (where :open is the initial state)
152
+ #
153
+ #
154
+ # foo = Foo.find(:first)
155
+ # foo.aasm_state # => 1
156
+ # foo.aasm_state = nil
157
+ # foo.valid?
158
+ # foo.aasm_state # => nil
159
+ #
160
+ def aasm_ensure_initial_state
161
+ send("#{self.class.aasm_column}=", self.aasm_current_state.to_s)
162
+ end
163
+
164
+ end
165
+
166
+ module WriteStateWithoutPersistence
167
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
168
+ #
169
+ # foo = Foo.find(1)
170
+ # foo.aasm_current_state # => :opened
171
+ # foo.close
172
+ # foo.aasm_current_state # => :closed
173
+ # Foo.find(1).aasm_current_state # => :opened
174
+ # foo.save
175
+ # foo.aasm_current_state # => :closed
176
+ # Foo.find(1).aasm_current_state # => :closed
177
+ #
178
+ # NOTE: intended to be called from an event
179
+ def aasm_write_state_without_persistence(state)
180
+ write_attribute(self.class.aasm_column, state.to_s)
181
+ end
182
+ end
183
+
184
+ module WriteState
185
+ # Writes <tt>state</tt> to the state column and persists it to the database
186
+ # using update_attribute (which bypasses validation)
187
+ #
188
+ # foo = Foo.find(1)
189
+ # foo.aasm_current_state # => :opened
190
+ # foo.close!
191
+ # foo.aasm_current_state # => :closed
192
+ # Foo.find(1).aasm_current_state # => :closed
193
+ #
194
+ # NOTE: intended to be called from an event
195
+ def aasm_write_state(state)
196
+ old_value = read_attribute(self.class.aasm_column)
197
+ write_attribute(self.class.aasm_column, state.to_s)
198
+
199
+ unless self.save(false)
200
+ write_attribute(self.class.aasm_column, old_value)
201
+ return false
202
+ end
203
+
204
+ true
205
+ end
206
+ end
207
+
208
+ module ReadState
209
+
210
+ # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
211
+ #
212
+ # If it's a new record, and the aasm state column is blank it returns the initial state:
213
+ #
214
+ # class Foo < ActiveRecord::Base
215
+ # include AASM
216
+ # aasm_column :status
217
+ # aasm_state :opened
218
+ # aasm_state :closed
219
+ # end
220
+ #
221
+ # foo = Foo.new
222
+ # foo.current_state # => :opened
223
+ # foo.close
224
+ # foo.current_state # => :closed
225
+ #
226
+ # foo = Foo.find(1)
227
+ # foo.current_state # => :opened
228
+ # foo.aasm_state = nil
229
+ # foo.current_state # => nil
230
+ #
231
+ # NOTE: intended to be called from an event
232
+ #
233
+ # This allows for nil aasm states - be sure to add validation to your model
234
+ def aasm_read_state
235
+ if new_record?
236
+ send(self.class.aasm_column).blank? ? aasm_determine_state_name(self.class.aasm_initial_state) : send(self.class.aasm_column).to_sym
237
+ else
238
+ send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
239
+ end
240
+ end
241
+ end
242
+
243
+ module NamedScopeMethods
244
+ def aasm_state_with_named_scope name, options = {}
245
+ aasm_state_without_named_scope name, options
246
+ self.named_scope name, :conditions => { "#{table_name}.#{self.aasm_column}" => name.to_s} unless self.respond_to?(name)
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,35 @@
1
+ module AASM
2
+ module SupportingClasses
3
+ class State
4
+ attr_reader :name, :options
5
+
6
+ def initialize(name, options={})
7
+ @name, @options = name, options
8
+ end
9
+
10
+ def ==(state)
11
+ if state.is_a? Symbol
12
+ name == state
13
+ else
14
+ name == state.name
15
+ end
16
+ end
17
+
18
+ def call_action(action, record)
19
+ action = @options[action]
20
+ case action
21
+ when Symbol, String
22
+ record.send(action)
23
+ when Proc
24
+ action.call(record)
25
+ when Array
26
+ action.each { |a| record.send(a) }
27
+ end
28
+ end
29
+
30
+ def for_select
31
+ [name.to_s.gsub(/_/, ' ').capitalize, name.to_s]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ require 'ostruct'
2
+
3
+ module AASM
4
+ class StateMachine
5
+ def self.[](*args)
6
+ (@machines ||= {})[args]
7
+ end
8
+
9
+ def self.[]=(*args)
10
+ val = args.pop
11
+ (@machines ||= {})[args] = val
12
+ end
13
+
14
+ attr_accessor :states, :events, :initial_state, :config
15
+ attr_reader :name
16
+
17
+ def initialize(name)
18
+ @name = name
19
+ @initial_state = nil
20
+ @states = []
21
+ @events = {}
22
+ @config = OpenStruct.new
23
+ end
24
+
25
+ def clone
26
+ klone = super
27
+ klone.states = states.clone
28
+ klone
29
+ end
30
+
31
+ def create_state(name, options)
32
+ @states << AASM::SupportingClasses::State.new(name, options) unless @states.include?(name)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ module AASM
2
+ module SupportingClasses
3
+ class StateTransition
4
+ attr_reader :from, :to, :opts
5
+
6
+ def initialize(opts)
7
+ @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
8
+ @opts = opts
9
+ end
10
+
11
+ def perform(obj)
12
+ case @guard
13
+ when Symbol, String
14
+ obj.send(@guard)
15
+ when Proc
16
+ @guard.call(obj)
17
+ else
18
+ true
19
+ end
20
+ end
21
+
22
+ def execute(obj, *args)
23
+ case @on_transition
24
+ when Symbol, String
25
+ obj.send(@on_transition, *args)
26
+ when Proc
27
+ @on_transition.call(obj, *args)
28
+ end
29
+ end
30
+
31
+ def ==(obj)
32
+ @from == obj.from && @to == obj.to
33
+ end
34
+ end
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: faasm
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 2
7
+ - 1
8
+ - 1
9
+ version: 2.1.1
10
+ platform: ruby
11
+ authors:
12
+ - Scott Barron
13
+ - Scott Petersen
14
+ - Travis Tilley
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-01-30 00:00:00 +00:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rspec
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: shoulda
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: sdoc
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ type: :development
60
+ version_requirements: *id003
61
+ description: Fixed AASM is a continuation of the acts as state machine rails plugin, built for plain Ruby objects.
62
+ email: scott@elitists.net, ttilley@gmail.com
63
+ executables: []
64
+
65
+ extensions: []
66
+
67
+ extra_rdoc_files: []
68
+
69
+ files:
70
+ - CHANGELOG
71
+ - MIT-LICENSE
72
+ - README.rdoc
73
+ - Rakefile
74
+ - TODO
75
+ - lib/aasm.rb
76
+ - lib/aasm/aasm.rb
77
+ - lib/aasm/event.rb
78
+ - lib/aasm/persistence.rb
79
+ - lib/aasm/persistence/active_record_persistence.rb
80
+ - lib/aasm/state.rb
81
+ - lib/aasm/state_machine.rb
82
+ - lib/aasm/state_transition.rb
83
+ has_rdoc: true
84
+ homepage: http://rubyist.github.com/aasm/
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options:
89
+ - --charset=UTF-8
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ requirements: []
109
+
110
+ rubyforge_project:
111
+ rubygems_version: 1.3.7
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: State machine mixin for Ruby objects
115
+ test_files: []
116
+