0

I'm trying to follow along with a lecture by implementing a gumball machine as a state machine. The intent is that it's initialized with 0 coins and 10 gumballs. By default, it's ready; you insert a coin, and from there either request_refund or turn_crank. Turning the crank will increment the coin counter by one while decrementing the coin counter (and returning a random gumball colour). If there are 0 gumballs left then turning the crank does nothing.

class GumballMachine
  attr_reader :coins, :gumballs

  def initialize
    @coins = 0
    @gumballs = 10
    super
  end

  def dispense_gumball
    @coins += 1
    @gumballs -= 1
    [:red, :green, :blue, :purple, :pink].sample
  end

  state_machine :state, initial: :ready do
    event :insert_coin { transition :ready => :holding_coin }

    event :request_refund { transition :holding_coin => :ready }

    event :turn_crank do
      dispense_gumball if @gumballs > 0
      transition :holding_coin => :ready
    end
  end
end

This fails, though, on the statement if @gumballs > 0, because @gumballs hasn't been defined at that point. I've read the documentation back and forth and Googled the hell out of it but I have zero idea what's wrong with this or what I should be doing instead.

GreenTriangle
  • 2,382
  • 2
  • 21
  • 35

2 Answers2

2

The instance variable isn't accessible within the state machine but as you made it attr_accessor you can access it by passing the gumball machine object.

Have a before_transition callback that does your dispensing.

before_transition holding_coin: :ready do |machine, transition|
  machine.dispense_gumball if machine.gumballs > 0
end

event :turn_crank do
  transition :holding_coin => :ready
end
SteveTurczyn
  • 36,057
  • 6
  • 41
  • 53
1

The context inside of an event block is not the same as the context inside a method definition. I'm guessing self refers to the event, not the GumballMachine when called in the event block.

The state_machine docs include an example that illustrates this:

event :crash do
  transition all - [:parked, :stalled] => :stalled, :if => lambda {|vehicle| !vehicle.passed_inspection?}
  end
end

In the example vehicle refers to the instance, and passed_inspection? is a method on the instance.

For your case, you might consider adding a method (e.g. has_gumball?) which then can access the instance variable.

zetetic
  • 47,184
  • 10
  • 111
  • 119