state_machine 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
#
|