Rails state_machine transition causes password validation error

264 views Asked by At

I've been following Michael Hartl's rails tutorial and have been tackling the extension he suggests in Signup confirmation to get users to click on an emailed link to activate their account. To manage the activation I'm using the state_machine gem but when I try to activate a user account I get an error "Cannot transition state via :activate from :pending (Reason(s): Password is too short (minimum is 6 characters))". To me it seems as if the database operation which state_machine is implementing to update the state field is somehow causing validation associated with the user record (possibly associated with the has_secure_password method) to be triggered and to fail. I did come across another stackoverflow question here on the topic a year ago, but reading the comments on that question there didn't seem to be a resolution.

My user model (user.rb) code is below (it's basically the tutorial code in listing 6.29 plus the addition of a state machine). [Sorry, can't provide a link as stackoverflow is not allowing me more than 2 links!]

class User < ActiveRecord::Base
  validates :name, presence: true, length: { maximum: 50 } 
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, 
    uniqueness: { case_sensitive: false }
  before_save { email.downcase! }
  validates :password, length: { minimum: 6, maximum: 50 }
  has_secure_password
  before_create :create_remember_token

  state_machine initial: :pending do
    state :pending, value: 0
    state :active, value: 1

    event :activate do
      transition :pending => :active
    end

    event :deactivate do
      transition :active => :pending
    end 
  end

  def initialize
    super()
  end

My database user table structure is:

create_table "users", force: true do |t|
t.string   "name"
t.string   "email"
t.datetime "created_at"
t.datetime "updated_at"
t.string   "password_digest"
t.string   "remember_token"
t.boolean  "admin",           default: false
t.string   "token"
t.integer  "state"
end

In the rails console the state_machine code seems to be interpreting the values in the database ok, because I can get a user record and when I do user.can_activate? and user.can_deactivate? it's returning me what I expect. But when I try to do user.activate! or user.deactivate! I get the password too short error. Any suggestions as to how to address this?

1

There are 1 answers

0
Robbie Jackson On BEST ANSWER

I found the answer to this through changing the code to dispense with state_machine and update the state field in the database directly. I then discovered that all updates of the user record were causing the password validation to trigger and the password length error to appear (hence not a problem with state_machine). As described here the solution is to prevent the password validation from occurring if the password is not one of the attributes being updated by changing the "validates" line above to:

validates :password, length: { minimum: 6, maximum: 50 }, :if => :validate_password?
def validate_password?
  password.present? || password_confirmation.present?
end

Once that is changed the state_machine functionality works fine.