eric-aasm 2.0.2

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.
data/lib/aasm.rb ADDED
@@ -0,0 +1,146 @@
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
+ class InvalidTransition < RuntimeError
8
+ end
9
+
10
+ def self.included(base) #:nodoc:
11
+ # TODO - need to ensure that a machine is being created because
12
+ # AASM was either included or arrived at via inheritance. It
13
+ # cannot be both.
14
+ base.extend AASM::ClassMethods
15
+ AASM::Persistence.set_persistence(base)
16
+ AASM::StateMachine[base] = AASM::StateMachine.new('')
17
+
18
+ base.class_eval do
19
+ def base.inherited(klass)
20
+ AASM::StateMachine[klass] = AASM::StateMachine[self].dup
21
+ end
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def aasm_initial_state(set_state=nil)
27
+ if set_state
28
+ AASM::StateMachine[self].initial_state = set_state
29
+ else
30
+ AASM::StateMachine[self].initial_state
31
+ end
32
+ end
33
+
34
+ def aasm_initial_state=(state)
35
+ AASM::StateMachine[self].initial_state = state
36
+ end
37
+
38
+ def aasm_state(name, options={})
39
+ sm = AASM::StateMachine[self]
40
+ sm.create_state(name, options)
41
+ sm.initial_state = name unless sm.initial_state
42
+
43
+ define_method("#{name.to_s}?") do
44
+ aasm_current_state == name
45
+ end
46
+ end
47
+
48
+ def aasm_event(name, options = {}, &block)
49
+ sm = AASM::StateMachine[self]
50
+
51
+ unless sm.events.has_key?(name)
52
+ sm.events[name] = AASM::SupportingClasses::Event.new(name, options, &block)
53
+ end
54
+
55
+ define_method("#{name.to_s}!") do |*args|
56
+ aasm_fire_event(name, true, *args)
57
+ end
58
+
59
+ define_method("#{name.to_s}") do |*args|
60
+ aasm_fire_event(name, false, *args)
61
+ end
62
+ end
63
+
64
+ def aasm_states
65
+ AASM::StateMachine[self].states
66
+ end
67
+
68
+ def aasm_events
69
+ AASM::StateMachine[self].events
70
+ end
71
+
72
+ def aasm_states_for_select
73
+ AASM::StateMachine[self].states.map { |state| state.for_select }
74
+ end
75
+
76
+ end
77
+
78
+ # Instance methods
79
+ def aasm_current_state
80
+ return @aasm_current_state if @aasm_current_state
81
+
82
+ if self.respond_to?(:aasm_read_state) || self.private_methods.include?('aasm_read_state')
83
+ @aasm_current_state = aasm_read_state
84
+ end
85
+ return @aasm_current_state if @aasm_current_state
86
+ self.class.aasm_initial_state
87
+ end
88
+
89
+ def aasm_events_for_current_state
90
+ aasm_events_for_state(aasm_current_state)
91
+ end
92
+
93
+ def aasm_events_for_state(state)
94
+ events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
95
+ events.map {|event| event.name}
96
+ end
97
+
98
+ private
99
+ def aasm_current_state_with_persistence=(state)
100
+ if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
101
+ aasm_write_state(state)
102
+ end
103
+ self.aasm_current_state = state
104
+ end
105
+
106
+ def aasm_current_state=(state)
107
+ if self.respond_to?(:aasm_write_state_without_persistence) || self.private_methods.include?('aasm_write_state_without_persistence')
108
+ aasm_write_state_without_persistence(state)
109
+ end
110
+ @aasm_current_state = state
111
+ end
112
+
113
+ def aasm_state_object_for_state(name)
114
+ self.class.aasm_states.find {|s| s == name}
115
+ end
116
+
117
+ def aasm_fire_event(name, persist, *args)
118
+ aasm_state_object_for_state(aasm_current_state).call_action(:exit, self)
119
+
120
+ new_state = self.class.aasm_events[name].fire(self, *args)
121
+
122
+ unless new_state.nil?
123
+ if persist
124
+ self.aasm_current_state_with_persistence = new_state
125
+ else
126
+ self.aasm_current_state = new_state
127
+ end
128
+
129
+ self.send(self.class.aasm_events[name].success) if self.class.aasm_events[name].success
130
+
131
+ aasm_state_object_for_state(new_state).call_action(:enter, self)
132
+
133
+ if self.respond_to?(:aasm_event_fired)
134
+ self.aasm_event_fired(self.aasm_current_state, new_state)
135
+ end
136
+
137
+ true
138
+ else
139
+ if self.respond_to?(:aasm_event_failed)
140
+ self.aasm_event_failed(name)
141
+ end
142
+
143
+ false
144
+ end
145
+ end
146
+ end
data/lib/event.rb ADDED
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), 'state_transition')
2
+
3
+ module AASM
4
+ module SupportingClasses
5
+ class Event
6
+ attr_reader :name, :success
7
+
8
+ def initialize(name, options = {}, &block)
9
+ @name = name
10
+ @success = options[:success]
11
+ @transitions = []
12
+ instance_eval(&block) if block
13
+ end
14
+
15
+ def fire(obj, to_state=nil, *args)
16
+ transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
17
+ raise AASM::InvalidTransition, "Event '#{name}' cannot transition from '#{obj.aasm_current_state}'" if transitions.size == 0
18
+
19
+ next_state = nil
20
+ transitions.each do |transition|
21
+ next if to_state and !Array(transition.to).include?(to_state)
22
+ if transition.perform(obj)
23
+ next_state = to_state || Array(transition.to).first
24
+ transition.execute(obj, *args)
25
+ break
26
+ end
27
+ end
28
+ next_state
29
+ end
30
+
31
+ def transitions_from_state?(state)
32
+ @transitions.any? { |t| t.from == state }
33
+ end
34
+
35
+ private
36
+ def transitions(trans_opts)
37
+ Array(trans_opts[:from]).each do |s|
38
+ @transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,238 @@
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
+ alias_method :aasm_state_without_named_scope, :aasm_state
47
+ alias_method :aasm_state, :aasm_state_with_named_scope
48
+ end
49
+ end
50
+ end
51
+
52
+ base.before_validation_on_create :aasm_ensure_initial_state
53
+ end
54
+
55
+ module ClassMethods
56
+ # Maps to the aasm_column in the database. Deafults to "aasm_state". You can write:
57
+ #
58
+ # create_table :foos do |t|
59
+ # t.string :name
60
+ # t.string :aasm_state
61
+ # end
62
+ #
63
+ # class Foo < ActiveRecord::Base
64
+ # include AASM
65
+ # end
66
+ #
67
+ # OR:
68
+ #
69
+ # create_table :foos do |t|
70
+ # t.string :name
71
+ # t.string :status
72
+ # end
73
+ #
74
+ # class Foo < ActiveRecord::Base
75
+ # include AASM
76
+ # aasm_column :status
77
+ # end
78
+ #
79
+ # This method is both a getter and a setter
80
+ def aasm_column(column_name=nil)
81
+ if column_name
82
+ AASM::StateMachine[self].config.column = column_name.to_sym
83
+ # @aasm_column = column_name.to_sym
84
+ else
85
+ AASM::StateMachine[self].config.column ||= :aasm_state
86
+ # @aasm_column ||= :aasm_state
87
+ end
88
+ # @aasm_column
89
+ AASM::StateMachine[self].config.column
90
+ end
91
+
92
+ def find_in_state(number, state, *args)
93
+ with_state_scope state do
94
+ find(number, *args)
95
+ end
96
+ end
97
+
98
+ def count_in_state(state, *args)
99
+ with_state_scope state do
100
+ count(*args)
101
+ end
102
+ end
103
+
104
+ def calculate_in_state(state, *args)
105
+ with_state_scope state do
106
+ calculate(*args)
107
+ end
108
+ end
109
+
110
+ protected
111
+ def with_state_scope(state)
112
+ with_scope :find => {:conditions => ["#{table_name}.#{aasm_column} = ?", state.to_s]} do
113
+ yield if block_given?
114
+ end
115
+ end
116
+ end
117
+
118
+ module InstanceMethods
119
+
120
+ # Returns the current aasm_state of the object. Respects reload and
121
+ # any changes made to the aasm_state field directly
122
+ #
123
+ # Internally just calls <tt>aasm_read_state</tt>
124
+ #
125
+ # foo = Foo.find(1)
126
+ # foo.aasm_current_state # => :pending
127
+ # foo.aasm_state = "opened"
128
+ # foo.aasm_current_state # => :opened
129
+ # foo.close # => calls aasm_write_state_without_persistence
130
+ # foo.aasm_current_state # => :closed
131
+ # foo.reload
132
+ # foo.aasm_current_state # => :pending
133
+ #
134
+ def aasm_current_state
135
+ @current_state = aasm_read_state
136
+ end
137
+
138
+ private
139
+
140
+ # Ensures that if the aasm_state column is nil and the record is new
141
+ # that the initial state gets populated before validation on create
142
+ #
143
+ # foo = Foo.new
144
+ # foo.aasm_state # => nil
145
+ # foo.valid?
146
+ # foo.aasm_state # => "open" (where :open is the initial state)
147
+ #
148
+ #
149
+ # foo = Foo.find(:first)
150
+ # foo.aasm_state # => 1
151
+ # foo.aasm_state = nil
152
+ # foo.valid?
153
+ # foo.aasm_state # => nil
154
+ #
155
+ def aasm_ensure_initial_state
156
+ send("#{self.class.aasm_column}=", self.aasm_current_state.to_s)
157
+ end
158
+
159
+ end
160
+
161
+ module WriteStateWithoutPersistence
162
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
163
+ #
164
+ # foo = Foo.find(1)
165
+ # foo.aasm_current_state # => :opened
166
+ # foo.close
167
+ # foo.aasm_current_state # => :closed
168
+ # Foo.find(1).aasm_current_state # => :opened
169
+ # foo.save
170
+ # foo.aasm_current_state # => :closed
171
+ # Foo.find(1).aasm_current_state # => :closed
172
+ #
173
+ # NOTE: intended to be called from an event
174
+ def aasm_write_state_without_persistence(state)
175
+ write_attribute(self.class.aasm_column, state.to_s)
176
+ end
177
+ end
178
+
179
+ module WriteState
180
+ # Writes <tt>state</tt> to the state column and persists it to the database
181
+ # using update_attribute (which bypasses validation)
182
+ #
183
+ # foo = Foo.find(1)
184
+ # foo.aasm_current_state # => :opened
185
+ # foo.close!
186
+ # foo.aasm_current_state # => :closed
187
+ # Foo.find(1).aasm_current_state # => :closed
188
+ #
189
+ # NOTE: intended to be called from an event
190
+ def aasm_write_state(state)
191
+ update_attribute(self.class.aasm_column, state.to_s)
192
+ end
193
+ end
194
+
195
+ module ReadState
196
+
197
+ # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
198
+ #
199
+ # If it's a new record, and the aasm state column is blank it returns the initial state:
200
+ #
201
+ # class Foo < ActiveRecord::Base
202
+ # include AASM
203
+ # aasm_column :status
204
+ # aasm_state :opened
205
+ # aasm_state :closed
206
+ # end
207
+ #
208
+ # foo = Foo.new
209
+ # foo.current_state # => :opened
210
+ # foo.close
211
+ # foo.current_state # => :closed
212
+ #
213
+ # foo = Foo.find(1)
214
+ # foo.current_state # => :opened
215
+ # foo.aasm_state = nil
216
+ # foo.current_state # => nil
217
+ #
218
+ # NOTE: intended to be called from an event
219
+ #
220
+ # This allows for nil aasm states - be sure to add validation to your model
221
+ def aasm_read_state
222
+ if new_record?
223
+ send(self.class.aasm_column).blank? ? self.class.aasm_initial_state : send(self.class.aasm_column).to_sym
224
+ else
225
+ send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
226
+ end
227
+ end
228
+ end
229
+
230
+ module NamedScopeMethods
231
+ def aasm_state_with_named_scope name, options = {}
232
+ aasm_state_without_named_scope name, options
233
+ self.named_scope name, :conditions => {self.aasm_column => name.to_s} unless self.scopes.include?(name)
234
+ end
235
+ end
236
+ end
237
+ end
238
+ 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
data/lib/state.rb ADDED
@@ -0,0 +1,33 @@
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
+ end
26
+ end
27
+
28
+ def for_select
29
+ [name.to_s.gsub(/_/, ' ').capitalize, name.to_s]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
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 create_state(name, options)
26
+ @states << AASM::SupportingClasses::State.new(name, options) unless @states.include?(name)
27
+ end
28
+ end
29
+ 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
data/lib/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ module AASM
2
+ module VERSION
3
+ STRING = '2.0.2'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eric-aasm
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Scott Barron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: AASM is a continuation of the acts as state machine rails plugin, built for plain Ruby objects.
17
+ email: scott@elitists.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - MIT-LICENSE
25
+ - TODO
26
+ - CHANGELOG
27
+ files:
28
+ - CHANGELOG
29
+ - MIT-LICENSE
30
+ - Rakefile
31
+ - README.rdoc
32
+ - TODO
33
+ - lib/aasm.rb
34
+ - lib/event.rb
35
+ - lib/persistence/active_record_persistence.rb
36
+ - lib/persistence.rb
37
+ - lib/state.rb
38
+ - lib/state_machine.rb
39
+ - lib/state_transition.rb
40
+ - lib/version.rb
41
+ - doc/jamis.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/rubyist/aasm
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --line-numbers
47
+ - --inline-source
48
+ - --main
49
+ - README.rdoc
50
+ - --title
51
+ - AASM
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.2.0
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: State machine mixin for Ruby objects
73
+ test_files: []
74
+