acts_as_state_machine and variable initial states

acts_as_state_machine is a great plugin really useful when you want to add constraints and behavior to your model objects.
Note : Those who don’t know what this plugin is all about should stop reading right there or risk being completely lost.
One thing that seems impossible to do with the plugin is to have variable “initial states” for an object. Say for example that you have a User model that needs to act as a state machine. New users have to fill up a signup form to gain access to the application. These new users will be “pending” until an administrator approve them (enabled) or refuse them (disabled)

class User < ActiveRecord::Base
  acts_as_state_machine :initial => :pending
  state :pending
  state :enabled
  state :disabled
  event :enable do
    transitions :from => :pending, :to => :enabled
    transitions :from => :disabled, :to => :enabled
  end
  event :disable do
    transitions :from => :pending, :to => :disabled
    transitions :from => :enabled, :to => :disabled
  end
end

So far so good. But what if an administrator can also create a new user? In this case we would’nt want the user to be “pending”, we want him to be “enabled” right away.
I experimented a similar scenario, so I tried a few things to bypass the initial state :

#FAILED ATTEMPT #1
def create
  user = User.new(params[:user])
  user.state = :enabled
  user.save
end

Too bad, so I tried this :

#FAILED ATTEMPT #2
def create
  User.initial_state = :enabled
  user = User.new(params[:user])
  user.save
end

Of course, the following worked :

#SUCCESSFUL BUT SUCKY ATTEMPT
def create
  user = User.new(params[:user])
  user.save
  user.enable!
end

Yuk… there had to be something better.
Finally, I learned that initial_state was an inheritable attribute. I didn’t know what those were all about so I did some googling. The only good explanation I found is this one.
Anyway, I was able to override the initial state of my user object by doing this :

#SUCCESSFUL ATTEMPT!
def create
  User.write_inheritable_attribute :initial_state, :enabled
  user = User.new(params[:user])
  user.save
end

It’s not pretty I know, but at least it gets the job done. I wonder if the fact that you cannot “easily” change the initial state programmatically is a missing feature that should be added in a future version… or if it’s only my understanding of a “state machine” that sucks. Any thoughts?

6 thoughts on “acts_as_state_machine and variable initial states

  1. Hey Frank,
    I think the best way is:
    def create
    user = User.new(params[:user])
    user.enable!
    end
    enable! calls save, no hacks and pretty explicit.
    Changing a class attribute like this is not good practice and would prevent your app from being multi-thread amongst other things.

  2. Marc, thanks for your input. Feel a bit silly now 😉
    However I still wonder why it isn’t possible to bypass the initial state specified in the model. I’d prefer if I could just send the "state" attribute among the rest like that : User.new(:name => "blabla", :state => "enabled")

  3. Hmm… interesting. I started experiencing some weird deadlock errors as soon as I tried what you said Marc. I am too lazy to check the plugin code but I would guess it does something special with the “!” events. Or maybe it’s something completely unrelated… still checking the issue.

  4. Greetings,
    I believe the reason you can’t do that is that you’d be jumping to a middle state of a state machine. Essentially you’d be bypassing any code that was set up to happen during transitions.
    The system needs to trigger when you transition from (for example) :free to :paid, so it can provision you with the appropriate features.
    In order to trigger on transition between states, you have to have a consistent starting state.
    Think of it as stateful integrity, a term that has only two Google hits, so I consider myself to have made it up just now, for just this purpose. 🙂
    — Morgan

  5. Cyberfox,
    good call, it makes sense. So basically I should redesign my “user state machine” to have just 1 initial state.
    Maybe have the following states :
    All new users could be : new (initial)
    Unapproved users : unapproved
    Enabled users : enabled
    Disabled users : disabled
    And the events :
    approve! (from :unapproved to :enabled)
    enable! (from :new to :enabled)
    disable! (from :enabled to :disabled and from :unapproved to :disabled)
    I guess that would be better than having more than 1 initial states. Any thoughts?

  6. Hello Frank,
    I am bit confused
    Isn’t it good way to use
    “User.write_inheritable_attribute :initial_state, :enabled”
    Because I want to assign the specific state (e.g. :xyz) initially only in few cases. And in other cases when i migrate from any other state to :xyz and have to perform some events which are not required in first case.
    Therefore I cannot use
    User.xyz! as this performs all the event actions when I do this.
    So can anyone suggest me a good way to do this.
    “User.write_inheritable_attribute :initial_state, :xyz” serves my purpose but as someone has already said its not a good practice.

Leave a Reply

Your email address will not be published. Required fields are marked *