spicycode-aasm 2.0.0

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,23 @@
1
+ * Incremented version number
2
+ * Cleaned up aasm_states_for_select to return the value as a string
3
+ * Specs and bug fixes for the ActiveRecordPersistence, keeping persistence columns in sync
4
+ Allowing for nil values in states for active record
5
+ Only set state to default state before_validation_on_create
6
+ New rake task to uninstall, build and reinstall the gem (useful for development)
7
+ Changed scott's email address to protect it from spambots when publishing rdocs
8
+ New non-(!) methods that allow for firing events without persisting [Jeff Dean]
9
+
10
+ * Added aasm_states_for_select that will return a select friendly collection of states.
11
+
12
+ * Add some event callbacks, #aasm_event_fired(from, to), and #aasm_event_failed(event)
13
+ Based on transition logging suggestion [Artem Vasiliev] and timestamp column suggestion [Mike Ferrier]
14
+
15
+ * Add #aasm_events_for_state and #aasm_events_for_current_state [Joao Paulo Lins]
16
+
17
+ * Ensure that a state is written for a new record even if aasm_current_state or
18
+ {state}= are never called.
19
+
20
+ * Fix AR persistence so new records have their state set. [Joao Paulo Lins]
21
+
22
+ * Make #event! methods return a boolean [Joel Chippindale]
23
+
@@ -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,72 @@
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
+ * Feature
10
+
11
+ * Feature
12
+
13
+
14
+ == Download
15
+
16
+ The latest AASM can currently be pulled from the git repository on github.
17
+
18
+ * http://github.com/rubyist/aasm/tree/master
19
+
20
+ A release and a gem are forthcoming.
21
+
22
+
23
+
24
+ == Installation
25
+
26
+ === From GitHub hosted gems
27
+
28
+ % sudo gem sources -a http://gems.github.com # (you only need to do this once)
29
+ % sudo gem install rubyist-aasm
30
+
31
+ === Building your own gems
32
+
33
+ % rake gem
34
+ % sudo gem install pkg/aasm-0.0.2.gem
35
+
36
+
37
+ == Simple Example
38
+
39
+ Here's a quick example highlighting some of the features.
40
+
41
+ class Conversation
42
+ include AASM
43
+
44
+ aasm_initial_state :new
45
+
46
+ aasm_state :new
47
+ aasm_state :read
48
+ aasm_state :closed
49
+
50
+
51
+ aasm_event :view do
52
+ transitions :to => :read, :from => [:new]
53
+ end
54
+
55
+ aasm_event :close do
56
+ transitions :to => :closed, :from => [:read, :new]
57
+ end
58
+ end
59
+
60
+ = Other Stuff
61
+
62
+ Author:: Scott Barron <scott at elitists dot net>
63
+ License:: Copyright 2006, 2007, 2008 by Scott Barron.
64
+ Released under an MIT-style license. See the LICENSE file
65
+ included in the distribution.
66
+
67
+ == Warranty
68
+
69
+ This software is provided "as is" and without any express or
70
+ implied warranties, including, without limitation, the implied
71
+ warranties of merchantibility and fitness for a particular
72
+ purpose.
@@ -0,0 +1,94 @@
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 -rversion -e "print AASM::VERSION::STRING"` =~ /([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', '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
+ s.files = PKG_FILES.to_a
51
+ s.require_path = 'lib'
52
+ s.has_rdoc = true
53
+ s.extra_rdoc_files = rd.rdoc_files.reject {|fn| fn =~ /\.rb$/}.to_a
54
+ s.rdoc_options = rd.options
55
+
56
+ s.author = 'Scott Barron'
57
+ s.email = 'scott@elitists.net'
58
+ s.homepage = 'http://rubyi.st/aasm'
59
+ end
60
+
61
+ package_task = Rake::GemPackageTask.new(spec) do |pkg|
62
+ pkg.need_zip = true
63
+ pkg.need_tar = true
64
+ end
65
+ end
66
+
67
+ if !defined?(Spec)
68
+ puts "spec and cruise targets require RSpec"
69
+ else
70
+ desc "Run all examples with RCov"
71
+ Spec::Rake::SpecTask.new('cruise') do |t|
72
+ t.spec_files = FileList['spec/**/*.rb']
73
+ t.rcov = true
74
+ t.rcov_opts = ['--exclude', 'spec']
75
+ end
76
+
77
+ desc "Run all examples"
78
+ Spec::Rake::SpecTask.new('spec') do |t|
79
+ t.spec_files = FileList['spec/**/*.rb']
80
+ t.rcov = false
81
+ t.spec_opts = ['-cfs']
82
+ end
83
+ end
84
+
85
+ if !defined?(Gem)
86
+ puts "Package target requires RubyGEMs"
87
+ else
88
+ desc "sudo gem uninstall aasm && rake gem && sudo gem install pkg/aasm-3.0.0.gem"
89
+ task :reinstall do
90
+ puts `sudo gem uninstall aasm && rake gem && sudo gem install pkg/aasm-3.0.0.gem`
91
+ end
92
+ end
93
+
94
+ task :default => [:spec]
data/TODO ADDED
@@ -0,0 +1,11 @@
1
+ Before Next Release:
2
+
3
+ * Add state actions (enter, exit, after)
4
+ * Add #aasm_next_state_for_event
5
+ * Add #aasm_next_states_for_event
6
+
7
+
8
+ Cool ideas from users:
9
+
10
+ * Support multiple state machines on one object (Chris Nelson)
11
+ * http://justbarebones.blogspot.com/2007/11/actsasstatemachine-enhancements.html (Chetan Patil)
@@ -0,0 +1,591 @@
1
+ module RDoc
2
+ module Page
3
+
4
+ FONTS = "\"Bitstream Vera Sans\", Verdana, Arial, Helvetica, sans-serif"
5
+
6
+ STYLE = <<CSS
7
+ a {
8
+ color: #00F;
9
+ text-decoration: none;
10
+ }
11
+
12
+ a:hover {
13
+ color: #77F;
14
+ text-decoration: underline;
15
+ }
16
+
17
+ body, td, p {
18
+ font-family: %fonts%;
19
+ background: #FFF;
20
+ color: #000;
21
+ margin: 0px;
22
+ font-size: small;
23
+ }
24
+
25
+ #content {
26
+ margin: 2em;
27
+ }
28
+
29
+ #description p {
30
+ margin-bottom: 0.5em;
31
+ }
32
+
33
+ .sectiontitle {
34
+ margin-top: 1em;
35
+ margin-bottom: 1em;
36
+ padding: 0.5em;
37
+ padding-left: 2em;
38
+ background: #005;
39
+ color: #FFF;
40
+ font-weight: bold;
41
+ border: 1px dotted black;
42
+ }
43
+
44
+ .attr-rw {
45
+ padding-left: 1em;
46
+ padding-right: 1em;
47
+ text-align: center;
48
+ color: #055;
49
+ }
50
+
51
+ .attr-name {
52
+ font-weight: bold;
53
+ }
54
+
55
+ .attr-desc {
56
+ }
57
+
58
+ .attr-value {
59
+ font-family: monospace;
60
+ }
61
+
62
+ .file-title-prefix {
63
+ font-size: large;
64
+ }
65
+
66
+ .file-title {
67
+ font-size: large;
68
+ font-weight: bold;
69
+ background: #005;
70
+ color: #FFF;
71
+ }
72
+
73
+ .banner {
74
+ background: #005;
75
+ color: #FFF;
76
+ border: 1px solid black;
77
+ padding: 1em;
78
+ }
79
+
80
+ .banner td {
81
+ background: transparent;
82
+ color: #FFF;
83
+ }
84
+
85
+ h1 a, h2 a, .sectiontitle a, .banner a {
86
+ color: #FF0;
87
+ }
88
+
89
+ h1 a:hover, h2 a:hover, .sectiontitle a:hover, .banner a:hover {
90
+ color: #FF7;
91
+ }
92
+
93
+ .dyn-source {
94
+ display: none;
95
+ background: #FFE;
96
+ color: #000;
97
+ border: 1px dotted black;
98
+ margin: 0.5em 2em 0.5em 2em;
99
+ padding: 0.5em;
100
+ }
101
+
102
+ .dyn-source .cmt {
103
+ color: #00F;
104
+ font-style: italic;
105
+ }
106
+
107
+ .dyn-source .kw {
108
+ color: #070;
109
+ font-weight: bold;
110
+ }
111
+
112
+ .method {
113
+ margin-left: 1em;
114
+ margin-right: 1em;
115
+ margin-bottom: 1em;
116
+ }
117
+
118
+ .description pre {
119
+ padding: 0.5em;
120
+ border: 1px dotted black;
121
+ background: #FFE;
122
+ }
123
+
124
+ .method .title {
125
+ font-family: monospace;
126
+ font-size: large;
127
+ border-bottom: 1px dashed black;
128
+ margin-bottom: 0.3em;
129
+ padding-bottom: 0.1em;
130
+ }
131
+
132
+ .method .description, .method .sourcecode {
133
+ margin-left: 1em;
134
+ }
135
+
136
+ .description p, .sourcecode p {
137
+ margin-bottom: 0.5em;
138
+ }
139
+
140
+ .method .sourcecode p.source-link {
141
+ text-indent: 0em;
142
+ margin-top: 0.5em;
143
+ }
144
+
145
+ .method .aka {
146
+ margin-top: 0.3em;
147
+ margin-left: 1em;
148
+ font-style: italic;
149
+ text-indent: 2em;
150
+ }
151
+
152
+ h1 {
153
+ padding: 1em;
154
+ border: 1px solid black;
155
+ font-size: x-large;
156
+ font-weight: bold;
157
+ color: #FFF;
158
+ background: #007;
159
+ }
160
+
161
+ h2 {
162
+ padding: 0.5em 1em 0.5em 1em;
163
+ border: 1px solid black;
164
+ font-size: large;
165
+ font-weight: bold;
166
+ color: #FFF;
167
+ background: #009;
168
+ }
169
+
170
+ h3, h4, h5, h6 {
171
+ padding: 0.2em 1em 0.2em 1em;
172
+ border: 1px dashed black;
173
+ color: #000;
174
+ background: #AAF;
175
+ }
176
+
177
+ .sourcecode > pre {
178
+ padding: 0.5em;
179
+ border: 1px dotted black;
180
+ background: #FFE;
181
+ }
182
+
183
+ CSS
184
+
185
+ XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?>
186
+ <!DOCTYPE html
187
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
188
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
189
+ }
190
+
191
+ HEADER = XHTML_PREAMBLE + <<ENDHEADER
192
+ <html>
193
+ <head>
194
+ <title>%title%</title>
195
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
196
+ <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
197
+
198
+ <script language="JavaScript" type="text/javascript">
199
+ // <![CDATA[
200
+
201
+ function toggleSource( id )
202
+ {
203
+ var elem
204
+ var link
205
+
206
+ if( document.getElementById )
207
+ {
208
+ elem = document.getElementById( id )
209
+ link = document.getElementById( "l_" + id )
210
+ }
211
+ else if ( document.all )
212
+ {
213
+ elem = eval( "document.all." + id )
214
+ link = eval( "document.all.l_" + id )
215
+ }
216
+ else
217
+ return false;
218
+
219
+ if( elem.style.display == "block" )
220
+ {
221
+ elem.style.display = "none"
222
+ link.innerHTML = "show source"
223
+ }
224
+ else
225
+ {
226
+ elem.style.display = "block"
227
+ link.innerHTML = "hide source"
228
+ }
229
+ }
230
+
231
+ function openCode( url )
232
+ {
233
+ window.open( url, "SOURCE_CODE", "width=400,height=400,scrollbars=yes" )
234
+ }
235
+ // ]]>
236
+ </script>
237
+ </head>
238
+
239
+ <body>
240
+ ENDHEADER
241
+
242
+ FILE_PAGE = <<HTML
243
+ <table border='0' cellpadding='0' cellspacing='0' width="100%" class='banner'>
244
+ <tr><td>
245
+ <table width="100%" border='0' cellpadding='0' cellspacing='0'><tr>
246
+ <td class="file-title" colspan="2"><span class="file-title-prefix">File</span><br />%short_name%</td>
247
+ <td align="right">
248
+ <table border='0' cellspacing="0" cellpadding="2">
249
+ <tr>
250
+ <td>Path:</td>
251
+ <td>%full_path%
252
+ IF:cvsurl
253
+ &nbsp;(<a href="%cvsurl%">CVS</a>)
254
+ ENDIF:cvsurl
255
+ </td>
256
+ </tr>
257
+ <tr>
258
+ <td>Modified:</td>
259
+ <td>%dtm_modified%</td>
260
+ </tr>
261
+ </table>
262
+ </td></tr>
263
+ </table>
264
+ </td></tr>
265
+ </table><br>
266
+ HTML
267
+
268
+ ###################################################################
269
+
270
+ CLASS_PAGE = <<HTML
271
+ <table width="100%" border='0' cellpadding='0' cellspacing='0' class='banner'><tr>
272
+ <td class="file-title"><span class="file-title-prefix">%classmod%</span><br />%full_name%</td>
273
+ <td align="right">
274
+ <table cellspacing=0 cellpadding=2>
275
+ <tr valign="top">
276
+ <td>In:</td>
277
+ <td>
278
+ START:infiles
279
+ HREF:full_path_url:full_path:
280
+ IF:cvsurl
281
+ &nbsp;(<a href="%cvsurl%">CVS</a>)
282
+ ENDIF:cvsurl
283
+ END:infiles
284
+ </td>
285
+ </tr>
286
+ IF:parent
287
+ <tr>
288
+ <td>Parent:</td>
289
+ <td>
290
+ IF:par_url
291
+ <a href="%par_url%">
292
+ ENDIF:par_url
293
+ %parent%
294
+ IF:par_url
295
+ </a>
296
+ ENDIF:par_url
297
+ </td>
298
+ </tr>
299
+ ENDIF:parent
300
+ </table>
301
+ </td>
302
+ </tr>
303
+ </table>
304
+ HTML
305
+
306
+ ###################################################################
307
+
308
+ METHOD_LIST = <<HTML
309
+ <div id="content">
310
+ IF:diagram
311
+ <table cellpadding='0' cellspacing='0' border='0' width="100%"><tr><td align="center">
312
+ %diagram%
313
+ </td></tr></table>
314
+ ENDIF:diagram
315
+
316
+ IF:description
317
+ <div class="description">%description%</div>
318
+ ENDIF:description
319
+
320
+ IF:requires
321
+ <div class="sectiontitle">Required Files</div>
322
+ <ul>
323
+ START:requires
324
+ <li>HREF:aref:name:</li>
325
+ END:requires
326
+ </ul>
327
+ ENDIF:requires
328
+
329
+ IF:toc
330
+ <div class="sectiontitle">Contents</div>
331
+ <ul>
332
+ START:toc
333
+ <li><a href="#%href%">%secname%</a></li>
334
+ END:toc
335
+ </ul>
336
+ ENDIF:toc
337
+
338
+ IF:methods
339
+ <div class="sectiontitle">Methods</div>
340
+ <ul>
341
+ START:methods
342
+ <li>HREF:aref:name:</li>
343
+ END:methods
344
+ </ul>
345
+ ENDIF:methods
346
+
347
+ IF:includes
348
+ <div class="sectiontitle">Included Modules</div>
349
+ <ul>
350
+ START:includes
351
+ <li>HREF:aref:name:</li>
352
+ END:includes
353
+ </ul>
354
+ ENDIF:includes
355
+
356
+ START:sections
357
+ IF:sectitle
358
+ <div class="sectiontitle"><a nem="%secsequence%">%sectitle%</a></div>
359
+ IF:seccomment
360
+ <div class="description">
361
+ %seccomment%
362
+ </div>
363
+ ENDIF:seccomment
364
+ ENDIF:sectitle
365
+
366
+ IF:classlist
367
+ <div class="sectiontitle">Classes and Modules</div>
368
+ %classlist%
369
+ ENDIF:classlist
370
+
371
+ IF:constants
372
+ <div class="sectiontitle">Constants</div>
373
+ <table border='0' cellpadding='5'>
374
+ START:constants
375
+ <tr valign='top'>
376
+ <td class="attr-name">%name%</td>
377
+ <td>=</td>
378
+ <td class="attr-value">%value%</td>
379
+ </tr>
380
+ IF:desc
381
+ <tr valign='top'>
382
+ <td>&nbsp;</td>
383
+ <td colspan="2" class="attr-desc">%desc%</td>
384
+ </tr>
385
+ ENDIF:desc
386
+ END:constants
387
+ </table>
388
+ ENDIF:constants
389
+
390
+ IF:attributes
391
+ <div class="sectiontitle">Attributes</div>
392
+ <table border='0' cellpadding='5'>
393
+ START:attributes
394
+ <tr valign='top'>
395
+ <td class='attr-rw'>
396
+ IF:rw
397
+ [%rw%]
398
+ ENDIF:rw
399
+ </td>
400
+ <td class='attr-name'>%name%</td>
401
+ <td class='attr-desc'>%a_desc%</td>
402
+ </tr>
403
+ END:attributes
404
+ </table>
405
+ ENDIF:attributes
406
+
407
+ IF:method_list
408
+ START:method_list
409
+ IF:methods
410
+ <div class="sectiontitle">%type% %category% methods</div>
411
+ START:methods
412
+ <div class="method">
413
+ <div class="title">
414
+ IF:callseq
415
+ <a name="%aref%"></a><b>%callseq%</b>
416
+ ENDIF:callseq
417
+ IFNOT:callseq
418
+ <a name="%aref%"></a><b>%name%</b>%params%
419
+ ENDIF:callseq
420
+ IF:codeurl
421
+ [ <a href="javascript:openCode('%codeurl%')">source</a> ]
422
+ ENDIF:codeurl
423
+ </div>
424
+ IF:m_desc
425
+ <div class="description">
426
+ %m_desc%
427
+ </div>
428
+ ENDIF:m_desc
429
+ IF:aka
430
+ <div class="aka">
431
+ This method is also aliased as
432
+ START:aka
433
+ <a href="%aref%">%name%</a>
434
+ END:aka
435
+ </div>
436
+ ENDIF:aka
437
+ IF:sourcecode
438
+ <div class="sourcecode">
439
+ <p class="source-link">[ <a href="javascript:toggleSource('%aref%_source')" id="l_%aref%_source">show source</a> ]</p>
440
+ <div id="%aref%_source" class="dyn-source">
441
+ <pre>
442
+ %sourcecode%
443
+ </pre>
444
+ </div>
445
+ </div>
446
+ ENDIF:sourcecode
447
+ </div>
448
+ END:methods
449
+ ENDIF:methods
450
+ END:method_list
451
+ ENDIF:method_list
452
+ END:sections
453
+ </div>
454
+ HTML
455
+
456
+ FOOTER = <<ENDFOOTER
457
+ </body>
458
+ </html>
459
+ ENDFOOTER
460
+
461
+ BODY = HEADER + <<ENDBODY
462
+ !INCLUDE! <!-- banner header -->
463
+
464
+ <div id="bodyContent">
465
+ #{METHOD_LIST}
466
+ </div>
467
+
468
+ #{FOOTER}
469
+ ENDBODY
470
+
471
+ ########################## Source code ##########################
472
+
473
+ SRC_PAGE = XHTML_PREAMBLE + <<HTML
474
+ <html>
475
+ <head><title>%title%</title>
476
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%">
477
+ <style>
478
+ .ruby-comment { color: green; font-style: italic }
479
+ .ruby-constant { color: #4433aa; font-weight: bold; }
480
+ .ruby-identifier { color: #222222; }
481
+ .ruby-ivar { color: #2233dd; }
482
+ .ruby-keyword { color: #3333FF; font-weight: bold }
483
+ .ruby-node { color: #777777; }
484
+ .ruby-operator { color: #111111; }
485
+ .ruby-regexp { color: #662222; }
486
+ .ruby-value { color: #662222; font-style: italic }
487
+ .kw { color: #3333FF; font-weight: bold }
488
+ .cmt { color: green; font-style: italic }
489
+ .str { color: #662222; font-style: italic }
490
+ .re { color: #662222; }
491
+ </style>
492
+ </head>
493
+ <body bgcolor="white">
494
+ <pre>%code%</pre>
495
+ </body>
496
+ </html>
497
+ HTML
498
+
499
+ ########################## Index ################################
500
+
501
+ FR_INDEX_BODY = <<HTML
502
+ !INCLUDE!
503
+ HTML
504
+
505
+ FILE_INDEX = XHTML_PREAMBLE + <<HTML
506
+ <html>
507
+ <head>
508
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%">
509
+ <style>
510
+ <!--
511
+ body {
512
+ background-color: #EEE;
513
+ font-family: #{FONTS};
514
+ color: #000;
515
+ margin: 0px;
516
+ }
517
+ .banner {
518
+ background: #005;
519
+ color: #FFF;
520
+ padding: 0.2em;
521
+ font-size: small;
522
+ font-weight: bold;
523
+ text-align: center;
524
+ }
525
+ .entries {
526
+ margin: 0.25em 1em 0 1em;
527
+ font-size: x-small;
528
+ }
529
+ a {
530
+ color: #00F;
531
+ text-decoration: none;
532
+ white-space: nowrap;
533
+ }
534
+ a:hover {
535
+ color: #77F;
536
+ text-decoration: underline;
537
+ }
538
+ -->
539
+ </style>
540
+ <base target="docwin">
541
+ </head>
542
+ <body>
543
+ <div class="banner">%list_title%</div>
544
+ <div class="entries">
545
+ START:entries
546
+ <a href="%href%">%name%</a><br>
547
+ END:entries
548
+ </div>
549
+ </body></html>
550
+ HTML
551
+
552
+ CLASS_INDEX = FILE_INDEX
553
+ METHOD_INDEX = FILE_INDEX
554
+
555
+ INDEX = XHTML_PREAMBLE + <<HTML
556
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
557
+ <head>
558
+ <title>%title%</title>
559
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%">
560
+ </head>
561
+
562
+ <frameset cols="20%,*">
563
+ <frameset rows="15%,35%,50%">
564
+ <frame src="fr_file_index.html" title="Files" name="Files" />
565
+ <frame src="fr_class_index.html" name="Classes" />
566
+ <frame src="fr_method_index.html" name="Methods" />
567
+ </frameset>
568
+ IF:inline_source
569
+ <frame src="%initial_page%" name="docwin">
570
+ ENDIF:inline_source
571
+ IFNOT:inline_source
572
+ <frameset rows="80%,20%">
573
+ <frame src="%initial_page%" name="docwin">
574
+ <frame src="blank.html" name="source">
575
+ </frameset>
576
+ ENDIF:inline_source
577
+ <noframes>
578
+ <body bgcolor="white">
579
+ Click <a href="html/index.html">here</a> for a non-frames
580
+ version of this page.
581
+ </body>
582
+ </noframes>
583
+ </frameset>
584
+
585
+ </html>
586
+ HTML
587
+
588
+ end
589
+ end
590
+
591
+
@@ -0,0 +1,131 @@
1
+ require File.join(File.dirname(__FILE__), 'event')
2
+ require File.join(File.dirname(__FILE__), 'state')
3
+ require File.join(File.dirname(__FILE__), 'persistence')
4
+
5
+ module AASM
6
+ class InvalidTransition < Exception
7
+ end
8
+
9
+ def self.included(base) #:nodoc:
10
+ base.extend AASM::ClassMethods
11
+ AASM::Persistence.set_persistence(base)
12
+ end
13
+
14
+ module ClassMethods
15
+ def aasm_initial_state(set_state=nil)
16
+ if set_state
17
+ aasm_initial_state = set_state
18
+ else
19
+ @aasm_initial_state
20
+ end
21
+ end
22
+
23
+ def aasm_initial_state=(state)
24
+ @aasm_initial_state = state
25
+ end
26
+
27
+ def aasm_state(name, options={})
28
+ aasm_states << name unless aasm_states.include?(name)
29
+ self.aasm_initial_state = name unless self.aasm_initial_state
30
+
31
+ define_method("#{name.to_s}?") do
32
+ aasm_current_state == name
33
+ end
34
+ end
35
+
36
+ def aasm_event(name, options = {}, &block)
37
+ unless aasm_events.has_key?(name)
38
+ aasm_events[name] = AASM::SupportingClasses::Event.new(name, options, &block)
39
+ end
40
+
41
+ define_method("#{name.to_s}!") do
42
+ new_state = self.class.aasm_events[name].fire(self)
43
+ unless new_state.nil?
44
+ if self.respond_to?(:aasm_event_fired)
45
+ self.aasm_event_fired(self.aasm_current_state, new_state)
46
+ end
47
+
48
+ self.aasm_current_state_with_persistence = new_state
49
+
50
+ self.send(self.class.aasm_events[name].success) if self.class.aasm_events[name].success
51
+
52
+ true
53
+ else
54
+ if self.respond_to?(:aasm_event_failed)
55
+ self.aasm_event_failed(name)
56
+ end
57
+
58
+ false
59
+ end
60
+ end
61
+
62
+ define_method("#{name.to_s}") do
63
+ new_state = self.class.aasm_events[name].fire(self)
64
+ unless new_state.nil?
65
+ if self.respond_to?(:aasm_event_fired)
66
+ self.aasm_event_fired(self.aasm_current_state, new_state)
67
+ end
68
+
69
+ self.aasm_current_state = new_state
70
+ true
71
+ else
72
+ if self.respond_to?(:aasm_event_failed)
73
+ self.aasm_event_failed(name)
74
+ end
75
+
76
+ false
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ def aasm_states
83
+ @aasm_states ||= []
84
+ end
85
+
86
+ def aasm_events
87
+ @aasm_events ||= {}
88
+ end
89
+
90
+ def aasm_states_for_select
91
+ aasm_states.collect { |state| [state.to_s.gsub(/_/, ' ').capitalize, state.to_s] }
92
+ end
93
+
94
+ end
95
+
96
+ # Instance methods
97
+ def aasm_current_state
98
+ return @aasm_current_state if @aasm_current_state
99
+
100
+ if self.respond_to?(:aasm_read_state) || self.private_methods.include?('aasm_read_state')
101
+ @aasm_current_state = aasm_read_state
102
+ end
103
+ return @aasm_current_state if @aasm_current_state
104
+ self.class.aasm_initial_state
105
+ end
106
+
107
+ def aasm_events_for_current_state
108
+ aasm_events_for_state(aasm_current_state)
109
+ end
110
+
111
+ def aasm_events_for_state(state)
112
+ events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
113
+ events.map {|event| event.name}
114
+ end
115
+
116
+ private
117
+ def aasm_current_state_with_persistence=(state)
118
+ if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
119
+ aasm_write_state(state)
120
+ end
121
+ self.aasm_current_state = state
122
+ end
123
+
124
+ def aasm_current_state=(state)
125
+ if self.respond_to?(:aasm_write_state_without_persistence) || self.private_methods.include?('aasm_write_state_without_persistence')
126
+ aasm_write_state_without_persistence(state)
127
+ end
128
+ @aasm_current_state = state
129
+ end
130
+
131
+ end
@@ -0,0 +1,41 @@
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)
16
+ transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
17
+ raise AASM::InvalidTransition if transitions.size == 0
18
+
19
+ next_state = nil
20
+ transitions.each do |transition|
21
+ if transition.perform(obj)
22
+ next_state = transition.to
23
+ break
24
+ end
25
+ end
26
+ next_state
27
+ end
28
+
29
+ def transitions_from_state?(state)
30
+ @transitions.any? { |t| t.from == state }
31
+ end
32
+
33
+ private
34
+ def transitions(trans_opts)
35
+ Array(trans_opts[:from]).each do |s|
36
+ @transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ 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,192 @@
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
+ base.before_validation_on_create :aasm_ensure_initial_state
41
+ end
42
+
43
+ module ClassMethods
44
+ # Maps to the aasm_column in the database. Deafults to "aasm_state". You can write:
45
+ #
46
+ # create_table :foos do |t|
47
+ # t.string :name
48
+ # t.string :aasm_state
49
+ # end
50
+ #
51
+ # class Foo < ActiveRecord::Base
52
+ # include AASM
53
+ # end
54
+ #
55
+ # OR:
56
+ #
57
+ # create_table :foos do |t|
58
+ # t.string :name
59
+ # t.string :status
60
+ # end
61
+ #
62
+ # class Foo < ActiveRecord::Base
63
+ # include AASM
64
+ # aasm_column :status
65
+ # end
66
+ #
67
+ # This method is both a getter and a setter
68
+ def aasm_column(column_name=nil)
69
+ if column_name
70
+ @aasm_column = column_name.to_sym
71
+ else
72
+ @aasm_column ||= :aasm_state
73
+ end
74
+ @aasm_column
75
+ end
76
+
77
+ end
78
+
79
+ module InstanceMethods
80
+
81
+ # Returns the current aasm_state of the object. Respects reload and
82
+ # any changes made to the aasm_state field directly
83
+ #
84
+ # Internally just calls <tt>aasm_read_state</tt>
85
+ #
86
+ # foo = Foo.find(1)
87
+ # foo.aasm_current_state # => :pending
88
+ # foo.aasm_state = "opened"
89
+ # foo.aasm_current_state # => :opened
90
+ # foo.close # => calls aasm_write_state_without_persistence
91
+ # foo.aasm_current_state # => :closed
92
+ # foo.reload
93
+ # foo.aasm_current_state # => :pending
94
+ #
95
+ def aasm_current_state
96
+ @current_state = aasm_read_state
97
+ end
98
+
99
+ private
100
+
101
+ # Ensures that if the aasm_state column is nil and the record is new
102
+ # that the initial state gets populated before validation on create
103
+ #
104
+ # foo = Foo.new
105
+ # foo.aasm_state # => nil
106
+ # foo.valid?
107
+ # foo.aasm_state # => "open" (where :open is the initial state)
108
+ #
109
+ #
110
+ # foo = Foo.find(:first)
111
+ # foo.aasm_state # => 1
112
+ # foo.aasm_state = nil
113
+ # foo.valid?
114
+ # foo.aasm_state # => nil
115
+ #
116
+ def aasm_ensure_initial_state
117
+ send("#{self.class.aasm_column}=", self.aasm_current_state.to_s)
118
+ end
119
+
120
+ end
121
+
122
+ module WriteStateWithoutPersistence
123
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
124
+ #
125
+ # foo = Foo.find(1)
126
+ # foo.aasm_current_state # => :opened
127
+ # foo.close
128
+ # foo.aasm_current_state # => :closed
129
+ # Foo.find(1).aasm_current_state # => :opened
130
+ # foo.save
131
+ # foo.aasm_current_state # => :closed
132
+ # Foo.find(1).aasm_current_state # => :closed
133
+ #
134
+ # NOTE: intended to be called from an event
135
+ def aasm_write_state_without_persistence(state)
136
+ write_attribute(self.class.aasm_column, state.to_s)
137
+ end
138
+ end
139
+
140
+ module WriteState
141
+ # Writes <tt>state</tt> to the state column and persists it to the database
142
+ # using update_attribute (which bypasses validation)
143
+ #
144
+ # foo = Foo.find(1)
145
+ # foo.aasm_current_state # => :opened
146
+ # foo.close!
147
+ # foo.aasm_current_state # => :closed
148
+ # Foo.find(1).aasm_current_state # => :closed
149
+ #
150
+ # NOTE: intended to be called from an event
151
+ def aasm_write_state(state)
152
+ update_attribute(self.class.aasm_column, state.to_s)
153
+ end
154
+ end
155
+
156
+ module ReadState
157
+
158
+ # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
159
+ #
160
+ # If it's a new record, and the aasm state column is blank it returns the initial state:
161
+ #
162
+ # class Foo < ActiveRecord::Base
163
+ # include AASM
164
+ # aasm_column :status
165
+ # aasm_state :opened
166
+ # aasm_state :closed
167
+ # end
168
+ #
169
+ # foo = Foo.new
170
+ # foo.current_state # => :opened
171
+ # foo.close
172
+ # foo.current_state # => :closed
173
+ #
174
+ # foo = Foo.find(1)
175
+ # foo.current_state # => :opened
176
+ # foo.aasm_state = nil
177
+ # foo.current_state # => nil
178
+ #
179
+ # NOTE: intended to be called from an event
180
+ #
181
+ # This allows for nil aasm states - be sure to add validation to your model
182
+ def aasm_read_state
183
+ if new_record?
184
+ send(self.class.aasm_column).blank? ? self.class.aasm_initial_state : send(self.class.aasm_column).to_sym
185
+ else
186
+ send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,29 @@
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 entering(record)
11
+ enteract = @options[:enter]
12
+ record.send(:run_transition_action, enteract) if enteract
13
+ end
14
+
15
+ def entered(record)
16
+ afteractions = @options[:after]
17
+ return unless afteractions
18
+ Array(afteractions).each do |afteract|
19
+ record.send(:run_transition_action, afteract)
20
+ end
21
+ end
22
+
23
+ def exited(record)
24
+ exitact = @options[:exit]
25
+ record.send(:run_transition_action, exitact) if exitact
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ module AASM
2
+ module SupportingClasses
3
+ class StateTransition
4
+ attr_reader :from, :to, :opts
5
+
6
+ def initialize(opts)
7
+ @from, @to, @guard = opts[:from], opts[:to], opts[:guard]
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 ==(obj)
23
+ @from == obj.from && @to == obj.to
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module AASM
2
+ module VERSION
3
+ STRING = '0.0.2'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spicycode-aasm
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Scott Barron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-14 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_transition.rb
39
+ - lib/version.rb
40
+ - doc/jamis.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/rubyist/aasm
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --line-numbers
46
+ - --inline-source
47
+ - --main
48
+ - README.rdoc
49
+ - --title
50
+ - AASM
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.0.1
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: State machine mixin for Ruby objects
72
+ test_files: []
73
+