# Example based on https://github.com/thinkbeforecoding/dddeu-2023-deciders/blob/main/run.fsx require "bundler/inline" gemfile do source "https://rubygems.org" gem "minitest" gem "decide.rb", "~> 0.4.1" end require "minitest/autorun" require "decider" module Bulb module Commands Fit = Data.define(:max_uses) SwitchOn = Data.define SwitchOff = Data.define end module Events Fitted = Data.define(:max_uses) SwitchedOn = Data.define SwitchedOff = Data.define Blew = Data.define end module States NotFitted = Data.define Working = Data.define(:status, :remaining_uses) Blown = Data.define end Decider = ::Decider.define do initial_state States::NotFitted.new terminal? { _1.is_a? Blown } decide Commands::Fit do |command, state| case state in States::NotFitted [Events::Fitted.new(command.max_uses)] else raise "Bulb has already been fitted" end end decide Commands::SwitchOn do |command, state| case state in States::Working(status: :off, remaining_uses:) if remaining_uses > 0 [Events::SwitchedOn.new] in States::Working(status: :off) [Events::Blew.new] else [] end end decide Commands::SwitchOff do |command, state| case state in States::Working(status: :on) [Events::SwitchedOff.new] else [] end end evolve Events::Fitted do |state, event| States::Working.new(status: :off, remaining_uses: event.max_uses) end evolve Events::SwitchedOn do |state, event| state.with(status: :on, remaining_uses: state.remaining_uses - 1) end evolve Events::SwitchedOff do |state, event| state.with(status: :off) end evolve Events::Blew do |state, event| States::Blown.new end end end class TestDecide < Minitest::Test def decider = Bulb::Decider def given(events) @state = events.reduce(decider.initial_state, &decider.method(:evolve)) self end def when(command) @events = decider.decide(command, @state) self end def expect(expected) assert_equal(expected, @events) end def test_fit given( [] ).when( Bulb::Commands::Fit.new(max_uses: 5) ).expect( [Bulb::Events::Fitted.new(max_uses: 5)] ) end def test_switch_on given( [Bulb::Events::Fitted.new(max_uses: 5)] ).when( Bulb::Commands::SwitchOn.new ).expect( [Bulb::Events::SwitchedOn.new] ) end def test_switch_on_already_on given( [ Bulb::Events::Fitted.new(max_uses: 5), Bulb::Events::SwitchedOn.new ] ).when( Bulb::Commands::SwitchOn.new ).expect( [] ) end def test_switch_off given( [ Bulb::Events::Fitted.new(max_uses: 5), Bulb::Events::SwitchedOn.new ] ).when( Bulb::Commands::SwitchOff.new ).expect( [Bulb::Events::SwitchedOff.new] ) end def test_switch_off_already_off given( [ Bulb::Events::Fitted.new(max_uses: 5), Bulb::Events::SwitchedOn.new, Bulb::Events::SwitchedOff.new ] ).when( Bulb::Commands::SwitchOff.new ).expect( [] ) end def test_blew given( [ Bulb::Events::Fitted.new(max_uses: 1), Bulb::Events::SwitchedOn.new, Bulb::Events::SwitchedOff.new ] ).when( Bulb::Commands::SwitchOn.new ).expect( [Bulb::Events::Blew.new] ) end end