finite_machine 0.11.3 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +564 -569
  4. data/Rakefile +5 -1
  5. data/benchmarks/memory_profile.rb +11 -0
  6. data/benchmarks/memory_usage.rb +16 -9
  7. data/finite_machine.gemspec +10 -3
  8. data/lib/finite_machine.rb +34 -46
  9. data/lib/finite_machine/async_call.rb +5 -21
  10. data/lib/finite_machine/callable.rb +4 -4
  11. data/lib/finite_machine/catchable.rb +4 -2
  12. data/lib/finite_machine/choice_merger.rb +19 -19
  13. data/lib/finite_machine/const.rb +16 -0
  14. data/lib/finite_machine/definition.rb +2 -2
  15. data/lib/finite_machine/dsl.rb +66 -149
  16. data/lib/finite_machine/env.rb +4 -2
  17. data/lib/finite_machine/event_definition.rb +7 -15
  18. data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
  19. data/lib/finite_machine/hook_event.rb +60 -61
  20. data/lib/finite_machine/hooks.rb +44 -36
  21. data/lib/finite_machine/listener.rb +2 -2
  22. data/lib/finite_machine/logger.rb +5 -4
  23. data/lib/finite_machine/message_queue.rb +39 -30
  24. data/lib/finite_machine/observer.rb +55 -37
  25. data/lib/finite_machine/safety.rb +12 -10
  26. data/lib/finite_machine/state_definition.rb +3 -5
  27. data/lib/finite_machine/state_machine.rb +83 -64
  28. data/lib/finite_machine/state_parser.rb +51 -79
  29. data/lib/finite_machine/subscribers.rb +1 -1
  30. data/lib/finite_machine/threadable.rb +3 -1
  31. data/lib/finite_machine/transition.rb +30 -31
  32. data/lib/finite_machine/transition_builder.rb +23 -32
  33. data/lib/finite_machine/transition_event.rb +12 -11
  34. data/lib/finite_machine/two_phase_lock.rb +3 -1
  35. data/lib/finite_machine/undefined_transition.rb +5 -6
  36. data/lib/finite_machine/version.rb +2 -2
  37. data/spec/integration/system_spec.rb +36 -38
  38. data/spec/performance/benchmark_spec.rb +13 -21
  39. data/spec/unit/alias_target_spec.rb +22 -41
  40. data/spec/unit/async_callbacks_spec.rb +8 -13
  41. data/spec/unit/auto_methods_spec.rb +44 -0
  42. data/spec/unit/callable/call_spec.rb +1 -3
  43. data/spec/unit/callbacks_spec.rb +372 -463
  44. data/spec/unit/can_spec.rb +13 -23
  45. data/spec/unit/cancel_callbacks_spec.rb +46 -0
  46. data/spec/unit/choice_spec.rb +105 -141
  47. data/spec/unit/define_spec.rb +31 -31
  48. data/spec/unit/definition_spec.rb +24 -41
  49. data/spec/unit/event_names_spec.rb +6 -10
  50. data/spec/unit/events_map/add_spec.rb +23 -0
  51. data/spec/unit/events_map/choice_transition_spec.rb +25 -0
  52. data/spec/unit/events_map/clear_spec.rb +13 -0
  53. data/spec/unit/events_map/events_spec.rb +16 -0
  54. data/spec/unit/events_map/inspect_spec.rb +22 -0
  55. data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
  56. data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
  57. data/spec/unit/events_map/states_for_spec.rb +17 -0
  58. data/spec/unit/events_spec.rb +91 -160
  59. data/spec/unit/handlers_spec.rb +34 -66
  60. data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
  61. data/spec/unit/hook_event/build_spec.rb +1 -3
  62. data/spec/unit/hook_event/eql_spec.rb +1 -3
  63. data/spec/unit/hook_event/initialize_spec.rb +2 -4
  64. data/spec/unit/hook_event/notify_spec.rb +2 -4
  65. data/spec/unit/hooks/clear_spec.rb +1 -1
  66. data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
  67. data/spec/unit/hooks/inspect_spec.rb +16 -8
  68. data/spec/unit/hooks/register_spec.rb +4 -9
  69. data/spec/unit/if_unless_spec.rb +76 -115
  70. data/spec/unit/initial_spec.rb +50 -82
  71. data/spec/unit/inspect_spec.rb +14 -9
  72. data/spec/unit/is_spec.rb +12 -18
  73. data/spec/unit/log_transitions_spec.rb +4 -10
  74. data/spec/unit/logger_spec.rb +1 -3
  75. data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
  76. data/spec/unit/new_spec.rb +50 -0
  77. data/spec/unit/respond_to_spec.rb +2 -6
  78. data/spec/unit/state_parser/parse_spec.rb +9 -12
  79. data/spec/unit/states_spec.rb +12 -18
  80. data/spec/unit/subscribers_spec.rb +1 -3
  81. data/spec/unit/target_spec.rb +60 -93
  82. data/spec/unit/terminated_spec.rb +15 -25
  83. data/spec/unit/transition/check_conditions_spec.rb +16 -15
  84. data/spec/unit/transition/inspect_spec.rb +6 -6
  85. data/spec/unit/transition/matches_spec.rb +5 -7
  86. data/spec/unit/transition/states_spec.rb +5 -7
  87. data/spec/unit/transition/to_state_spec.rb +5 -13
  88. data/spec/unit/trigger_spec.rb +5 -9
  89. data/spec/unit/undefined_transition/eql_spec.rb +1 -3
  90. metadata +86 -49
  91. data/.gitignore +0 -18
  92. data/.rspec +0 -5
  93. data/.travis.yml +0 -27
  94. data/Gemfile +0 -16
  95. data/assets/finite_machine_logo.png +0 -0
  96. data/lib/finite_machine/async_proxy.rb +0 -55
  97. data/spec/unit/async_events_spec.rb +0 -107
  98. data/spec/unit/events_chain/add_spec.rb +0 -25
  99. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  100. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  101. data/spec/unit/events_chain/clear_spec.rb +0 -15
  102. data/spec/unit/events_chain/events_spec.rb +0 -18
  103. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  104. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  105. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  106. data/spec/unit/state_parser/inspect_spec.rb +0 -25
data/Rakefile CHANGED
@@ -4,5 +4,9 @@ require "bundler/gem_tasks"
4
4
 
5
5
  FileList['tasks/**/*.rake'].each(&method(:import))
6
6
 
7
+ jruby = RUBY_ENGINE == 'ruby'
8
+ specs = ['spec']
9
+ specs << 'spec:perf' if jruby
10
+
7
11
  desc 'Run all specs'
8
- task ci: %w[ spec spec:perf ]
12
+ task ci: specs
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/finite_machine'
4
+ require 'ruby-prof'
5
+
6
+ result = RubyProf.profile do
7
+ FiniteMachine.new
8
+ end
9
+
10
+ printer = RubyProf::FlatPrinter.new(result)
11
+ printer.print($stdout)
@@ -1,21 +1,28 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'finite_machine'
3
+ require_relative '../lib/finite_machine'
4
4
 
5
- 3.times do
5
+ 5.times do
6
6
  puts
7
7
 
8
8
  GC.start
9
9
 
10
- before = GC.stat
11
- p ObjectSpace.count_objects
10
+ gc_before = GC.stat
11
+ objects_before = ObjectSpace.count_objects
12
+ p objects_before[:T_OBJECT]
12
13
 
13
14
  1_000.times do
14
- FiniteMachine.define
15
+ FiniteMachine.new do
16
+ initial :green
17
+
18
+ events { event :slow, :green => :yellow }
19
+ end
15
20
  end
16
21
 
17
- p ObjectSpace.count_objects
18
- after = GC.stat
22
+ objects_after = ObjectSpace.count_objects
23
+ gc_after = GC.stat
24
+ p objects_after[:T_OBJECT]
19
25
 
20
- p "GC count: #{after[:count] - before[:count]}"
26
+ p "GC count: #{gc_after[:count] - gc_before[:count]}"
27
+ p "Objects count: #{objects_after[:T_OBJECT] - objects_before[:T_OBJECT]}"
21
28
  end
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'finite_machine/version'
@@ -10,14 +9,22 @@ Gem::Specification.new do |spec|
10
9
  spec.email = [""]
11
10
  spec.description = %q{A minimal finite state machine with a straightforward syntax. You can quickly model states, add callbacks and use object-oriented techniques to integrate with ORMs.}
12
11
  spec.summary = %q{A minimal finite state machine with a straightforward syntax.}
13
- spec.homepage = "http://peter-murach.github.io/finite_machine/"
12
+ spec.homepage = "http://piotrmurach.github.io/finite_machine/"
14
13
  spec.license = "MIT"
15
14
 
16
- spec.files = `git ls-files`.split($/)
15
+ spec.files = Dir['{lib,spec,examples,benchmarks}/**/*.rb']
16
+ spec.files += Dir['tasks/*', 'finite_machine.gemspec']
17
+ spec.files += Dir['README.md', 'CHANGELOG.md', 'LICENSE.txt', 'Rakefile']
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
20
  spec.require_paths = ["lib"]
20
21
 
22
+ spec.required_ruby_version = '>= 2.0.0'
23
+
24
+ spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
25
+
21
26
  spec.add_development_dependency 'bundler', '>= 1.5.0', '< 2.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.1'
28
+ spec.add_development_dependency 'rspec-benchmark', '~> 0.4.0'
22
29
  spec.add_development_dependency 'rake'
23
30
  end
@@ -1,33 +1,12 @@
1
- # encoding: utf-8
2
-
3
- require "logger"
4
- require "thread"
5
- require "sync"
6
- require "forwardable"
7
-
8
- require "finite_machine/version"
9
- require "finite_machine/threadable"
10
- require "finite_machine/safety"
11
- require "finite_machine/callable"
12
- require "finite_machine/catchable"
13
- require "finite_machine/choice_merger"
14
- require "finite_machine/async_proxy"
15
- require "finite_machine/async_call"
16
- require "finite_machine/hook_event"
17
- require "finite_machine/env"
18
- require "finite_machine/message_queue"
19
- require "finite_machine/events_chain"
20
- require "finite_machine/logger"
21
- require "finite_machine/transition"
22
- require "finite_machine/transition_builder"
23
- require "finite_machine/transition_event"
24
- require "finite_machine/dsl"
25
- require "finite_machine/definition"
26
- require "finite_machine/state_machine"
27
- require "finite_machine/subscribers"
28
- require "finite_machine/observer"
29
- require "finite_machine/listener"
30
- require "finite_machine/two_phase_lock"
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require_relative 'finite_machine/const'
6
+ require_relative 'finite_machine/logger'
7
+ require_relative 'finite_machine/definition'
8
+ require_relative 'finite_machine/state_machine'
9
+ require_relative 'finite_machine/version'
31
10
 
32
11
  module FiniteMachine
33
12
  # Default state name
@@ -36,22 +15,19 @@ module FiniteMachine
36
15
  # Initial default event name
37
16
  DEFAULT_EVENT_NAME = :init
38
17
 
39
- # Describe any state transition
40
- ANY_STATE = :any
18
+ # Describe any transition state
19
+ ANY_STATE = Const.new(:any)
41
20
 
42
21
  # Describe any event name
43
- ANY_EVENT = :any_event
44
-
45
- # Returned when transition has successfully performed
46
- SUCCEEDED = 1
47
-
48
- # Returned when transition is cancelled in callback
49
- CANCELLED = 2
22
+ ANY_EVENT = Const.new(:any_event)
50
23
 
51
24
  # When transition between states is invalid
52
25
  TransitionError = Class.new(::StandardError)
53
26
 
54
- # Raised when transitining to invalid state
27
+ # When failed to process callback
28
+ CallbackError = Class.new(::StandardError)
29
+
30
+ # Raised when transitioning to invalid state
55
31
  InvalidStateError = Class.new(::ArgumentError)
56
32
 
57
33
  InvalidEventError = Class.new(::NoMethodError)
@@ -74,21 +50,33 @@ module FiniteMachine
74
50
  class << self
75
51
  attr_accessor :logger
76
52
 
77
- # TODO: this should instantiate system not the state machine
78
- # and then delegate calls to StateMachine instance etc...
53
+ # Initialize an instance of finite machine
79
54
  #
80
55
  # @example
81
- # FiniteMachine.define do
56
+ # FiniteMachine.new do
82
57
  # ...
83
58
  # end
84
59
  #
85
60
  # @return [FiniteMachine::StateMachine]
86
61
  #
87
62
  # @api public
88
- def define(*args, &block)
89
- StateMachine.new(*args, &block)
63
+ def new(*args, **options, &block)
64
+ StateMachine.new(*args, **options, &block)
65
+ end
66
+
67
+ # A factory method for creating reusable FiniteMachine definitions
68
+ #
69
+ # @example
70
+ # TrafficLights = FiniteMachine.define
71
+ # lights_fm_a = TrafficLights.new
72
+ # lights_fm_b = TrafficLights.new
73
+ #
74
+ # @return [Class]
75
+ #
76
+ # @api public
77
+ def define(&block)
78
+ Class.new(Definition, &block)
90
79
  end
91
- alias_method :new, :define
92
80
  end
93
81
  end # FiniteMachine
94
82
 
@@ -1,14 +1,13 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
- # An asynchronouse call representation
4
+ # An immutable asynchronouse call representation that wraps
5
+ # the {Callable} object
5
6
  #
6
- # Used internally by {EventQueue} to schedule events
7
+ # Used internally by {MessageQueue} to dispatch events
7
8
  #
8
9
  # @api private
9
10
  class AsyncCall
10
- include Threadable
11
-
12
11
  # Create asynchronous call instance
13
12
  #
14
13
  # @param [Object] context
@@ -19,15 +18,12 @@ module FiniteMachine
19
18
  # @example
20
19
  # AsyncCall.new(context, Callable.new(:method), :a, :b)
21
20
  #
22
- # @return [self]
23
- #
24
21
  # @api public
25
22
  def initialize(context, callable, *args, &block)
26
23
  @context = context
27
24
  @callable = callable
28
25
  @arguments = args.dup
29
26
  @block = block
30
- @mutex = Mutex.new
31
27
  freeze
32
28
  end
33
29
 
@@ -37,19 +33,7 @@ module FiniteMachine
37
33
  #
38
34
  # @api private
39
35
  def dispatch
40
- @mutex.synchronize do
41
- callable.call(context, *arguments, &block)
42
- end
36
+ @callable.call(@context, *@arguments, &@block)
43
37
  end
44
-
45
- protected
46
-
47
- attr_threadsafe :context
48
-
49
- attr_threadsafe :callable
50
-
51
- attr_threadsafe :arguments
52
-
53
- attr_threadsafe :block
54
38
  end # AsyncCall
55
39
  end # FiniteMachine
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
4
  # A generic interface for executing strings, symbol methods or procs.
@@ -20,7 +20,7 @@ module FiniteMachine
20
20
  #
21
21
  # @api public
22
22
  def invert
23
- lambda { |*args, &block| !call(*args, &block) }
23
+ ->(*args, &block) { !call(*args, &block) }
24
24
  end
25
25
 
26
26
  # Execute action
@@ -34,10 +34,10 @@ module FiniteMachine
34
34
  target.public_send(object.to_sym, *args, &block)
35
35
  when String
36
36
  string = args.empty? ? "-> { #{object} }" : "-> { #{object}(*#{args}) }"
37
- value = eval string
37
+ value = eval(string)
38
38
  target.instance_exec(&value)
39
39
  when ::Proc
40
- object.arity.zero? ? object.call : object.call(target, *args)
40
+ object.arity.zero? ? object.call : object.call(target, *args)
41
41
  else
42
42
  raise ArgumentError, "Unknown callable #{object}"
43
43
  end
@@ -1,9 +1,11 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
4
  # A mixin to allow for specifying error handlers
5
5
  module Catchable
6
-
6
+ # Extends object with error handling methods
7
+ #
8
+ # @api private
7
9
  def self.included(base)
8
10
  base.module_eval do
9
11
  attr_threadsafe :error_handlers, default: []
@@ -1,43 +1,43 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'transition_builder'
2
4
 
3
5
  module FiniteMachine
4
6
  # A class responsible for merging choice options
5
7
  class ChoiceMerger
6
- include Threadable
7
-
8
- # The context where choice is executed
9
- attr_threadsafe :machine
10
-
11
- # The options passed in to the machine
12
- attr_threadsafe :options
13
-
14
8
  # Initialize a ChoiceMerger
15
9
  #
10
+ # @param [StateMachine] machine
11
+ # @param [String] name
12
+ # @param [Hash] transitions
13
+ # the transitions and attributes
14
+ #
16
15
  # @api private
17
- def initialize(machine, options)
18
- self.machine = machine
19
- self.options = options
16
+ def initialize(machine, name, **transitions)
17
+ @machine = machine
18
+ @name = name
19
+ @transitions = transitions
20
20
  end
21
21
 
22
22
  # Create choice transition
23
23
  #
24
24
  # @example
25
- # event from: :green do
25
+ # event :stop, from: :green do
26
26
  # choice :yellow
27
27
  # end
28
28
  #
29
29
  # @param [Symbol] to
30
30
  # the to state
31
- # @param [Hash] attrs
31
+ # @param [Hash] conditions
32
+ # the conditions associated with this choice
32
33
  #
33
34
  # @return [FiniteMachine::Transition]
34
35
  #
35
36
  # @api public
36
- def choice(to, attrs = {})
37
- opts = options.dup
38
- opts.merge!(attrs)
39
- transition_builder = TransitionBuilder.new(machine, opts)
40
- transition_builder.call(options[:from] => to)
37
+ def choice(to, **conditions)
38
+ transition_builder = TransitionBuilder.new(@machine, @name,
39
+ @transitions.merge(conditions))
40
+ transition_builder.call(@transitions[:from] => to)
41
41
  end
42
42
  alias_method :default, :choice
43
43
  end # ChoiceMerger
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiniteMachine
4
+ class Const
5
+ def initialize(name)
6
+ @name = name.to_s
7
+ freeze
8
+ end
9
+
10
+ def to_s
11
+ @name
12
+ end
13
+ alias to_str to_s
14
+ alias inspect to_s
15
+ end # Const
16
+ end # FiniteMachine
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
4
  # A class responsible for defining standalone state machine
@@ -38,7 +38,7 @@ module FiniteMachine
38
38
  # @api public
39
39
  def self.new(*args)
40
40
  context = self
41
- FiniteMachine.define(*args) do
41
+ FiniteMachine.new(*args) do
42
42
  context.deferreds.each { |d| d.call(self) }
43
43
  end
44
44
  end
@@ -1,59 +1,68 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'choice_merger'
4
+ require_relative 'safety'
5
+ require_relative 'transition_builder'
2
6
 
3
7
  module FiniteMachine
4
8
  # A generic DSL for describing the state machine
5
9
  class GenericDSL
6
- include Threadable
7
-
8
- class << self
9
- # @api private
10
- attr_accessor :top_level
10
+ # Initialize a generic DSL
11
+ #
12
+ # @api public
13
+ def initialize(machine, **attrs)
14
+ @machine = machine
15
+ @attrs = attrs
11
16
  end
12
17
 
13
- attr_threadsafe :machine
14
-
15
- attr_threadsafe :attrs
18
+ # Expose any state constant
19
+ # @api public
20
+ def any_state
21
+ ANY_STATE
22
+ end
16
23
 
17
- # Initialize a generic DSL
18
- #
24
+ # Expose any event constant
19
25
  # @api public
20
- def initialize(machine, attrs = {})
21
- self.attrs = attrs
22
- self.machine = machine
26
+ def any_event
27
+ ANY_EVENT
23
28
  end
24
29
 
25
30
  # Delegate attributes to machine instance
26
31
  #
27
32
  # @api private
28
33
  def method_missing(method_name, *args, &block)
29
- if machine.respond_to?(method_name)
30
- machine.send(method_name, *args, &block)
34
+ if @machine.respond_to?(method_name)
35
+ @machine.send(method_name, *args, &block)
31
36
  else
32
37
  super
33
38
  end
34
39
  end
35
40
 
41
+ # Configure state machine properties
42
+ #
43
+ # @api private
36
44
  def call(&block)
37
- sync_exclusive { instance_eval(&block) }
38
- # top_level.instance_eval(&block)
45
+ instance_eval(&block)
39
46
  end
40
47
  end # GenericDSL
41
48
 
42
49
  # A class responsible for adding state machine specific dsl
43
50
  class DSL < GenericDSL
44
- attr_threadsafe :defer
45
-
46
- attr_threadsafe :initial_event
51
+ include Safety
47
52
 
48
53
  # Initialize top level DSL
49
54
  #
50
55
  # @api public
51
- def initialize(machine, attrs = {})
56
+ def initialize(machine, **attrs)
52
57
  super(machine, attrs)
53
- machine.state = FiniteMachine::DEFAULT_STATE
54
- self.defer = true
55
58
 
56
- initialize_attrs
59
+ @machine.state = FiniteMachine::DEFAULT_STATE
60
+ @defer_initial = true
61
+ @silent_initial = true
62
+
63
+ initial(@attrs[:initial]) if @attrs[:initial]
64
+ terminal(@attrs[:terminal]) if @attrs[:terminal]
65
+ log_transitions(@attrs.fetch(:log_transitions, false))
57
66
  end
58
67
 
59
68
  # Define initial state
@@ -87,11 +96,11 @@ module FiniteMachine
87
96
  # @return [StateMachine]
88
97
  #
89
98
  # @api public
90
- def initial(value, options = {})
99
+ def initial(value, **options)
91
100
  state = (value && !value.is_a?(Hash)) ? value : raise_missing_state
92
- name, self.defer, silent = *parse_initial(options)
93
- self.initial_event = name
94
- event(name, FiniteMachine::DEFAULT_STATE => state, silent: silent)
101
+ name, @defer_initial, @silent_initial = *parse_initial(options)
102
+ @initial_event = name
103
+ event(name, FiniteMachine::DEFAULT_STATE => state, silent: @silent_initial)
95
104
  end
96
105
 
97
106
  # Trigger initial event
@@ -100,51 +109,8 @@ module FiniteMachine
100
109
  #
101
110
  # @api private
102
111
  def trigger_init
103
- public_send(:"#{initial_event}") unless defer
104
- end
105
-
106
- # Attach state machine to an object
107
- #
108
- # This allows state machine to initiate events in the context
109
- # of a particular object
110
- #
111
- # @example
112
- # FiniteMachine.define do
113
- # target :red
114
- # end
115
- #
116
- # @param [Object] object
117
- #
118
- # @return [FiniteMachine::StateMachine]
119
- #
120
- # @api public
121
- def target(object = nil)
122
- if object.nil?
123
- env.target
124
- else
125
- env.target = object
126
- end
127
- end
128
-
129
- # Use alternative name for target
130
- #
131
- # @example
132
- # target_alias: :car
133
- #
134
- # callbacks {
135
- # on_transition do |event|
136
- # car.state = event.to
137
- # end
138
- # }
139
- #
140
- # @param [Symbol] alias_name
141
- # the name to alias target to
142
- #
143
- # @return [FiniteMachine::StateMachine]
144
- #
145
- # @api public
146
- def alias_target(alias_name)
147
- env.aliases << alias_name.to_sym
112
+ method = @silent_initial ? :transition : :trigger
113
+ @machine.public_send(method, :"#{@initial_event}") unless @defer_initial
148
114
  end
149
115
 
150
116
  # Define terminal state
@@ -159,39 +125,44 @@ module FiniteMachine
159
125
  self.final_state = values
160
126
  end
161
127
 
162
- # Define state machine events
128
+ # Create event and associate transition
163
129
  #
164
130
  # @example
165
- # events do
166
- # event :start, :red => :green
167
- # end
131
+ # event :go, :green => :yellow
132
+ # event :go, :green => :yellow, if: :lights_on?
168
133
  #
169
- # @return [FiniteMachine::StateMachine]
134
+ # @param [Symbol] name
135
+ # the event name
136
+ # @param [Hash] transitions
137
+ # the event transitions and conditions
138
+ #
139
+ # @return [Transition]
170
140
  #
171
141
  # @api public
172
- def events(&block)
173
- events_dsl.call(&block)
142
+ def event(name, transitions = {}, &block)
143
+ detect_event_conflict!(name) if machine.auto_methods?
144
+
145
+ if block_given?
146
+ merger = ChoiceMerger.new(machine, name, transitions)
147
+ merger.instance_eval(&block)
148
+ else
149
+ transition_builder = TransitionBuilder.new(machine, name, transitions)
150
+ transition_builder.call(transitions)
151
+ end
174
152
  end
175
153
 
176
- # Define state machine callbacks
154
+ # Add error handler
177
155
  #
178
- # @example
179
- # callbacks do
180
- # on_enter :green do |event| ... end
181
- # end
156
+ # @param [Array] exceptions
182
157
  #
183
- # @return [FiniteMachine::Observer]
158
+ # @example
159
+ # handle InvalidStateError, with: :log_errors
184
160
  #
185
- # @api public
186
- def callbacks(&block)
187
- observer.call(&block)
188
- end
189
-
190
- # Error handler that throws exception when machine is in illegal state
161
+ # @return [Array[Exception]]
191
162
  #
192
163
  # @api public
193
- def handlers(&block)
194
- errors_dsl.call(&block)
164
+ def handle(*exceptions, &block)
165
+ @machine.handle(*exceptions, &block)
195
166
  end
196
167
 
197
168
  # Decide whether to log transitions
@@ -203,16 +174,6 @@ module FiniteMachine
203
174
 
204
175
  private
205
176
 
206
- # Initialize state machine properties based off attributes
207
- #
208
- # @api private
209
- def initialize_attrs
210
- attrs[:initial] && initial(attrs[:initial])
211
- attrs[:target] && target(attrs[:target])
212
- attrs[:terminal] && terminal(attrs[:terminal])
213
- log_transitions(attrs.fetch(:log_transitions, false))
214
- end
215
-
216
177
  # Parse initial options
217
178
  #
218
179
  # @param [Hash] options
@@ -240,48 +201,4 @@ module FiniteMachine
240
201
  'Provide state to transition :to for the initial event'
241
202
  end
242
203
  end # DSL
243
-
244
- # A DSL for describing events
245
- class EventsDSL < GenericDSL
246
- include Safety
247
- # Create event and associate transition
248
- #
249
- # @example
250
- # event :go, :green => :yellow
251
- # event :go, :green => :yellow, if: :lights_on?
252
- #
253
- # @return [Transition]
254
- #
255
- # @api public
256
- def event(name, attrs = {}, &block)
257
- sync_exclusive do
258
- detect_event_conflict!(name)
259
- attributes = attrs.merge!(name: name)
260
- if block_given?
261
- merger = ChoiceMerger.new(machine, attributes)
262
- merger.instance_eval(&block)
263
- else
264
- transition_builder = TransitionBuilder.new(machine, attributes)
265
- transition_builder.call(attrs)
266
- end
267
- end
268
- end
269
- end # EventsDSL
270
-
271
- # A DSL for describing error conditions
272
- class ErrorsDSL < GenericDSL
273
- # Add error handler
274
- #
275
- # @param [Array] exceptions
276
- #
277
- # @example
278
- # handle InvalidStateError, with: :log_errors
279
- #
280
- # @return [Array[Exception]]
281
- #
282
- # @api public
283
- def handle(*exceptions, &block)
284
- machine.handle(*exceptions, &block)
285
- end
286
- end # ErrorsDSL
287
204
  end # FiniteMachine