state_machine 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +19 -0
- data/README.rdoc +24 -4
- data/Rakefile +10 -16
- data/lib/state_machine.rb +1 -1
- data/lib/state_machine/event_collection.rb +4 -6
- data/lib/state_machine/integrations/active_record.rb +100 -23
- data/lib/state_machine/integrations/active_record/observer.rb +5 -1
- data/lib/state_machine/integrations/data_mapper.rb +22 -4
- data/lib/state_machine/integrations/sequel.rb +6 -7
- data/lib/state_machine/machine.rb +29 -10
- data/lib/state_machine/machine_collection.rb +20 -13
- data/lib/state_machine/state.rb +4 -4
- data/{tasks → lib/tasks}/state_machine.rake +0 -0
- data/{tasks → lib/tasks}/state_machine.rb +1 -1
- data/test/functional/state_machine_test.rb +21 -1
- data/test/unit/event_collection_test.rb +9 -0
- data/test/unit/event_test.rb +45 -1
- data/test/unit/guard_test.rb +1 -1
- data/test/unit/integrations/active_record_test.rb +651 -325
- data/test/unit/integrations/data_mapper_test.rb +954 -404
- data/test/unit/integrations/sequel_test.rb +628 -189
- data/test/unit/machine_collection_test.rb +223 -18
- data/test/unit/machine_test.rb +16 -13
- data/test/unit/state_test.rb +14 -15
- metadata +70 -78
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
== master
|
2
2
|
|
3
|
+
== 0.8.1 / 2010-03-14
|
4
|
+
|
5
|
+
* Release gems via rake-gemcutter instead of rubyforge
|
6
|
+
* Move rake tasks to lib/tasks
|
7
|
+
* Dispatch state behavior to the superclass if it's undefined for a particular state [Sandro Turriate and Tim Pope]
|
8
|
+
* Fix state / event names not supporting i18n in ActiveRecord
|
9
|
+
* Fix original ActiveRecord::Observer#update not being used for non-state_machine callbacks [Jeremy Wells]
|
10
|
+
* Add support for ActiveRecord 3.0
|
11
|
+
* Fix without_{name} scopes not quoting columns in ActiveRecord [Jon Evans]
|
12
|
+
* Fix without_{name} scopes not scoping columns to the table in ActiveRecord and Sequel [Jon Evans]
|
13
|
+
* Fix custom state attributes not being marked properly as changed in ActiveRecord
|
14
|
+
* Fix tracked attributes changes in ActiveRecord / DataMapper integrations not working correctly for non-loopbacks [Joe Lind]
|
15
|
+
* Fix plural scope names being incorrect for DataMapper 0.9.4 - 0.9.6
|
16
|
+
* Fix deprecation warnings for ruby-graphviz 0.9.0+
|
17
|
+
* Add support for ActiveRecord 2.0.*
|
18
|
+
* Fix nil states being overwritten when they're explicitly set in ORM integrations
|
19
|
+
* Fix default states not getting set in ORM integrations if the column has a default
|
20
|
+
* Fix event transitions being kept around while running actions/callbacks, sometimes preventing object marshalling
|
21
|
+
|
3
22
|
== 0.8.0 / 2009-08-15
|
4
23
|
|
5
24
|
* Add support for DataMapper 0.10.0
|
data/README.rdoc
CHANGED
@@ -427,6 +427,16 @@ For example,
|
|
427
427
|
|
428
428
|
rake state_machine:draw:rails CLASS=Vehicle
|
429
429
|
|
430
|
+
If you are using this library as a gem, the following must be added to the end
|
431
|
+
of your application's Rakefile in order for the above task to work:
|
432
|
+
|
433
|
+
require 'tasks/state_machine'
|
434
|
+
|
435
|
+
If you are using Rails 3.0+, you must also add the following to your
|
436
|
+
application's Gemfile:
|
437
|
+
|
438
|
+
gem 'ruby-graphviz', :require => 'graphviz'
|
439
|
+
|
430
440
|
==== Merb Integration
|
431
441
|
|
432
442
|
Like Ruby on Rails, there is a special integration Rake task for generating
|
@@ -454,13 +464,23 @@ integrations if the proper dependencies are available):
|
|
454
464
|
|
455
465
|
Target specific versions of integrations like so:
|
456
466
|
|
457
|
-
rake test AR_VERSION=2.
|
467
|
+
rake test AR_VERSION=2.0.0 DM_VERSION=0.9.4 SEQUEL_VERSION=2.8.0
|
468
|
+
|
469
|
+
== Caveats
|
470
|
+
|
471
|
+
The following caveats should be noted when using state_machine:
|
472
|
+
|
473
|
+
* DataMapper: dm-validations 0.9.4 - 0.9.6 causes +after+ callbacks for
|
474
|
+
attribute-based event transitions to fail
|
475
|
+
* Overridden event methods won't get invoked when using attribute-based event
|
476
|
+
transitions
|
458
477
|
|
459
478
|
== Dependencies
|
460
479
|
|
461
|
-
|
462
|
-
|
480
|
+
* Ruby 1.8.6 or later
|
481
|
+
|
482
|
+
If using specific integrations:
|
463
483
|
|
464
|
-
* ActiveRecord[http://rubyonrails.org] integration: 2.
|
484
|
+
* ActiveRecord[http://rubyonrails.org] integration: 2.0.0 or later
|
465
485
|
* DataMapper[http://datamapper.org] integration: 0.9.4 or later
|
466
486
|
* Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later
|
data/Rakefile
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
1
3
|
require 'rake/testtask'
|
2
4
|
require 'rake/rdoctask'
|
3
5
|
require 'rake/gempackagetask'
|
4
|
-
require 'rake/contrib/sshpublisher'
|
5
6
|
|
6
7
|
spec = Gem::Specification.new do |s|
|
7
8
|
s.name = 'state_machine'
|
8
|
-
s.version = '0.8.
|
9
|
+
s.version = '0.8.1'
|
9
10
|
s.platform = Gem::Platform::RUBY
|
10
11
|
s.summary = 'Adds support for creating state machines for attributes on any Ruby class'
|
11
12
|
s.description = s.summary
|
12
13
|
|
13
|
-
s.files = FileList['{examples,lib,
|
14
|
+
s.files = FileList['{examples,lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/*.log']
|
14
15
|
s.require_path = 'lib'
|
15
16
|
s.has_rdoc = true
|
16
17
|
s.test_files = Dir['test/**/*_test.rb']
|
@@ -63,17 +64,17 @@ end
|
|
63
64
|
|
64
65
|
Rake::GemPackageTask.new(spec) do |p|
|
65
66
|
p.gem_spec = spec
|
66
|
-
p.need_tar = true
|
67
|
-
p.need_zip = true
|
68
67
|
end
|
69
68
|
|
70
69
|
desc 'Publish the beta gem.'
|
71
70
|
task :pgem => [:package] do
|
71
|
+
require 'rake/contrib/sshpublisher'
|
72
72
|
Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
|
73
73
|
end
|
74
74
|
|
75
75
|
desc 'Publish the API documentation.'
|
76
76
|
task :pdoc => [:rdoc] do
|
77
|
+
require 'rake/contrib/sshpublisher'
|
77
78
|
Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
|
78
79
|
end
|
79
80
|
|
@@ -82,17 +83,10 @@ task :publish => [:pgem, :pdoc, :release]
|
|
82
83
|
|
83
84
|
desc 'Publish the release files to RubyForge.'
|
84
85
|
task :release => [:gem, :package] do
|
85
|
-
require '
|
86
|
+
require 'rake/gemcutter'
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
%w(gem tgz zip).each do |ext|
|
91
|
-
file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
|
92
|
-
puts "Releasing #{File.basename(file)}..."
|
93
|
-
|
94
|
-
ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
|
95
|
-
end
|
88
|
+
Rake::Gemcutter::Tasks.new(spec)
|
89
|
+
Rake::Task['gem:push'].invoke
|
96
90
|
end
|
97
91
|
|
98
|
-
|
92
|
+
load 'lib/tasks/state_machine.rake'
|
data/lib/state_machine.rb
CHANGED
@@ -385,4 +385,4 @@ Class.class_eval do
|
|
385
385
|
end
|
386
386
|
|
387
387
|
# Register rake tasks for supported libraries
|
388
|
-
Merb::Plugins.add_rakefiles("#{File.dirname(__FILE__)}
|
388
|
+
Merb::Plugins.add_rakefiles("#{File.dirname(__FILE__)}/tasks/state_machine") if defined?(Merb::Plugins)
|
@@ -102,19 +102,17 @@ module StateMachine
|
|
102
102
|
def attribute_transition_for(object, invalidate = false)
|
103
103
|
return unless machine.action
|
104
104
|
|
105
|
-
result =
|
106
|
-
|
107
|
-
if event_name = machine.read(object, :event)
|
105
|
+
result = machine.read(object, :event_transition) || if event_name = machine.read(object, :event)
|
108
106
|
if event = self[event_name.to_sym, :name]
|
109
|
-
|
107
|
+
event.transition_for(object) || begin
|
110
108
|
# No valid transition: invalidate
|
111
109
|
machine.invalidate(object, :event, :invalid_event, [[:state, machine.states.match!(object).name || 'nil']]) if invalidate
|
112
|
-
|
110
|
+
false
|
113
111
|
end
|
114
112
|
else
|
115
113
|
# Event is unknown: invalidate
|
116
114
|
machine.invalidate(object, :event, :invalid) if invalidate
|
117
|
-
|
115
|
+
false
|
118
116
|
end
|
119
117
|
end
|
120
118
|
|
@@ -259,6 +259,50 @@ module StateMachine
|
|
259
259
|
# Audit.log(record, transition)
|
260
260
|
# end
|
261
261
|
# end
|
262
|
+
#
|
263
|
+
# == Internationalization
|
264
|
+
#
|
265
|
+
# In Rails 2.2+, any error message that is generated from performing invalid
|
266
|
+
# transitions can be localized. The following default translations are used:
|
267
|
+
#
|
268
|
+
# en:
|
269
|
+
# activerecord:
|
270
|
+
# errors:
|
271
|
+
# messages:
|
272
|
+
# invalid: "is invalid"
|
273
|
+
# invalid_event: "cannot transition when {{state}}"
|
274
|
+
# invalid_transition: "cannot transition via {{event}}"
|
275
|
+
#
|
276
|
+
# You can override these for a specific model like so:
|
277
|
+
#
|
278
|
+
# en:
|
279
|
+
# activerecord:
|
280
|
+
# errors:
|
281
|
+
# models:
|
282
|
+
# user:
|
283
|
+
# invalid: "is not valid"
|
284
|
+
#
|
285
|
+
# In addition to the above, you can also provide translations for the
|
286
|
+
# various states / events in each state machine. Using the Vehicle example,
|
287
|
+
# state translations will be looked for using the following keys:
|
288
|
+
# * <tt>activerecord.state_machines.vehicle.state.states.parked</tt>
|
289
|
+
# * <tt>activerecord.state_machines.state.states.parked
|
290
|
+
# * <tt>activerecord.state_machines.states.parked</tt>
|
291
|
+
#
|
292
|
+
# Event translations will be looked for using the following keys:
|
293
|
+
# * <tt>activerecord.state_machines.vehicle.state.events.ignite</tt>
|
294
|
+
# * <tt>activerecord.state_machines.state.events.ignite
|
295
|
+
# * <tt>activerecord.state_machines.events.ignite</tt>
|
296
|
+
#
|
297
|
+
# An example translation configuration might look like so:
|
298
|
+
#
|
299
|
+
# es:
|
300
|
+
# activerecord:
|
301
|
+
# state_machines:
|
302
|
+
# states:
|
303
|
+
# parked: 'estacionado'
|
304
|
+
# events:
|
305
|
+
# park: 'estacionarse'
|
262
306
|
module ActiveRecord
|
263
307
|
# The default options to use for state machines using this integration
|
264
308
|
class << self; attr_reader :defaults; end
|
@@ -285,7 +329,9 @@ module StateMachine
|
|
285
329
|
# state value actually changed
|
286
330
|
def write(object, attribute, value)
|
287
331
|
result = super
|
288
|
-
object.
|
332
|
+
if attribute == :state && object.respond_to?("#{self.attribute}_will_change!") && !object.send("#{self.attribute}_changed?")
|
333
|
+
object.send("#{self.attribute}_will_change!")
|
334
|
+
end
|
289
335
|
result
|
290
336
|
end
|
291
337
|
|
@@ -294,10 +340,26 @@ module StateMachine
|
|
294
340
|
attribute = self.attribute(attribute)
|
295
341
|
|
296
342
|
if Object.const_defined?(:I18n)
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
343
|
+
klasses =
|
344
|
+
if ::ActiveRecord::VERSION::MAJOR >= 3
|
345
|
+
object.class.lookup_ancestors
|
346
|
+
elsif ::ActiveRecord::VERSION::MINOR == 3 && ::ActiveRecord::VERSION::TINY >= 2
|
347
|
+
object.class.self_and_descendants_from_active_record
|
348
|
+
else
|
349
|
+
object.class.self_and_descendents_from_active_record
|
350
|
+
end
|
351
|
+
|
352
|
+
options = values.inject({}) do |options, (key, value)|
|
353
|
+
# Generate all possible translation keys
|
354
|
+
group = key.to_s.pluralize
|
355
|
+
translations = klasses.map {|klass| :"#{klass.model_name.underscore}.#{name}.#{group}.#{value}"}
|
356
|
+
translations.concat([:"#{name}.#{group}.#{value}", :"#{group}.#{value}", value.to_s])
|
357
|
+
|
358
|
+
options[key] = I18n.translate(translations.shift, :default => translations, :scope => [:activerecord, :state_machines])
|
359
|
+
options
|
360
|
+
end
|
361
|
+
|
362
|
+
object.errors.add(attribute, message, options.merge(:default => @messages[message]))
|
301
363
|
else
|
302
364
|
object.errors.add(attribute, generate_message(message, values))
|
303
365
|
end
|
@@ -330,13 +392,21 @@ module StateMachine
|
|
330
392
|
|
331
393
|
# Hooks in to attribute initialization to set the states *prior*
|
332
394
|
# to the attributes being set
|
333
|
-
def attributes=(*args)
|
395
|
+
def attributes=(new_attributes, *args)
|
334
396
|
if new_record? && !@initialized_state_machines
|
335
397
|
@initialized_state_machines = true
|
336
398
|
|
337
|
-
|
399
|
+
if new_attributes
|
400
|
+
attributes = new_attributes.dup
|
401
|
+
attributes.stringify_keys!
|
402
|
+
ignore = remove_attributes_protected_from_mass_assignment(attributes).keys
|
403
|
+
else
|
404
|
+
ignore = []
|
405
|
+
end
|
406
|
+
|
407
|
+
initialize_state_machines(:dynamic => false, :ignore => ignore)
|
338
408
|
super
|
339
|
-
initialize_state_machines(:dynamic => true)
|
409
|
+
initialize_state_machines(:dynamic => true, :ignore => ignore)
|
340
410
|
else
|
341
411
|
super
|
342
412
|
end
|
@@ -387,15 +457,16 @@ module StateMachine
|
|
387
457
|
# Creates a scope for finding records *with* a particular state or
|
388
458
|
# states for the attribute
|
389
459
|
def create_with_scope(name)
|
390
|
-
attribute = self.attribute
|
391
460
|
define_scope(name, lambda {|values| {:conditions => {attribute => values}}})
|
392
461
|
end
|
393
462
|
|
394
463
|
# Creates a scope for finding records *without* a particular state or
|
395
464
|
# states for the attribute
|
396
465
|
def create_without_scope(name)
|
397
|
-
|
398
|
-
|
466
|
+
define_scope(name, lambda {|values|
|
467
|
+
connection = owner_class.connection
|
468
|
+
{:conditions => ["#{connection.quote_table_name(owner_class.table_name)}.#{connection.quote_column_name(attribute)} NOT IN (?)", values]}
|
469
|
+
})
|
399
470
|
end
|
400
471
|
|
401
472
|
# Runs a new database transaction, rolling back any changes by raising
|
@@ -424,20 +495,26 @@ module StateMachine
|
|
424
495
|
# state names can be translated to their associated values and so that
|
425
496
|
# inheritance is respected properly.
|
426
497
|
def define_scope(name, scope)
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
498
|
+
if ::ActiveRecord::VERSION::MAJOR <= 2
|
499
|
+
if owner_class.respond_to?(:named_scope)
|
500
|
+
name = name.to_sym
|
501
|
+
machine_name = self.name
|
502
|
+
|
503
|
+
# Create the scope and then override it with state translation
|
504
|
+
owner_class.named_scope(name)
|
505
|
+
owner_class.scopes[name] = lambda do |klass, *states|
|
506
|
+
machine_states = klass.state_machine(machine_name).states
|
507
|
+
values = states.flatten.map {|state| machine_states.fetch(state).value}
|
508
|
+
|
509
|
+
::ActiveRecord::NamedScope::Scope.new(klass, scope.call(values))
|
510
|
+
end
|
511
|
+
end
|
435
512
|
|
436
|
-
|
513
|
+
# Prevent the Machine class from wrapping the scope
|
514
|
+
false
|
515
|
+
else
|
516
|
+
lambda {|klass, values| klass.where(scope.call(values)[:conditions])}
|
437
517
|
end
|
438
|
-
|
439
|
-
# Prevent the Machine class from wrapping the scope
|
440
|
-
false
|
441
518
|
end
|
442
519
|
|
443
520
|
# Notifies observers on the given object that a callback occurred
|
@@ -29,7 +29,11 @@ module StateMachine
|
|
29
29
|
# Allows additional arguments other than the object to be passed to the
|
30
30
|
# observed methods
|
31
31
|
def update_with_multiple_args(observed_method, object, *args) #:nodoc:
|
32
|
-
|
32
|
+
if args.any?
|
33
|
+
send(observed_method, object, *args) if respond_to?(observed_method)
|
34
|
+
else
|
35
|
+
update_without_multiple_args(observed_method, object)
|
36
|
+
end
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
@@ -257,9 +257,10 @@ module StateMachine
|
|
257
257
|
result = super
|
258
258
|
if attribute == :state && owner_class.properties.detect {|property| property.name == self.attribute}
|
259
259
|
if ::DataMapper::VERSION =~ /^(0\.\d\.)/ # Match anything < 0.10
|
260
|
-
object.original_values[self.attribute] = "#{value}-ignored"
|
260
|
+
object.original_values[self.attribute] = "#{value}-ignored" if object.original_values[self.attribute] == value
|
261
261
|
else
|
262
|
-
|
262
|
+
property = owner_class.properties[self.attribute]
|
263
|
+
object.original_attributes[property] = "#{value}-ignored" unless object.original_attributes.include?(property)
|
263
264
|
end
|
264
265
|
end
|
265
266
|
result
|
@@ -281,6 +282,25 @@ module StateMachine
|
|
281
282
|
@supports_validations ||= ::DataMapper.const_defined?('Validate')
|
282
283
|
end
|
283
284
|
|
285
|
+
# Pluralizes the name using the built-in inflector
|
286
|
+
def pluralize(word)
|
287
|
+
Extlib::Inflection.pluralize(word.to_s)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Defines an initialization hook into the owner class for setting the
|
291
|
+
# initial state of the machine *before* any attributes are set on the
|
292
|
+
# object
|
293
|
+
def define_state_initializer
|
294
|
+
@instance_helper_module.class_eval <<-end_eval, __FILE__, __LINE__
|
295
|
+
def initialize(attributes = {}, *args)
|
296
|
+
ignore = attributes ? attributes.keys : []
|
297
|
+
initialize_state_machines(:dynamic => false, :ignore => ignore)
|
298
|
+
super
|
299
|
+
initialize_state_machines(:dynamic => true, :ignore => ignore)
|
300
|
+
end
|
301
|
+
end_eval
|
302
|
+
end
|
303
|
+
|
284
304
|
# Skips defining reader/writer methods since this is done automatically
|
285
305
|
def define_state_accessor
|
286
306
|
owner_class.property(attribute, String) unless owner_class.properties.detect {|property| property.name == attribute}
|
@@ -308,14 +328,12 @@ module StateMachine
|
|
308
328
|
# Creates a scope for finding records *with* a particular state or
|
309
329
|
# states for the attribute
|
310
330
|
def create_with_scope(name)
|
311
|
-
attribute = self.attribute
|
312
331
|
lambda {|resource, values| resource.all(attribute => values)}
|
313
332
|
end
|
314
333
|
|
315
334
|
# Creates a scope for finding records *without* a particular state or
|
316
335
|
# states for the attribute
|
317
336
|
def create_without_scope(name)
|
318
|
-
attribute = self.attribute
|
319
337
|
lambda {|resource, values| resource.all(attribute.to_sym.not => values)}
|
320
338
|
end
|
321
339
|
|
@@ -253,13 +253,14 @@ module StateMachine
|
|
253
253
|
@instance_helper_module.class_eval <<-end_eval, __FILE__, __LINE__
|
254
254
|
# Hooks in to attribute initialization to set the states *prior*
|
255
255
|
# to the attributes being set
|
256
|
-
def set(*args)
|
256
|
+
def set(hash, *args)
|
257
257
|
if new? && !@initialized_state_machines
|
258
258
|
@initialized_state_machines = true
|
259
259
|
|
260
|
-
|
260
|
+
ignore = setter_methods(nil, nil).map {|setter| setter.chop.to_sym} & (hash ? hash.keys.map {|attribute| attribute.to_sym} : [])
|
261
|
+
initialize_state_machines(:dynamic => false, :ignore => ignore)
|
261
262
|
result = super
|
262
|
-
initialize_state_machines(:dynamic => true)
|
263
|
+
initialize_state_machines(:dynamic => true, :ignore => ignore)
|
263
264
|
result
|
264
265
|
else
|
265
266
|
super
|
@@ -291,15 +292,13 @@ module StateMachine
|
|
291
292
|
# Creates a scope for finding records *with* a particular state or
|
292
293
|
# states for the attribute
|
293
294
|
def create_with_scope(name)
|
294
|
-
|
295
|
-
lambda {|model, values| model.filter(attribute.to_sym => values)}
|
295
|
+
lambda {|model, values| model.filter(:"#{owner_class.table_name}__#{attribute}" => values)}
|
296
296
|
end
|
297
297
|
|
298
298
|
# Creates a scope for finding records *without* a particular state or
|
299
299
|
# states for the attribute
|
300
300
|
def create_without_scope(name)
|
301
|
-
|
302
|
-
lambda {|model, values| model.filter(~{attribute.to_sym => values})}
|
301
|
+
lambda {|model, values| model.filter(~{:"#{owner_class.table_name}__#{attribute}" => values})}
|
303
302
|
end
|
304
303
|
|
305
304
|
# Runs a new database transaction, rolling back any changes if the
|
@@ -1258,21 +1258,16 @@ module StateMachine
|
|
1258
1258
|
:path => '.',
|
1259
1259
|
:format => 'png',
|
1260
1260
|
:font => 'Arial',
|
1261
|
-
:orientation => 'portrait'
|
1262
|
-
:output => true
|
1261
|
+
:orientation => 'portrait'
|
1263
1262
|
}.merge(options)
|
1264
|
-
assert_valid_keys(options, :name, :path, :format, :font, :orientation
|
1263
|
+
assert_valid_keys(options, :name, :path, :format, :font, :orientation)
|
1265
1264
|
|
1266
1265
|
begin
|
1267
1266
|
# Load the graphviz library
|
1268
1267
|
require 'rubygems'
|
1269
1268
|
require 'graphviz'
|
1270
1269
|
|
1271
|
-
graph = GraphViz.new('G',
|
1272
|
-
:output => options[:format],
|
1273
|
-
:file => File.join(options[:path], "#{options[:name]}.#{options[:format]}"),
|
1274
|
-
:rankdir => options[:orientation] == 'landscape' ? 'LR' : 'TB'
|
1275
|
-
)
|
1270
|
+
graph = GraphViz.new('G', :rankdir => options[:orientation] == 'landscape' ? 'LR' : 'TB')
|
1276
1271
|
|
1277
1272
|
# Add nodes
|
1278
1273
|
states.by_priority.each do |state|
|
@@ -1287,7 +1282,20 @@ module StateMachine
|
|
1287
1282
|
end
|
1288
1283
|
|
1289
1284
|
# Generate the graph
|
1290
|
-
|
1285
|
+
graphvizVersion = Constants::RGV_VERSION.split('.')
|
1286
|
+
|
1287
|
+
if graphvizVersion[0] == '0' && (graphvizVersion[1] < '9' || graphvizVersion[1] == '9' && graphvizVersion[2] == '0')
|
1288
|
+
outputOptions = {
|
1289
|
+
:output => options[:format],
|
1290
|
+
:file => File.join(options[:path], "#{options[:name]}.#{options[:format]}")
|
1291
|
+
}
|
1292
|
+
else
|
1293
|
+
outputOptions = {
|
1294
|
+
options[:format] => File.join(options[:path], "#{options[:name]}.#{options[:format]}")
|
1295
|
+
}
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
graph.output(outputOptions)
|
1291
1299
|
graph
|
1292
1300
|
rescue LoadError
|
1293
1301
|
$stderr.puts 'Cannot draw the machine. `gem install ruby-graphviz` and try again.'
|
@@ -1408,7 +1416,7 @@ module StateMachine
|
|
1408
1416
|
# automatically determined by either calling +pluralize+ on the attribute
|
1409
1417
|
# name or adding an "s" to the end of the name.
|
1410
1418
|
def define_scopes(custom_plural = nil)
|
1411
|
-
plural = custom_plural || (name
|
1419
|
+
plural = custom_plural || pluralize(name)
|
1412
1420
|
|
1413
1421
|
[name, plural].uniq.each do |name|
|
1414
1422
|
[:with, :without].each do |kind|
|
@@ -1426,6 +1434,17 @@ module StateMachine
|
|
1426
1434
|
end
|
1427
1435
|
end
|
1428
1436
|
|
1437
|
+
# Pluralizes the given word using #pluralize (if available) or simply
|
1438
|
+
# adding an "s" to the end of the word
|
1439
|
+
def pluralize(word)
|
1440
|
+
word = word.to_s
|
1441
|
+
if word.respond_to?(:pluralize)
|
1442
|
+
word.pluralize
|
1443
|
+
else
|
1444
|
+
"#{name}s"
|
1445
|
+
end
|
1446
|
+
end
|
1447
|
+
|
1429
1448
|
# Creates a scope for finding objects *with* a particular value or values
|
1430
1449
|
# for the attribute.
|
1431
1450
|
#
|