apollo 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ Apollo Changelog
2
+ ================
3
+
4
+ 1.1
5
+
6
+ * Added state sets. (7498cf0)
7
+ * Unbranded methods. E.g. `Apollo::apollo` -> `Apollo::state_machine` (f8b42b8)
8
+
9
+ 1.0
10
+ ---
11
+ This is an initial release of the Apollo fork from [Workflow](http://github.com/geekq/workflow). The reason I decided to fork Workflow, and in particular change the name, is because I feel that Workflow is an excellent starting point for my needs; however, my desire for Apollo is somewhat different from the desires of the creators of Workflow. **In no way is my fork (and renaming) intended to be any kind of insult or intellectual rights infringement.** I chose Workflow for the basis of Apollo because I feel it is the **best** state machine gem available. I will most certainly maintain the original MIT-LICENSE as apart of Apollo so long as *any* portion of the code is derived from Workflow (which I suspect will always be the case).
12
+
13
+ * Rebranded modules from "workflow" to "apollo".
14
+ * Using [Jeweler](http://github.com/technicalpickles/jeweler).
15
+ * Removed unnecessary workflow.rb "initializer" file from root of project. (f2ac52a)
16
+ * Extracted Workflow submodules and classes into separated files. (6d4e90f)
17
+ * Changed default "column" name from `workflow_state` to `current_state`. (f562e71)
18
+ * Require reason for `halt` and allow arbitrary exception for `halt!`. (0f903d7)
19
+
20
+ Original Workflow Changelog
21
+ ===========================
22
+
23
+ ### New in the version 0.4.0
24
+
25
+ * completely rewritten the documentation to match my branch. Every
26
+ described feature is backed up by an automated test.
27
+
28
+ ### New in the version 0.3.0
29
+
30
+ Intermixing of transition graph definition (states, transitions)
31
+ on the one side and implementation of the actions on the other side
32
+ for a bigger state machine can introduce clutter.
33
+
34
+ To reduce this clutter it is now possible to use state entry- and
35
+ exit- hooks defined through a naming convention. For example, if there
36
+ is a state :pending, then instead of using a
37
+ block:
38
+
39
+ state :pending do
40
+ on_entry do
41
+ # your implementation here
42
+ end
43
+ end
44
+
45
+ you can hook in by defining method
46
+
47
+ def on_pending_exit(new_state, event, *args)
48
+ # your implementation here
49
+ end
50
+
51
+ anywhere in your class. You can also use a simpler function signature
52
+ like `def on_pending_exit(*args)` if your are not interested in
53
+ arguments. Please note: `def on_pending_exit()` with an empty list
54
+ would not work.
55
+
56
+ If both a function with a name according to naming convention and the
57
+ on_entry/on_exit block are given, then only on_entry/on_exit block is used.
@@ -8,7 +8,38 @@ What is apollo?
8
8
  > poetry, and the arts; and more. Apollo is the son of Zeus and Leto, and has a twin
9
9
  > sister, the chaste huntress Artemis. [Wikipedia: Dionysus (2010/04/23)](http://en.wikipedia.org/wiki/Apollo)
10
10
 
11
- Apollo is an fork of workflow.
11
+ Apollo is an fork of [Workflow](http://github.com/geekq/workflow).
12
+
13
+ Resources
14
+ ---------
15
+
16
+ * [Github Project](http://github.com/tekwiz/apollo)
17
+ * [Rdocs on Rdoc.info](http://rdoc.info/projects/tekwiz/apollo)
18
+ * [Wiki on Github](http://wiki.github.com/tekwiz/apollo/)
19
+ * [Issues Tracker on Github](http://github.com/tekwiz/apollo/issues)
20
+ * [Metrics on Caliper](http://getcaliper.com/caliper/project?repo=http%3A%2F%2Frubygems.org%2Fgems%2Fapollo)
21
+
22
+ Features & Issues
23
+ -----------------
24
+
25
+ This is a brand new project, so if you find bugs or use cases that would helpful to satisfy, post an [Issue](http://github.com/tekwiz/apollo/issues) on Github.
26
+
27
+ This project is intended to be a **very** eclectic mix of helpful tools, so please feel free to send pull requests. That said, make **absolutely sure** your modifications are well tested. The hodge-podge nature of this project may make it prone to issues, so no code will be pulled-in that is not __fully_tested__.
28
+
29
+ Note on Patches/Pull Requests
30
+ -----------------------------
31
+
32
+ * Fork the project.
33
+ * Make your feature addition or bug fix.
34
+ * Add tests for it. This is important so I don't break it in a
35
+ future version unintentionally.
36
+ * Commit, do not mess with rakefile, version, or history.
37
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
38
+ * Send me a pull request. Bonus points for topic branches.
39
+
40
+ ### Documentation Warning
41
+
42
+ Beware that the below documentation has NOT been validated for the changes from Workflow to Apollo.
12
43
 
13
44
  What is workflow?
14
45
  -----------------
@@ -332,106 +363,47 @@ The whole event sequence is as follows:
332
363
  * PERSIST WORKFLOW STATE, i.e. transition
333
364
  * on_entry
334
365
 
335
-
336
366
  Documenting with diagrams
337
367
  -------------------------
338
368
 
339
369
  You can generate a graphical representation of your apollo for
340
370
  documentation purposes. S. Apollo::create_apollo_diagram.
341
371
 
342
-
343
- Earlier versions
344
- ----------------
345
-
346
- The `apollo` library was originally written by Ryan Allen.
347
-
348
- The version 0.3 was almost completely (including ActiveRecord
349
- integration, API for accessing apollo specification,
350
- method_missing free implementation) rewritten by Vladimir Dobriakov
351
- keeping the original apollo DSL spirit.
352
-
353
-
354
- Migration from the original Ryan's library
355
- ------------------------------------------
356
-
357
- Credit: Michael (rockrep)
358
-
359
- Accessing apollo specification
360
-
361
- my_instance.apollo # old
362
- MyClass.apollo_spec # new
363
-
364
- Accessing states, events, meta, e.g.
365
-
366
- my_instance.apollo.states(:some_state).events(:some_event).meta[:some_meta_tag] # old
367
- MyClass.apollo_spec.states[:some_state].events[:some_event].meta[:some_meta_tag] # new
368
-
369
- Causing state transitions
370
-
371
- my_instance.apollo.my_event # old
372
- my_instance.my_event! # new
373
-
374
- when using both a block and a callback method for an event, the block executes prior to the callback
375
-
376
-
377
- Changelog
372
+ Copyright
378
373
  ---------
379
374
 
380
- ### New in the version 0.4.0
381
-
382
- * completely rewritten the documentation to match my branch. Every
383
- described feature is backed up by an automated test.
375
+ Copyright 2010 Travis D. Warlick, Jr.
376
+
377
+ Licensed under the Apache License, Version 2.0 (the "License");
378
+ you may not use this file except in compliance with the License.
379
+ You may obtain a copy of the License at
380
+
381
+ http://www.apache.org/licenses/LICENSE-2.0
382
+
383
+ Unless required by applicable law or agreed to in writing, software
384
+ distributed under the License is distributed on an "AS IS" BASIS,
385
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
386
+ See the License for the specific language governing permissions and
387
+ limitations under the License.
384
388
 
385
- ### New in the version 0.3.0
389
+ The original work from [Workflow](http://github.com/geekq/workflow)
386
390
 
387
- Intermixing of transition graph definition (states, transitions)
388
- on the one side and implementation of the actions on the other side
389
- for a bigger state machine can introduce clutter.
390
-
391
- To reduce this clutter it is now possible to use state entry- and
392
- exit- hooks defined through a naming convention. For example, if there
393
- is a state :pending, then instead of using a
394
- block:
395
-
396
- state :pending do
397
- on_entry do
398
- # your implementation here
399
- end
400
- end
401
-
402
- you can hook in by defining method
403
-
404
- def on_pending_exit(new_state, event, *args)
405
- # your implementation here
406
- end
391
+ Author: Vladimir Dobriakov, http://www.innoq.com/blog/vd, http://blog.geekq.net/
407
392
 
408
- anywhere in your class. You can also use a simpler function signature
409
- like `def on_pending_exit(*args)` if your are not interested in
410
- arguments. Please note: `def on_pending_exit()` with an empty list
411
- would not work.
393
+ Copyright (c) 2008-2009 Vodafone
412
394
 
413
- If both a function with a name according to naming convention and the
414
- on_entry/on_exit block are given, then only on_entry/on_exit block is used.
395
+ Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
415
396
 
397
+ Based on the work of Ryan Allen and Scott Barron
416
398
 
417
- Support
418
- -------
399
+ Licensed under MIT license, see the MIT-LICENSE file.
419
400
 
420
- ### Reporting bugs
421
-
422
- http://github.com/geekq/apollo/issues
423
-
424
-
425
- About
426
- -----
427
-
428
- Author: Vladimir Dobriakov, http://www.innoq.com/blog/vd, http://blog.geekq.net/
429
-
430
- Copyright (c) 2008-2009 Vodafone
431
-
432
- Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
433
-
434
- Based on the work of Ryan Allen and Scott Barron
401
+ Earlier versions
402
+ ----------------
435
403
 
436
- Licensed under MIT license, see the MIT-LICENSE file.
404
+ The `workflow` library was originally written by Ryan Allen.
437
405
 
406
+ The version 0.3 was almost completely (including ActiveRecord
407
+ integration, API for accessing apollo specification,
408
+ method_missing free implementation) rewritten by Vladimir Dobriakov
409
+ keeping the original apollo DSL spirit.
data/Rakefile CHANGED
@@ -41,8 +41,8 @@ Rake::RDocTask.new do |rdoc|
41
41
 
42
42
  rdoc.rdoc_dir = 'rdoc'
43
43
  rdoc.title = "Apollo #{version}"
44
- rdoc.rdoc_files.include('README*', 'MIT-LICENSE', 'LICENSE', 'VERSION')
44
+ rdoc.rdoc_files.include('README*', 'MIT-LICENSE', 'LICENSE', 'VERSION', 'CHANGELOG.md')
45
45
  rdoc.rdoc_files.include('lib/**/*.rb')
46
46
  end
47
47
 
48
- task :clobber => [:clobber_rcov, :clobber_rdoc]
48
+ # task :clobber => [:clobber_rcov, :clobber_rdoc]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -5,21 +5,22 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{apollo}
8
- s.version = "1.0.0"
8
+ s.version = "1.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Travis D. Warlick, Jr."]
12
- s.date = %q{2010-04-23}
12
+ s.date = %q{2010-04-24}
13
13
  s.email = %q{warlickt@operissystems.com}
14
14
  s.extra_rdoc_files = [
15
15
  "LICENSE",
16
- "README.markdown"
16
+ "README.md"
17
17
  ]
18
18
  s.files = [
19
19
  ".gitignore",
20
+ "CHANGELOG.md",
20
21
  "LICENSE",
21
22
  "MIT-LICENSE",
22
- "README.markdown",
23
+ "README.md",
23
24
  "Rakefile",
24
25
  "VERSION",
25
26
  "apollo.gemspec",
@@ -1,3 +1,20 @@
1
+ # Copyright 2010 Travis D. Warlick, Jr.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ # Original work from Workflow:
16
+ # Copyright (c) 2008-2009 Vodafone
17
+ # Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
1
18
  module Apollo
2
19
  autoload :Event, 'apollo/event'
3
20
  autoload :State, 'apollo/state'
@@ -23,18 +40,18 @@ module Apollo
23
40
  class ApolloDefinitionError < Exception; end
24
41
 
25
42
  module ClassMethods
26
- attr_reader :apollo_spec
27
-
28
- def apollo_column(column_name=nil)
43
+ def current_state_column(column_name=nil)
29
44
  if column_name
30
- @apollo_state_column_name = column_name.to_sym
45
+ @current_state_column_name = column_name.to_sym
31
46
  else
32
- @apollo_state_column_name ||= :current_state
47
+ @current_state_column_name ||= :current_state
33
48
  end
34
- @apollo_state_column_name
49
+ @current_state_column_name
35
50
  end
36
51
 
37
- def apollo(&specification)
52
+ def state_machine(&specification)
53
+ return @apollo_spec unless block_given?
54
+
38
55
  @apollo_spec = Specification.new(Hash.new, &specification)
39
56
  @apollo_spec.states.values.each do |state|
40
57
  state_name = state.name
@@ -53,12 +70,20 @@ module Apollo
53
70
  end
54
71
  end
55
72
  end
73
+
74
+ @apollo_spec.state_sets.keys.each do |set_name|
75
+ module_eval do
76
+ define_method "#{set_name}?" do
77
+ current_state.sets.include?(set_name)
78
+ end
79
+ end
80
+ end
56
81
  end
57
82
  end
58
83
 
59
84
  module InstanceMethods
60
85
  def current_state
61
- loaded_state = load_apollo_state
86
+ loaded_state = load_current_state
62
87
  res = spec.states[loaded_state.to_sym] if loaded_state
63
88
  res || spec.initial_state
64
89
  end
@@ -76,7 +101,7 @@ module Apollo
76
101
  raise NoTransitionAllowed.new(
77
102
  "There is no event #{name.to_sym} defined for the #{current_state} state") \
78
103
  if event.nil?
79
- # This three member variables are a relict from the old apollo library
104
+ # This three member variables are a relict from the old workflow library
80
105
  # TODO: refactor some day
81
106
  @halted_because = nil
82
107
  @halted = false
@@ -112,10 +137,10 @@ module Apollo
112
137
  c = self.class
113
138
  # using a simple loop instead of class_inheritable_accessor to avoid
114
139
  # dependency on Rails' ActiveSupport
115
- until c.apollo_spec || !(c.include? Apollo)
140
+ until c.state_machine || !(c.include? Apollo)
116
141
  c = c.superclass
117
142
  end
118
- c.apollo_spec
143
+ c.state_machine
119
144
  end
120
145
 
121
146
  def halt(reason)
@@ -137,7 +162,7 @@ module Apollo
137
162
 
138
163
  def transition(from, to, name, *args)
139
164
  run_on_exit(from, to, name, *args)
140
- persist_apollo_state to.to_s
165
+ persist_current_state to.to_s
141
166
  run_on_entry(to, from, name, *args)
142
167
  end
143
168
 
@@ -173,19 +198,19 @@ module Apollo
173
198
  end
174
199
  end
175
200
 
176
- # load_apollo_state and persist_apollo_state
177
- # can be overriden to handle the persistence of the apollo state.
201
+ # load_current_state and persist_current_state
202
+ # can be overriden to handle the persistence of the current state.
178
203
  #
179
204
  # Default (non ActiveRecord) implementation stores the current state
180
205
  # in a variable.
181
206
  #
182
- # Default ActiveRecord implementation uses a 'apollo_state' database column.
183
- def load_apollo_state
184
- @apollo_state if instance_variable_defined? :@apollo_state
207
+ # Default ActiveRecord implementation uses a 'current_state' database column.
208
+ def load_current_state
209
+ @current_state if instance_variable_defined? :@current_state
185
210
  end
186
211
 
187
- def persist_apollo_state(new_value)
188
- @apollo_state = new_value
212
+ def persist_current_state(new_value)
213
+ @current_state = new_value
189
214
  end
190
215
  end
191
216
 
@@ -200,21 +225,21 @@ module Apollo
200
225
  end
201
226
  end
202
227
 
203
- # Generates a `dot` graph of the apollo.
228
+ # Generates a `dot` graph of the state machine.
204
229
  # Prerequisite: the `dot` binary.
205
230
  # You can use it in your own Rakefile like this:
206
231
  #
207
232
  # namespace :doc do
208
- # desc "Generate a graph of the apollo."
209
- # task :apollo do
210
- # Apollo::create_apollo_diagram(Order.new)
233
+ # desc "Generate a graph of the state machine."
234
+ # task :state_machine do
235
+ # Apollo::create_state_diagram(Order.new)
211
236
  # end
212
237
  # end
213
238
  #
214
239
  # You can influence the placement of nodes by specifying
215
240
  # additional meta information in your states and transition descriptions.
216
241
  # You can assign higher `doc_weight` value to the typical transitions
217
- # in your apollo. All other states and transitions will be arranged
242
+ # in your state machine. All other states and transitions will be arranged
218
243
  # around that main line. See also `weight` in the graphviz documentation.
219
244
  # Example:
220
245
  #
@@ -223,21 +248,21 @@ module Apollo
223
248
  # end
224
249
  #
225
250
  #
226
- # @param klass A class with the Apollo mixin, for which you wish the graphical apollo representation
251
+ # @param klass A class with the Apollo mixin, for which you wish the graphical state machine representation
227
252
  # @param [String] target_dir Directory, where to save the dot and the pdf files
228
253
  # @param [String] graph_options You can change graph orientation, size etc. See graphviz documentation
229
- def self.create_apollo_diagram(klass, target_dir, graph_options='rankdir="LR", size="7,11.6", ratio="fill"')
230
- apollo_name = "#{klass.name.tableize}_apollo"
231
- fname = File.join(target_dir, "generated_#{apollo_name}")
254
+ def self.create_state_diagram(klass, target_dir, graph_options='rankdir="LR", size="7,11.6", ratio="fill"')
255
+ state_machine_name = "#{klass.name.tableize}_state_machine"
256
+ fname = File.join(target_dir, "generated_#{state_machine_name}")
232
257
  File.open("#{fname}.dot", 'w') do |file|
233
258
  file.puts %Q|
234
- digraph #{apollo_name} {
259
+ digraph #{state_machine_name} {
235
260
  graph [#{graph_options}];
236
261
  node [shape=box];
237
262
  edge [len=1];
238
263
  |
239
264
 
240
- klass.apollo_spec.states.each do |state_name, state|
265
+ klass.state_machine.states.each do |state_name, state|
241
266
  file.puts %Q{ #{state.name} [label="#{state.name}"];}
242
267
  state.events.each do |event_name, event|
243
268
  meta_info = event.meta
@@ -1,24 +1,24 @@
1
1
  module Apollo
2
2
  module ActiveRecordInstanceMethods
3
- def load_apollo_state
4
- read_attribute(self.class.apollo_column)
3
+ def load_current_state
4
+ read_attribute(self.class.current_state_column)
5
5
  end
6
6
 
7
- # On transition the new apollo state is immediately saved in the
7
+ # On transition the new current state is immediately saved in the
8
8
  # database.
9
- def persist_apollo_state(new_value)
10
- update_attribute self.class.apollo_column, new_value
9
+ def persist_current_state(new_value)
10
+ update_attribute self.class.current_state_column, new_value
11
11
  end
12
12
 
13
13
  private
14
14
 
15
- # Motivation: even if NULL is stored in the apollo_state database column,
15
+ # Motivation: even if NULL is stored in the current_state database column,
16
16
  # the current_state is correctly recognized in the Ruby code. The problem
17
17
  # arises when you want to SELECT records filtering by the value of initial
18
18
  # state. That's why it is important to save the string with the name of the
19
19
  # initial state in all the new records.
20
20
  def write_initial_state
21
- write_attribute self.class.apollo_column, current_state.to_s
21
+ write_attribute self.class.current_state_column, current_state.to_s
22
22
  end
23
23
  end
24
24
  end
@@ -1,9 +1,10 @@
1
1
  module Apollo
2
2
  class Specification
3
- attr_accessor :states, :initial_state, :meta, :on_transition_proc
3
+ attr_accessor :states, :initial_state, :meta, :on_transition_proc, :state_sets
4
4
 
5
5
  def initialize(meta = {}, &specification)
6
6
  @states = Hash.new
7
+ @state_sets = Hash.new
7
8
  @meta = meta
8
9
  instance_eval(&specification)
9
10
  end
@@ -11,6 +12,8 @@ module Apollo
11
12
  private
12
13
 
13
14
  def state(name, meta = {:meta => {}}, &events_and_etc)
15
+ validate_state_name(name)
16
+
14
17
  # meta[:meta] to keep the API consistent..., gah
15
18
  new_state = State.new(name, meta[:meta])
16
19
  @initial_state = new_state if @states.empty?
@@ -18,6 +21,21 @@ module Apollo
18
21
  @scoped_state = new_state
19
22
  instance_eval(&events_and_etc) if events_and_etc
20
23
  end
24
+
25
+ def state_set(name, *state_names)
26
+ validate_state_set_name(name)
27
+
28
+ set = Set.new
29
+ state_names.each do |state_name|
30
+ if state = @states[state_name]
31
+ set << state
32
+ state.sets << name
33
+ else
34
+ raise ApolloDefinitionError, "Unknown state: #{state}"
35
+ end
36
+ end
37
+ @state_sets[name] = set
38
+ end
21
39
 
22
40
  def event(name, args = {}, &action)
23
41
  target = args[:to] || args[:to]
@@ -39,5 +57,17 @@ module Apollo
39
57
  def on_transition(&proc)
40
58
  @on_transition_proc = proc
41
59
  end
60
+
61
+ def validate_state_name(name)
62
+ if @state_sets[name]
63
+ raise ApolloDefinitionError, "State name conflicts with state set name: #{name}"
64
+ end
65
+ end
66
+
67
+ def validate_state_set_name(name)
68
+ if @states[name]
69
+ raise ApolloDefinitionError, "State set name conflicts with state name: #{name}"
70
+ end
71
+ end
42
72
  end
43
73
  end
@@ -1,9 +1,9 @@
1
1
  module Apollo
2
2
  class State
3
- attr_accessor :name, :events, :meta, :on_entry, :on_exit
3
+ attr_accessor :name, :events, :meta, :on_entry, :on_exit, :sets
4
4
 
5
5
  def initialize(name, meta = {})
6
- @name, @events, @meta = name, Hash.new, meta
6
+ @name, @events, @meta, @sets = name, Hash.new, meta, Set.new
7
7
  end
8
8
 
9
9
  def to_s
@@ -14,4 +14,4 @@ module Apollo
14
14
  name.to_sym
15
15
  end
16
16
  end
17
- end
17
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
+ - 1
7
8
  - 0
8
- - 0
9
- version: 1.0.0
9
+ version: 1.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Travis D. Warlick, Jr.
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-23 00:00:00 -05:00
17
+ date: 2010-04-24 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -26,12 +26,13 @@ extensions: []
26
26
 
27
27
  extra_rdoc_files:
28
28
  - LICENSE
29
- - README.markdown
29
+ - README.md
30
30
  files:
31
31
  - .gitignore
32
+ - CHANGELOG.md
32
33
  - LICENSE
33
34
  - MIT-LICENSE
34
- - README.markdown
35
+ - README.md
35
36
  - Rakefile
36
37
  - VERSION
37
38
  - apollo.gemspec