pluginaweek/state_machine

Undefined method `state`

Opened this issue · 5 comments

We are having an issue when a model (User) is occasionally failing to save because state_machine validations are being triggered for it even though the model does not define a state machine. I've tried to find how it could be happening and am completely stumped since the state machine callbacks are only set up for objects with a state machine and so the lines that show in the backtrace should never be invoked for a User. The fact that it happens intermittently and so far only in production seems to imply it's something strange with the object instance. It happens on both rails 3 & 4, and seems to have surfaced when we moved to ruby 2. Here is an example backtrace:

NoMethodError: undefined method `state' for #<User:0x000000088beb58>
…ctivemodel-4.0.2/lib/active_model/attribute_methods.rb: 439:in `method_missing'
…0/gems/activemodel-4.0.2/lib/active_model/validator.rb: 151:in `block in validate'
…0/gems/activemodel-4.0.2/lib/active_model/validator.rb: 150:in `each'
…0/gems/activemodel-4.0.2/lib/active_model/validator.rb: 150:in `validate'
…gems/activemodel-4.0.2/lib/active_model/validations.rb: 373:in `run_validations!'
…emodel-4.0.2/lib/active_model/validations/callbacks.rb: 106:in `block in run_validations!'
…e-1.2.0/lib/state_machine/integrations/active_model.rb: 514:in `block in around_validation'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 150:in `block in run_actions'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 170:in `catch_exceptions'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 148:in `run_actions'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 133:in `run_callbacks'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 212:in `run_callbacks'
…chine-1.2.0/lib/state_machine/transition_collection.rb:  63:in `block (2 levels) in perform'
…chine-1.2.0/lib/state_machine/transition_collection.rb:  63:in `catch'
…chine-1.2.0/lib/state_machine/transition_collection.rb:  63:in `block in perform'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 186:in `within_transaction'
…chine-1.2.0/lib/state_machine/transition_collection.rb:  62:in `perform'
…e-1.2.0/lib/state_machine/integrations/active_model.rb: 514:in `around_validation'
…emodel-4.0.2/lib/active_model/validations/callbacks.rb: 106:in `run_validations!'
…gems/activemodel-4.0.2/lib/active_model/validations.rb: 314:in `valid?'
…-1.2.0/lib/state_machine/integrations/active_record.rb: 483:in `block in save'
…-1.2.0/lib/state_machine/integrations/active_record.rb: 502:in `block (2 levels) in around_save'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 150:in `block in run_actions'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 170:in `catch_exceptions'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 148:in `run_actions'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 133:in `run_callbacks'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 212:in `run_callbacks'
…chine-1.2.0/lib/state_machine/transition_collection.rb:  63:in `block (2 levels) in perform'
…chine-1.2.0/lib/state_machine/transition_collection.rb:  63:in `catch'
…chine-1.2.0/lib/state_machine/transition_collection.rb:  63:in `block in perform'
…chine-1.2.0/lib/state_machine/transition_collection.rb: 186:in `within_transaction'
…chine-1.2.0/lib/state_machine/transition_collection.rb:  62:in `perform'
…-1.2.0/lib/state_machine/integrations/active_record.rb: 502:in `block in around_save'
…-1.2.0/lib/state_machine/integrations/active_record.rb: 530:in `block in transaction'
…-1.2.0/lib/state_machine/integrations/active_record.rb: 529:in `transaction'
…-1.2.0/lib/state_machine/integrations/active_record.rb: 501:in `around_save'
…-1.2.0/lib/state_machine/integrations/active_record.rb: 483:in `save'
…1219211816/app/controllers/user_profiles_controller.rb:  38:in `update'

Can you create another application with the issue so we can test it?

Yes I can try to do that while I'm working on trying to reproduce it. Am I right in my assumption that the around_save method shouldn't even be in the backtrace of a class that does not have a state_machine defined? I'm trying to make sure that I'm barking up the right tree in looking into state machine. Thanks.

Tracked it down! Had some metaprogramming that was inadvertently adding a state_machine dynamically:

model.auditable.class.state_machine(:state)

When that code was invoked it would add a state_machine to the auditable class, in this case User. I don't know if it's intended behavior that you can dynamically add a state machine to any class via the StateMachine::MacroMethods.state_machine method. If that is intended then nothing much that can be done, if it's not then you could consider making StateMachine::MacroMethods.state_machine private. e.g.:

    private
    def state_machine(*args, &block)
      StateMachine::Machine.find_or_create(self, *args, &block)
    end

And from the console:

[2] pry(main)> User.state_machine
NoMethodError: private method `state_machine' called for #<Class:0x007f8c7d563810>

I'll leave this open in case you want to change, but feel free to close if it's intended behavior.

instead of doing model.auditable.class.state_machine(:state) you can do

clazz = model.auditable.class
machine = clazz.state_machines[:state] if clazz < StateMachine::InstanceMethods 

Thabks, that's more reliable than the responds_to check I was using.

On Friday, December 27, 2013, the8472 wrote:

instead of doing model.auditable.class.state_machine(:state) you can do

clazz = model.auditable.classmachine = clazz.state_machines[:state] if clazz < StateMachine::InstanceMethods


Reply to this email directly or view it on GitHubhttps://github.com//issues/296#issuecomment-31290693
.