finite_machine 0.3.0 → 0.4.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +85 -14
- data/examples/atm.rb +45 -0
- data/examples/bug_system.rb +145 -0
- data/lib/finite_machine.rb +16 -4
- data/lib/finite_machine/async_call.rb +10 -1
- data/lib/finite_machine/dsl.rb +27 -8
- data/lib/finite_machine/event_queue.rb +12 -10
- data/lib/finite_machine/logger.rb +23 -0
- data/lib/finite_machine/observer.rb +11 -3
- data/lib/finite_machine/state_machine.rb +2 -4
- data/lib/finite_machine/subscribers.rb +36 -2
- data/lib/finite_machine/thread_context.rb +3 -1
- data/lib/finite_machine/transition.rb +32 -6
- data/lib/finite_machine/version.rb +1 -1
- data/spec/unit/async_events_spec.rb +4 -4
- data/spec/unit/callbacks_spec.rb +48 -6
- data/spec/unit/events_spec.rb +15 -0
- data/spec/unit/if_unless_spec.rb +90 -60
- data/spec/unit/initialize_spec.rb +48 -1
- data/spec/unit/inspect_spec.rb +25 -0
- data/spec/unit/logger_spec.rb +33 -0
- data/spec/unit/subscribers_spec.rb +31 -0
- metadata +11 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b69fcd7446203267dc8130e4c986ea334e4b26ec
         | 
| 4 | 
            +
              data.tar.gz: 3a4bca63a58bf4a3c0a509cc99778d66771a535e
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d5c2636ea7607b138737e263771b5269d950f1403079e67dfd41d4940241f41cf62d1118cfe7ae177cfe3a716985cad9f33f76313ada9ce4424ba3fcbbd89511
         | 
| 7 | 
            +
              data.tar.gz: c40fda5ae25615ca3d2ee239dc59a952596c3860f309c22b8d01e8b4b8acd10f0d7aeb7ddb2f1dc80aecd46c9bd4b3b6e0fd456b28b14dfc9f34144b7ef80fac
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,13 @@ | |
| 1 | 
            +
            0.4.0 (April 13, 2014)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Change initial state to stop firing event notification
         | 
| 4 | 
            +
            * Fix initial to accept any state object
         | 
| 5 | 
            +
            * Add logger
         | 
| 6 | 
            +
            * Add ability to cancel transitions inside callbacks
         | 
| 7 | 
            +
            * Fix proc conditions to accept aditional arguments
         | 
| 8 | 
            +
            * Increase test coverage to 97%
         | 
| 9 | 
            +
            * Add ability to force transitions
         | 
| 10 | 
            +
             | 
| 1 11 | 
             
            0.3.0 (March 30, 2014)
         | 
| 2 12 |  | 
| 3 13 | 
             
            * Move development dependencies to Gemfile
         | 
    
        data/README.md
    CHANGED
    
    | @@ -43,11 +43,38 @@ Or install it yourself as: | |
| 43 43 | 
             
            ## Contents
         | 
| 44 44 |  | 
| 45 45 | 
             
            * [1. Usage](#1-usage)
         | 
| 46 | 
            +
                * [1.1 Current](#11-current)
         | 
| 47 | 
            +
                * [1.2 Initial](#12-initial)
         | 
| 48 | 
            +
                * [1.3 Terminal](#13-terminal)
         | 
| 49 | 
            +
                * [1.4 is?](#14-is)
         | 
| 50 | 
            +
                * [1.5 can? and cannot?](#15-can-and-cannot)
         | 
| 51 | 
            +
                * [1.6 states](#16-states)
         | 
| 52 | 
            +
                * [1.7 target](#17-target)
         | 
| 46 53 | 
             
            * [2. Transitions](#2-transitions)
         | 
| 54 | 
            +
                * [2.1 Performing transitions](#21-performing-transitions)
         | 
| 55 | 
            +
                * [2.2 Forcing transitions](#22-forcing-transitions)
         | 
| 56 | 
            +
                * [2.3 Asynchronous transitions](#23-asynchronous-transitions)
         | 
| 57 | 
            +
                * [2.4 Single event with multiple from states](#24-single-event-with-multiple-from-states)
         | 
| 47 58 | 
             
            * [3. Conditional transitions](#3-conditional-transitions)
         | 
| 59 | 
            +
                * [3.1 Using a Proc](#31-using-a-proc)
         | 
| 60 | 
            +
                * [3.2 Using a Symbol](#32-using-a-symbol)
         | 
| 61 | 
            +
                * [3.3 Using a String](#33-using-a-string)
         | 
| 62 | 
            +
                * [3.4 Combining transition conditions](#34-combining-transition-conditions)
         | 
| 48 63 | 
             
            * [4. Callbacks](#4-callbacks)
         | 
| 64 | 
            +
                * [4.1 on_enter](#41-on_enter)
         | 
| 65 | 
            +
                * [4.2 on_transition](#42-on_transition)
         | 
| 66 | 
            +
                * [4.3 on_exit](#43-on_exit)
         | 
| 67 | 
            +
                * [4.4 once_on](#44-once_on)
         | 
| 68 | 
            +
                * [4.5 parameters](#45-parameters)
         | 
| 69 | 
            +
                * [4.6 Same kind of callbacks](#46-same-kind-of-callbacks)
         | 
| 70 | 
            +
                * [4.7 Fluid callbacks](#47-fluid-callbacks)
         | 
| 71 | 
            +
                * [4.8 Executing methods inside callbacks](#48-executing-methods-inside-callbacks)
         | 
| 72 | 
            +
                * [4.9 Defining callbacks](#49-defining-callbacks)
         | 
| 73 | 
            +
                * [4.10 Cancelling inside callbacks](#410-cancelling-inside-callbacks)
         | 
| 49 74 | 
             
            * [5. Errors](#5-errors)
         | 
| 75 | 
            +
                * [5.1 Using target](#51-using-target)
         | 
| 50 76 | 
             
            * [6. Integration](#6-integration)
         | 
| 77 | 
            +
                * [6.1 ActiveRecord](#61-activerecord)
         | 
| 51 78 | 
             
            * [7. Tips](#7-tips)
         | 
| 52 79 |  | 
| 53 80 | 
             
            ## 1 Usage
         | 
| @@ -98,9 +125,11 @@ fm = FiniteMachine.define do | |
| 98 125 | 
             
            end
         | 
| 99 126 |  | 
| 100 127 | 
             
            fm.current # => :none
         | 
| 128 | 
            +
            fm.start
         | 
| 129 | 
            +
            fm.current # => :green
         | 
| 101 130 | 
             
            ```
         | 
| 102 131 |  | 
| 103 | 
            -
            If you specify initial state using the `initial` helper,  | 
| 132 | 
            +
            If you specify initial state using the `initial` helper, the state machine will be created already in that state.
         | 
| 104 133 |  | 
| 105 134 | 
             
            ```ruby
         | 
| 106 135 | 
             
            fm = FiniteMachine.define do
         | 
| @@ -115,34 +144,36 @@ end | |
| 115 144 | 
             
            fm.current # => :green
         | 
| 116 145 | 
             
            ```
         | 
| 117 146 |  | 
| 118 | 
            -
            If  | 
| 147 | 
            +
            If you want to defer setting the initial state, pass the `:defer` option to the `initial` helper. By default **FiniteMachine** will create `init` event that will allow to transition from `:none` state to the new state.
         | 
| 119 148 |  | 
| 120 149 | 
             
            ```ruby
         | 
| 121 150 | 
             
            fm = FiniteMachine.define do
         | 
| 122 | 
            -
              initial :green,  | 
| 151 | 
            +
              initial state: :green, defer: true
         | 
| 123 152 |  | 
| 124 153 | 
             
              events {
         | 
| 125 154 | 
             
                event :slow,  :green  => :yellow
         | 
| 126 155 | 
             
                event :stop,  :yellow => :red
         | 
| 127 156 | 
             
              }
         | 
| 128 157 | 
             
            end
         | 
| 129 | 
            -
             | 
| 158 | 
            +
            fm.current # => :none
         | 
| 159 | 
            +
            fm.init
         | 
| 130 160 | 
             
            fm.current # => :green
         | 
| 131 161 | 
             
            ```
         | 
| 132 162 |  | 
| 133 | 
            -
            If  | 
| 163 | 
            +
            If your target object already has `init` method or one of the events names redefines `init`, you can use different name by passing `:event` option to `initial` helper.
         | 
| 134 164 |  | 
| 135 165 | 
             
            ```ruby
         | 
| 136 166 | 
             
            fm = FiniteMachine.define do
         | 
| 137 | 
            -
              initial state: :green, defer: true
         | 
| 167 | 
            +
              initial state: :green, event: :start, defer: true
         | 
| 138 168 |  | 
| 139 169 | 
             
              events {
         | 
| 140 170 | 
             
                event :slow,  :green  => :yellow
         | 
| 141 171 | 
             
                event :stop,  :yellow => :red
         | 
| 142 172 | 
             
              }
         | 
| 143 173 | 
             
            end
         | 
| 174 | 
            +
             | 
| 144 175 | 
             
            fm.current # => :none
         | 
| 145 | 
            -
            fm. | 
| 176 | 
            +
            fm.start
         | 
| 146 177 | 
             
            fm.current # => :green
         | 
| 147 178 | 
             
            ```
         | 
| 148 179 |  | 
| @@ -286,7 +317,11 @@ fm.go('Piotr!') | |
| 286 317 | 
             
            fm.current       # => :green
         | 
| 287 318 | 
             
            ```
         | 
| 288 319 |  | 
| 289 | 
            -
            ### 2.2  | 
| 320 | 
            +
            ### 2.2 Forcing transitions
         | 
| 321 | 
            +
             | 
| 322 | 
            +
            When you declare event, for instance `ready`, the **FiniteMachine** will provide a dangerous version with a bang `ready!`. In the case when you need to perform transition disregarding current state reachable states, the `ready!` will transition without any validations or callbacks.
         | 
| 323 | 
            +
             | 
| 324 | 
            +
            ### 2.3 Asynchronous transitions
         | 
| 290 325 |  | 
| 291 326 | 
             
            By default the transitions will be fired synchronosuly.
         | 
| 292 327 |  | 
| @@ -303,7 +338,7 @@ In order to fire the event transition asynchronously use the `async` scope like | |
| 303 338 | 
             
            fm.async.ready  # => executes in separate Thread
         | 
| 304 339 | 
             
            ```
         | 
| 305 340 |  | 
| 306 | 
            -
            ### 2. | 
| 341 | 
            +
            ### 2.4 Single event with multiple from states
         | 
| 307 342 |  | 
| 308 343 | 
             
            If an event transitions from multiple states to the same state then all the states can be grouped into an array.
         | 
| 309 344 | 
             
            Altenatively, you can create separte events under the same name for each transition that needs combining.
         | 
| @@ -342,10 +377,12 @@ fm.slow    # doesn't transition to :yellow state | |
| 342 377 | 
             
            fm.current # => :green
         | 
| 343 378 | 
             
            ```
         | 
| 344 379 |  | 
| 345 | 
            -
             | 
| 380 | 
            +
            Provided your **FiniteMachine** is associated with another object through `target` helper. Then the target object together with event arguments will be passed to the `:if` or `:unless` condition scope.
         | 
| 346 381 |  | 
| 347 382 | 
             
            ```ruby
         | 
| 348 383 | 
             
            class Car
         | 
| 384 | 
            +
              attr_accessor :engine_on
         | 
| 385 | 
            +
             | 
| 349 386 | 
             
              def turn_engine_on
         | 
| 350 387 | 
             
                @engine_on = true
         | 
| 351 388 | 
             
              end
         | 
| @@ -368,14 +405,21 @@ fm = FiniteMachine.define do | |
| 368 405 | 
             
              target car
         | 
| 369 406 |  | 
| 370 407 | 
             
              events {
         | 
| 371 | 
            -
                event :start, :neutral => :one, if:  | 
| 408 | 
            +
                event :start, :neutral => :one, if: -> (_car, state) {
         | 
| 409 | 
            +
                  _car.engine_on = state
         | 
| 410 | 
            +
                  _car.engine_on?
         | 
| 411 | 
            +
                }
         | 
| 372 412 | 
             
              }
         | 
| 373 413 | 
             
            end
         | 
| 374 414 |  | 
| 375 | 
            -
            fm.start
         | 
| 376 | 
            -
            fm.current | 
| 415 | 
            +
            fm.start(false)
         | 
| 416 | 
            +
            fm.current       # => :neutral
         | 
| 417 | 
            +
            fm.start(true)
         | 
| 418 | 
            +
            fm.current       # => :one
         | 
| 377 419 | 
             
            ```
         | 
| 378 420 |  | 
| 421 | 
            +
            When the one-liner conditions are not enough for your needs, you can perform conditional logic inside the callbacks. See [4.10 Cancelling inside callbacks](#410-cancelling-inside-callbacks)
         | 
| 422 | 
            +
             | 
| 379 423 | 
             
            ### 3.2 Using a Symbol
         | 
| 380 424 |  | 
| 381 425 | 
             
            You can also use a symbol corresponding to the name of a method that will get called right before transition happens.
         | 
| @@ -643,6 +687,33 @@ fm.on_enter_yellow do |event| | |
| 643 687 | 
             
            end
         | 
| 644 688 | 
             
            ```
         | 
| 645 689 |  | 
| 690 | 
            +
            ### 4.10 Cancelling inside callbacks
         | 
| 691 | 
            +
             | 
| 692 | 
            +
            Preferred way to handle cancelling transitions is to use [3 Conditional transitions](#3-conditional-transitions). However if the logic is more than one liner you can cancel the event, hence the transition by returning `FiniteMachine::CANCELLED` constant from the callback scope. The two ways you can affect the event are
         | 
| 693 | 
            +
             | 
| 694 | 
            +
            * `on_exit :state_name`
         | 
| 695 | 
            +
            * `on_enter :event_name`
         | 
| 696 | 
            +
             | 
| 697 | 
            +
            For example
         | 
| 698 | 
            +
             | 
| 699 | 
            +
            ```ruby
         | 
| 700 | 
            +
            fm = FiniteMachine.define do
         | 
| 701 | 
            +
              initial :red
         | 
| 702 | 
            +
             | 
| 703 | 
            +
              events {
         | 
| 704 | 
            +
                event :ready, :red    => :yellow
         | 
| 705 | 
            +
                event :go,    :yellow => :green
         | 
| 706 | 
            +
                event :stop,  :green  => :red
         | 
| 707 | 
            +
              }
         | 
| 708 | 
            +
              callbacks {
         | 
| 709 | 
            +
                on_exit :red do |event| FiniteMachine::CANCELLED end
         | 
| 710 | 
            +
              }
         | 
| 711 | 
            +
            end
         | 
| 712 | 
            +
             | 
| 713 | 
            +
            fm.ready
         | 
| 714 | 
            +
            fm.current  # => :red
         | 
| 715 | 
            +
            ```
         | 
| 716 | 
            +
             | 
| 646 717 | 
             
            ## 5 Errors
         | 
| 647 718 |  | 
| 648 719 | 
             
            By default, the **FiniteMachine** will throw an exception whenever the machine is in invalid state or fails to transition.
         | 
| @@ -792,7 +863,7 @@ account.state   # => :access | |
| 792 863 |  | 
| 793 864 | 
             
            ## 7 Tips
         | 
| 794 865 |  | 
| 795 | 
            -
            Creating a standalone **FiniteMachine** brings  | 
| 866 | 
            +
            Creating a standalone **FiniteMachine** brings a number of benefits, one of them being easier testing. This is especially true if the state machine is extremely complex itself. Ideally, you would test the machine in isolation and then integrate it with other objects or ORMs.
         | 
| 796 867 |  | 
| 797 868 | 
             
            ## Contributing
         | 
| 798 869 |  | 
    
        data/examples/atm.rb
    ADDED
    
    | @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'finite_machine'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Account
         | 
| 6 | 
            +
              attr_accessor :number
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def verify(account_number, pin)
         | 
| 9 | 
            +
                return account_number == 123456 && pin == 666
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            account = Account.new
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            atm = FiniteMachine.define do
         | 
| 16 | 
            +
              initial :unauthorized
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              target account
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              events {
         | 
| 21 | 
            +
                event :authorize, :unauthorized => :authorized, if: -> (account, account_number, pin) {
         | 
| 22 | 
            +
                  account.verify(account_number, pin)
         | 
| 23 | 
            +
                }
         | 
| 24 | 
            +
                event :deauthorize, :authorized => :unauthorized
         | 
| 25 | 
            +
              }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              callbacks {
         | 
| 28 | 
            +
                on_exit :unauthorized do |event, account_number, pin|
         | 
| 29 | 
            +
                  # if verify(account_number, pin)
         | 
| 30 | 
            +
                    self.number = account_number
         | 
| 31 | 
            +
            #       else
         | 
| 32 | 
            +
            #         puts "Invalid Account and/or PIN"
         | 
| 33 | 
            +
            #         FiniteMachine::CANCELLED
         | 
| 34 | 
            +
            #       end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              }
         | 
| 37 | 
            +
            end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            atm.authorize(111222, 666)
         | 
| 40 | 
            +
            puts "authorized: #{atm.authorized?}"
         | 
| 41 | 
            +
            puts "Number: #{account.number}"
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            atm.authorize(123456, 666)
         | 
| 44 | 
            +
            puts "authorized: #{atm.authorized?}"
         | 
| 45 | 
            +
            puts "Number: #{account.number}"
         | 
| @@ -0,0 +1,145 @@ | |
| 1 | 
            +
            $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'finite_machine'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class User
         | 
| 6 | 
            +
              attr_accessor :name
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def initialize(name)
         | 
| 9 | 
            +
                @name = name
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            class Manager < User
         | 
| 14 | 
            +
              attr_accessor :developers
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def initialize(name)
         | 
| 17 | 
            +
                super
         | 
| 18 | 
            +
                @developers = []
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              def manages(developer)
         | 
| 22 | 
            +
                @developers << developer
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def assign(bug)
         | 
| 26 | 
            +
                developer = @developers.first
         | 
| 27 | 
            +
                bug.assign
         | 
| 28 | 
            +
                developer.bug = bug
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            class Tester < User
         | 
| 33 | 
            +
              def report(bug)
         | 
| 34 | 
            +
                bug.report
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def reopen(bug)
         | 
| 38 | 
            +
                bug.reopen
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            class Developer < User
         | 
| 43 | 
            +
              attr_accessor :bug
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def work_on
         | 
| 46 | 
            +
                bug.start
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def resolve
         | 
| 50 | 
            +
                bug.close
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
            end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            class BugSystem
         | 
| 55 | 
            +
              attr_accessor :managers
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              def initialize(managers = [])
         | 
| 58 | 
            +
                @managers = managers
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              def notify_manager(bug)
         | 
| 62 | 
            +
                manager = @managers.first
         | 
| 63 | 
            +
                manager.assign(bug)
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            class Bug
         | 
| 68 | 
            +
              attr_accessor :name
         | 
| 69 | 
            +
              attr_accessor :priority
         | 
| 70 | 
            +
              # fake belongs_to relationship
         | 
| 71 | 
            +
              attr_accessor :bug_system
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              def initialize(name, priority)
         | 
| 74 | 
            +
                @name = name
         | 
| 75 | 
            +
                @priority = priority
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              def report
         | 
| 79 | 
            +
                status.report
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              def assign
         | 
| 83 | 
            +
                status.assign
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              def start
         | 
| 87 | 
            +
                status.start
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              def close
         | 
| 91 | 
            +
                status.close
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              def reopen
         | 
| 95 | 
            +
                status.reopen
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
              def status
         | 
| 99 | 
            +
                context = self
         | 
| 100 | 
            +
                @status ||= FiniteMachine.define do
         | 
| 101 | 
            +
                  target context
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  events {
         | 
| 104 | 
            +
                    event :report, :none => :new
         | 
| 105 | 
            +
                    event :assign, :new => :assigned
         | 
| 106 | 
            +
                    event :start,  :assigned => :in_progress
         | 
| 107 | 
            +
                    event :close,  [:in_progress, :reopened] => :resolved
         | 
| 108 | 
            +
                    event :reopen, :resolved => :reopened
         | 
| 109 | 
            +
                  }
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  callbacks {
         | 
| 112 | 
            +
                    on_enter :new do |event|
         | 
| 113 | 
            +
                      bug_system.notify_manager(self)
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  }
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
            end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            tester    = Tester.new("John")
         | 
| 121 | 
            +
            manager   = Manager.new("David")
         | 
| 122 | 
            +
            developer = Developer.new("Piotr")
         | 
| 123 | 
            +
            manager.manages(developer)
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            bug_system = BugSystem.new([manager])
         | 
| 126 | 
            +
            bug        = Bug.new(:trojan, :high)
         | 
| 127 | 
            +
            bug.bug_system = bug_system
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            puts "A BUG's LIFE"
         | 
| 130 | 
            +
            puts "#1 #{bug.status.current}"
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            tester.report(bug)
         | 
| 133 | 
            +
            puts "#2 #{bug.status.current}"
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            developer.work_on
         | 
| 136 | 
            +
            puts "#3 #{bug.status.current}"
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            developer.resolve
         | 
| 139 | 
            +
            puts "#4 #{bug.status.current}"
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            tester.reopen(bug)
         | 
| 142 | 
            +
            puts "#5 #{bug.status.current}"
         | 
| 143 | 
            +
             | 
| 144 | 
            +
            developer.resolve
         | 
| 145 | 
            +
            puts "#6 #{bug.status.current}"
         | 
    
        data/lib/finite_machine.rb
    CHANGED
    
    | @@ -1,5 +1,6 @@ | |
| 1 1 | 
             
            # encoding: utf-8
         | 
| 2 2 |  | 
| 3 | 
            +
            require "logger"
         | 
| 3 4 | 
             
            require "thread"
         | 
| 4 5 | 
             
            require "sync"
         | 
| 5 6 |  | 
| @@ -13,6 +14,7 @@ require "finite_machine/async_call" | |
| 13 14 | 
             
            require "finite_machine/event"
         | 
| 14 15 | 
             
            require "finite_machine/event_queue"
         | 
| 15 16 | 
             
            require "finite_machine/hooks"
         | 
| 17 | 
            +
            require "finite_machine/logger"
         | 
| 16 18 | 
             
            require "finite_machine/transition"
         | 
| 17 19 | 
             
            require "finite_machine/dsl"
         | 
| 18 20 | 
             
            require "finite_machine/state_machine"
         | 
| @@ -56,11 +58,21 @@ module FiniteMachine | |
| 56 58 | 
             
              # Raised when event has no transitions
         | 
| 57 59 | 
             
              NotEnoughTransitionsError = Class.new(::ArgumentError)
         | 
| 58 60 |  | 
| 61 | 
            +
              # Raised when initial event specified without state name
         | 
| 62 | 
            +
              MissingInitialStateError = Class.new(::StandardError)
         | 
| 63 | 
            +
             | 
| 59 64 | 
             
              Environment = Struct.new(:target)
         | 
| 60 65 |  | 
| 61 | 
            -
               | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
                 | 
| 66 | 
            +
              class << self
         | 
| 67 | 
            +
                attr_accessor :logger
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                # TODO: this should instantiate system not the state machine
         | 
| 70 | 
            +
                # and then delegate calls to StateMachine instance etc...
         | 
| 71 | 
            +
                def define(*args, &block)
         | 
| 72 | 
            +
                  StateMachine.new(*args, &block)
         | 
| 73 | 
            +
                end
         | 
| 65 74 | 
             
              end
         | 
| 75 | 
            +
             | 
| 66 76 | 
             
            end # FiniteMachine
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            FiniteMachine.logger = Logger.new(STDERR)
         |