Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

With the state_machine gem, is there a way to make the events private/protected?

I am wondering if there's a way to make the state event private when using the state_machine gem?

I have a three states

unpaid, pending, paid.

When a receipt is in unpaid then an event can be fired to charge the user. This switches the receipt to pending (while it talks to the merchant service) Then once it is done, it would call the pay event and thus set the state to paid.

The user of the receipt class can technically call the pay event, which would switch the receipt to paid even though it didn't run through the merchant.

NOTE: THIS IS A CONTRIVED EXAMPLE...

I am a strong believer in private and protected methods, and I was wondering how one would engage them in the context of a state_machine implementation..

like image 519
baash05 Avatar asked Aug 18 '14 05:08

baash05


1 Answers

I'm assuming you're talking about this state_machine.

You can easily make your event transition methods private by marking them as so after their definition, e.g.

class Payment
  attr_reader :state

  state_machine :state, :initial => :pending do
    event :pay do
      transition [:pending] => :paid
    end
  end

  private :pay # that should do!
end

Even though it answers your question, I highly advise against it. Making a method private or protected, only concerns about method visibility, i.e. what you want to expose in your API. What you should really be looking for in your case, is a way to control access to a certain feature in a certain moment. This requirement is highly coupled with your domain logic, and not API.

Besides, even if you mark your methods as private, it does not guarantee any security because one can easily bypass this restriction calling the method via send, e.g payment.send(:pay).

I think a better solution would be to create some sort of policy checker or filter before your transactions to make sure they can be processed, e.g.

before_transition :pending => :paid, :do => :check_merchant

def check_merchant
  really_paid = ... # logic to check with the merchant
    or raise "Payment haven't been processed yet. Hang on a sec"
end

Hope that helps!

like image 58
wicz Avatar answered Oct 12 '22 23:10

wicz