hadees
hadees

Reputation: 1764

Using state machines with a lot of transaction conditionals

I am writing an e-commerce platform on rails 4 similar to kickstarter or indiegogo. What state a product is in is highly dependent on various conditions like if there are enough orders. So for example if I were to use the gem state_machine my code might look something like this.

class Product  < ActiveRecord::Base
  has_many :orders

  state_machine :initial => :prelaunch do
    event :launch do
      transition :prelaunch => :pending, :if => lambda {|p| p.launch_at <= Time.now }
    end

    event :fund do
      transition :pending => :funded, :if => :has_enough_orders?
    end
  end

  def has_enough_orders?
    if orders.count > 10
  end
end

Then I would probably create a model observer so that every time an order is placed I check product.has_enough_orders? and if that returns true I would call product.fund!. So has_enough_orders? is being checked multiple times. That just doesn't seem very efficient.

Additionally product.launch! has a similar issue. The best way I can think to implement it is to use something like sidekiq and have a job that checks if any pre-launched products are passed their launch_at time. However this seems equally dirty.

Am I just over thinking it or is this how you normally would use a state machine?

Upvotes: 1

Views: 1467

Answers (1)

TM.
TM.

Reputation: 3741

I just modified your state machine to better way to handle conditions.

You can use after_transition or before_transition methods

class Product < ActiveRecord::Base
  has_many :orders

  state_machine :initial => :prelaunch do
    after_transition :prelaunch, :do => :check_launch
    after_transition :pending, :do => :has_enough_orders?

    event :launch do
      transition :prelaunch => :pending
    end

    event :fund do
      transition :pending => :funded
    end
  end

  def check_launch
    if launch_at <= Time.now
      self.launch # call event :launch
    else
      # whatever you want
    end
  end

  def has_enough_orders?
    if orders.count > 10
      self.fund # call event :fund
    else
      # whatever you want
    end
  end
end

Upvotes: 5

Related Questions