mattwilkos-aasm 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: mattwilkos-aasm
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: Mattwilko 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
+