Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Allow states / events to be drawn with their human name instead of th…

…eir internal name. Closes #154
  • Loading branch information...
commit 8c7eb7414be374f62dc1435c5c9dcfb5b49e9875 1 parent fb0259e
@obrie obrie authored
View
2  CHANGELOG.md
@@ -1,5 +1,7 @@
# master
+* Allow states / events to be drawn with their human name instead of their internal name
+
## 1.1.2 / 2012-01-20
* Fix states not being initialized properly on ActiveRecord 3.2+
View
6 README.md
@@ -943,6 +943,12 @@ To generate multiple state machine graphs:
rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car
```
+To use human state / event names:
+
+```bash
+rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle HUMAN_NAMES=true
+```
+
**Note** that this will generate a different file for every state machine defined
in the class. The generated files will use an output filename of the format
`#{class_name}_#{machine_name}.#{format}`.
View
8 lib/state_machine/event.rb
@@ -194,9 +194,13 @@ def reset
# configured.
#
# A collection of the generated edges will be returned.
- def draw(graph)
+ #
+ # Configuration options:
+ # * <tt>:human_name</tt> - Whether to use the event's human name for the
+ # node's label that gets drawn on the graph
+ def draw(graph, options = {})
valid_states = machine.states.by_priority.map {|state| state.name}
- branches.collect {|branch| branch.draw(graph, name, valid_states)}.flatten
+ branches.collect {|branch| branch.draw(graph, options[:human_name] ? human_name : name, valid_states)}.flatten
end
# Generates a nicely formatted description of this event's contents.
View
12 lib/state_machine/machine.rb
@@ -1863,16 +1863,18 @@ def within_transaction(object)
# Default is "Arial".
# * <tt>:orientation</tt> - The direction of the graph ("portrait" or
# "landscape"). Default is "portrait".
- # * <tt>:output</tt> - Whether to generate the output of the graph
+ # * <tt>:human_names</tt> - Whether to use human state / event names for
+ # node labels on the graph instead of the internal name. Default is false.
def draw(options = {})
options = {
:name => "#{owner_class.name}_#{name}",
:path => '.',
:format => 'png',
:font => 'Arial',
- :orientation => 'portrait'
+ :orientation => 'portrait',
+ :human_names => false
}.merge(options)
- assert_valid_keys(options, :name, :path, :format, :font, :orientation)
+ assert_valid_keys(options, :name, :path, :format, :font, :orientation, :human_names)
begin
# Load the graphviz library
@@ -1884,13 +1886,13 @@ def draw(options = {})
# Add nodes
states.by_priority.each do |state|
- node = state.draw(graph)
+ node = state.draw(graph, :human_name => options[:human_names])
node.fontname = options[:font]
end
# Add edges
events.each do |event|
- edges = event.draw(graph)
+ edges = event.draw(graph, :human_name => options[:human_names])
edges.each {|edge| edge.fontname = options[:font]}
end
View
17 lib/state_machine/state.rb
@@ -125,8 +125,13 @@ def human_name(klass = @machine.owner_class)
# State.new(machine, :parked, :value => nil).description # => "parked (nil)"
# State.new(machine, :parked, :value => 1).description # => "parked (1)"
# State.new(machine, :parked, :value => lambda {Time.now}).description # => "parked (*)
- def description
- description = name ? name.to_s : name.inspect
+ #
+ # Configuration options:
+ # * <tt>:human_name</tt> - Whether to use this state's human name in the
+ # description or just the internal name
+ def description(options = {})
+ label = options[:human_name] ? human_name : name
+ description = label ? label.to_s : label.inspect
description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s
description
end
@@ -227,9 +232,13 @@ def call(object, method, method_missing = nil, *args, &block)
# state, then "doublecircle", otherwise "ellipse".
#
# The actual node generated on the graph will be returned.
- def draw(graph)
+ #
+ # Configuration options:
+ # * <tt>:human_name</tt> - Whether to use the state's human name for the
+ # node's label that gets drawn on the graph
+ def draw(graph, options = {})
node = graph.add_node(name ? name.to_s : 'nil',
- :label => description,
+ :label => description(options),
:width => '1',
:height => '1',
:shape => final? ? 'doublecircle' : 'ellipse'
View
3  lib/tasks/state_machine.rb
@@ -1,5 +1,5 @@
namespace :state_machine do
- desc 'Draws state machines using GraphViz (options: CLASS=User,Vehicle; FILE=user.rb,vehicle.rb [not required in Rails / Merb]; FONT=Arial; FORMAT=png; ORIENTATION=portrait'
+ desc 'Draws state machines using GraphViz (options: CLASS=User,Vehicle; FILE=user.rb,vehicle.rb [not required in Rails / Merb]; FONT=Arial; FORMAT=png; ORIENTATION=portrait; HUMAN_NAMES=true'
task :draw do
# Build drawing options
options = {}
@@ -8,6 +8,7 @@
options[:format] = ENV['FORMAT'] if ENV['FORMAT']
options[:font] = ENV['FONT'] if ENV['FONT']
options[:orientation] = ENV['ORIENTATION'] if ENV['ORIENTATION']
+ options[:human_names] = ENV['HUMAN_NAMES'] == 'true' if ENV['HUMAN_NAMES']
if defined?(Rails)
puts "Files are automatically loaded in Rails; ignoring FILE option" if options.delete(:file)
View
21 test/unit/event_test.rb
@@ -1021,6 +1021,27 @@ def test_should_use_event_name_for_edge_label
assert_equal 'park', @edges.first['label'].to_s.gsub('"', '')
end
end
+
+ class EventDrawingWithHumanNameTest < Test::Unit::TestCase
+ def setup
+ states = [:parked, :idling]
+
+ @machine = StateMachine::Machine.new(Class.new, :initial => :parked)
+ @machine.other_states(*states)
+
+ graph = GraphViz.new('G')
+ states.each {|state| graph.add_node(state.to_s)}
+
+ @machine.events << @event = StateMachine::Event.new(@machine , :park, :human_name => 'Park')
+ @event.transition :parked => :idling
+
+ @edges = @event.draw(graph, :human_name => true)
+ end
+
+ def test_should_use_event_human_name_for_edge_label
+ assert_equal 'Park', @edges.first['label'].to_s.gsub('"', '')
+ end
+ end
rescue LoadError
$stderr.puts 'Skipping GraphViz StateMachine::Event tests. `gem install ruby-graphviz` >= v0.9.0 and try again.'
end unless ENV['TRAVIS']
View
15 test/unit/machine_test.rb
@@ -3230,6 +3230,21 @@ def test_should_allow_orientation_to_be_portrait
assert_equal 'TB', graph['rankdir'].to_s.gsub('"', '')
end
+ if Constants::RGV_VERSION != '0.9.0'
+ def test_should_allow_human_names_to_be_displayed
+ @machine.event :ignite, :human_name => 'Ignite'
+ @machine.state :parked, :human_name => 'Parked'
+ @machine.state :idling, :human_name => 'Idling'
+ graph = @machine.draw(:human_names => true)
+
+ parked_node = graph.get_node('parked')
+ assert_equal 'Parked', parked_node['label'].to_s.gsub('"', '')
+
+ idling_node = graph.get_node('idling')
+ assert_equal 'Idling', idling_node['label'].to_s.gsub('"', '')
+ end
+ end
+
def teardown
FileUtils.rm Dir["{.,#{File.dirname(__FILE__)}}/*.{png,jpg}"]
end
View
41 test/unit/state_test.rb
@@ -115,6 +115,10 @@ def test_should_not_redefine_nil_predicate
def test_should_have_a_description
assert_equal 'nil', @state.description
end
+
+ def test_should_have_a_description_using_human_name
+ assert_equal 'nil', @state.description(:human_name => true)
+ end
end
class StateWithNameTest < Test::Unit::TestCase
@@ -149,6 +153,11 @@ def test_should_not_include_value_in_description
assert_equal 'parked', @state.description
end
+ def test_should_allow_using_human_name_in_description
+ @state.human_name = 'Parked'
+ assert_equal 'Parked', @state.description(:human_name => true)
+ end
+
def test_should_define_predicate
assert @klass.new.respond_to?(:parked?)
end
@@ -177,6 +186,11 @@ def test_should_have_a_description
assert_equal 'parked (nil)', @state.description
end
+ def test_should_have_a_description_with_human_name
+ @state.human_name = 'Parked'
+ assert_equal 'Parked (nil)', @state.description(:human_name => true)
+ end
+
def test_should_define_predicate
object = @klass.new
assert object.respond_to?(:parked?)
@@ -198,6 +212,11 @@ def test_should_not_include_value_in_description
assert_equal 'parked', @state.description
end
+ def test_should_allow_human_name_in_description
+ @state.human_name = 'Parked'
+ assert_equal 'Parked', @state.description(:human_name => true)
+ end
+
def test_should_match_symbolic_value
assert @state.matches?(:parked)
assert !@state.matches?('parked')
@@ -224,6 +243,11 @@ def test_should_include_value_in_description
assert_equal 'parked (1)', @state.description
end
+ def test_should_allow_human_name_in_description
+ @state.human_name = 'Parked'
+ assert_equal 'Parked (1)', @state.description(:human_name => true)
+ end
+
def test_should_match_integer_value
assert @state.matches?(1)
assert !@state.matches?(2)
@@ -956,6 +980,23 @@ def test_should_use_doublecircle_as_shape
assert_equal 'doublecircle', @node['shape'].to_s.gsub('"', '')
end
end
+
+ class StateDrawingWithHumanNameTest < Test::Unit::TestCase
+ def setup
+ @machine = StateMachine::Machine.new(Class.new)
+ @machine.states << @state = StateMachine::State.new(@machine, :parked, :human_name => 'Parked')
+ @machine.event :ignite do
+ transition :parked => :idling
+ end
+
+ graph = GraphViz.new('G')
+ @node = @state.draw(graph, :human_name => true)
+ end
+
+ def test_should_use_description_with_human_name_as_label
+ assert_equal 'Parked', @node['label'].to_s.gsub('"', '')
+ end
+ end
rescue LoadError
$stderr.puts 'Skipping GraphViz StateMachine::State tests. `gem install ruby-graphviz` >= v0.9.0 and try again.'
end unless ENV['TRAVIS']
Please sign in to comment.
Something went wrong with that request. Please try again.