Passing variables to Rails StateMachine gem transi

2020-05-25 07:42发布

问题:

Is it possible to send variables in the the transition? i.e.

@car.crash!(:crashed_by => current_user)

I have callbacks in my model but I need to send them the user who instigated the transition

after_crash do |car, transition|
  # Log the car crashers name
end

I can't access current_user because I'm in the Model and not the Controller/View.

And before you say it... I know I know.

Don't try to access session variables in the model

I get it.

However, whenever you wish to create a callback that logs or audits something then it's quite likely you're going to want to know who caused it? Ordinarily I'd have something in my controller that did something like...

@foo.some_method(current_user)

and my Foo model would be expecting some user to instigate some_method but how do I do this with a transition with the StateMachine gem?

回答1:

If you are referring to the state_machine gem - https://github.com/pluginaweek/state_machine - then it supports arguments to events

after_crash do |car, transition|
  Log.crash(:car => car, :driver => transition.args.first)
end


回答2:

I was having trouble with all of the other answers, and then I found that you can simply override the event in the class.

class Car
  state_machine do
    ...
    event :crash do
      transition any => :crashed
    end
  end
  def crash(current_driver)
    logger.debug(current_driver)
    super
  end
end

Just make sure to call "super" in your custom method



回答3:

I don't think you can pass params to events with that gem, so maybe you could try storing the current_user on @car (temporarily) so that your audit callback can access it.

In controller

@car.driver = current_user

In callback

after_crash do |car, transition|
   create_audit_log car.driver, transition
end

Or something along those lines.. :)



回答4:

I used transactions, instead of updating the object and changing the state in one call. For example, in update action,

ActiveRecord::Base.transaction do
  if @car.update_attribute!(:crashed_by => current_user)
    if @car.crash!()
      format.html { redirect_to @car }
    else
      raise ActiveRecord::Rollback
  else
    raise ActiveRecord::Rollback
  end
end


回答5:

Another common pattern (see the state_machine docs) that saves you from having to pass variables between the controller and model is to dynamically define a state-checking method within the callback method. This wouldn't be very elegant in the example given above, but might be preferable in cases where the model needs to handle the same variable(s) in different states. For example, if you have 'crashed', 'stolen', and 'borrowed' states in your Car model, all of which can be associated with a responsible Person, you could have:

state :crashed, :stolen, :borrowed do
  def blameable?
    true
  end

state all - [:crashed, :stolen, :borrowed] do
  def blameable?
    false
  end

Then in the controller, you can do something like:

car.blame_person(person) if car.blameable?