rubyist-aasm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,21 @@
1
+ * Specs and bug fixes for the ActiveRecordPersistence, keeping persistence columns in sync
2
+ Allowing for nil values in states for active record
3
+ Only set state to default state before_validation_on_create
4
+ New rake task to uninstall, build and reinstall the gem (useful for development)
5
+ Changed scott's email address to protect it from spambots when publishing rdocs
6
+ New non-(!) methods that allow for firing events without persisting [Jeff Dean]
7
+
8
+ * Added aasm_states_for_select that will return a select friendly collection of states.
9
+
10
+ * Add some event callbacks, #aasm_event_fired(from, to), and #aasm_event_failed(event)
11
+ Based on transition logging suggestion [Artem Vasiliev] and timestamp column suggestion [Mike Ferrier]
12
+
13
+ * Add #aasm_events_for_state and #aasm_events_for_current_state [Joao Paulo Lins]
14
+
15
+ * Ensure that a state is written for a new record even if aasm_current_state or
16
+ {state}= are never called.
17
+
18
+ * Fix AR persistence so new records have their state set. [Joao Paulo Lins]
19
+
20
+ * Make #event! methods return a boolean [Joel Chippindale]
21
+
data/MIT-LICENSE ADDED
@@ -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.
data/README.rdoc ADDED
@@ -0,0 +1,67 @@
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
+ Until the gem release is made your best bet is to build the gem yourself and install it.
27
+
28
+ % rake gem
29
+ % sudo gem install pkg/aasm-3.0.0.gem
30
+
31
+
32
+ == Simple Example
33
+
34
+ Here's a quick example highlighting some of the features.
35
+
36
+ class Conversation
37
+ include AASM
38
+
39
+ aasm_initial_state :new
40
+
41
+ aasm_state :new
42
+ aasm_state :read
43
+ aasm_state :closed
44
+
45
+
46
+ aasm_event :view do
47
+ transitions :to => :read, :from => [:new]
48
+ end
49
+
50
+ aasm_event :close do
51
+ transitions :to => :closed, :from => [:read, :new]
52
+ end
53
+ end
54
+
55
+ = Other Stuff
56
+
57
+ Author:: Scott Barron <scott at elitists dot net>
58
+ License:: Copyright 2006, 2007, 2008 by Scott Barron.
59
+ Released under an MIT-style license. See the LICENSE file
60
+ included in the distribution.
61
+
62
+ == Warranty
63
+
64
+ This software is provided "as is" and without any express or
65
+ implied warranties, including, without limitation, the implied
66
+ warranties of merchantibility and fitness for a particular
67
+ purpose.
data/Rakefile ADDED
@@ -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)
data/doc/jamis.rb ADDED
@@ -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
+
data/lib/aasm.rb ADDED
@@ -0,0 +1,128 @@
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, &block)
37
+ unless aasm_events.has_key?(name)
38
+ aasm_events[name] = AASM::SupportingClasses::Event.new(name, &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
+ true
50
+ else
51
+ if self.respond_to?(:aasm_event_failed)
52
+ self.aasm_event_failed(name)
53
+ end
54
+
55
+ false
56
+ end
57
+ end
58
+
59
+ define_method("#{name.to_s}") do
60
+ new_state = self.class.aasm_events[name].fire(self)
61
+ unless new_state.nil?
62
+ if self.respond_to?(:aasm_event_fired)
63
+ self.aasm_event_fired(self.aasm_current_state, new_state)
64
+ end
65
+
66
+ self.aasm_current_state = new_state
67
+ true
68
+ else
69
+ if self.respond_to?(:aasm_event_failed)
70
+ self.aasm_event_failed(name)
71
+ end
72
+
73
+ false
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ def aasm_states
80
+ @aasm_states ||= []
81
+ end
82
+
83
+ def aasm_events
84
+ @aasm_events ||= {}
85
+ end
86
+
87
+ def aasm_states_for_select
88
+ aasm_states.collect { |state| [state.to_s.gsub(/_/, ' ').capitalize, state] }
89
+ end
90
+
91
+ end
92
+
93
+ # Instance methods
94
+ def aasm_current_state
95
+ return @aasm_current_state if @aasm_current_state
96
+
97
+ if self.respond_to?(:aasm_read_state) || self.private_methods.include?('aasm_read_state')
98
+ @aasm_current_state = aasm_read_state
99
+ end
100
+ return @aasm_current_state if @aasm_current_state
101
+ self.class.aasm_initial_state
102
+ end
103
+
104
+ def aasm_events_for_current_state
105
+ aasm_events_for_state(aasm_current_state)
106
+ end
107
+
108
+ def aasm_events_for_state(state)
109
+ events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
110
+ events.map {|event| event.name}
111
+ end
112
+
113
+ private
114
+ def aasm_current_state_with_persistence=(state)
115
+ if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
116
+ aasm_write_state(state)
117
+ end
118
+ self.aasm_current_state = state
119
+ end
120
+
121
+ def aasm_current_state=(state)
122
+ if self.respond_to?(:aasm_write_state_without_persistence) || self.private_methods.include?('aasm_write_state_without_persistence')
123
+ aasm_write_state_without_persistence(state)
124
+ end
125
+ @aasm_current_state = state
126
+ end
127
+
128
+ end
data/lib/event.rb ADDED
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), 'state_transition')
2
+
3
+ module AASM
4
+ module SupportingClasses
5
+ class Event
6
+ attr_reader :name
7
+
8
+ def initialize(name, &block)
9
+ @name = name
10
+ @transitions = []
11
+ instance_eval(&block) if block
12
+ end
13
+
14
+ def fire(obj)
15
+ transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
16
+ raise AASM::InvalidTransition if transitions.size == 0
17
+
18
+ next_state = nil
19
+ transitions.each do |transition|
20
+ if transition.perform(obj)
21
+ next_state = transition.to
22
+ break
23
+ end
24
+ end
25
+ next_state
26
+ end
27
+
28
+ def transitions_from_state?(state)
29
+ @transitions.any? { |t| t.from == state }
30
+ end
31
+
32
+ private
33
+ def transitions(trans_opts)
34
+ Array(trans_opts[:from]).each do |s|
35
+ @transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
36
+ end
37
+ end
38
+ end
39
+ end
40
+ 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,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,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
data/lib/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ module AASM
2
+ module VERSION
3
+ STRING = '3.0.0'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubyist-aasm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Scott Barron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-04-29 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
+