devise_token_auth: User tokens don't properly deserialize

I am having trouble integrating devise_token_auth v0.1.31.beta9 into an existing Rails 4.2 app that already uses Devise.

I first ran the install. The user model was missing a few columns that devise_token_auth uses, so I edited down the generated migration to:

class DeviseTokenAuthCreateUsers < ActiveRecord::Migration
  def change
    add_column :users, :provider, :string, :null => false, :default => ""
    add_column :users, :uid, :string, :null => false, :default => ""
    add_column :users, :tokens, :text

    User.all.each do |user|
      user.provider = 'email'
      user.uid = user.email
      user.save!
    end

    add_index :users, [:uid, :provider],     :unique => true
  end
end

Attempting to perform the migration results in the following:

== 20150126183919 DeviseTokenAuthCreateUsers: migrating =======================
-- add_column(:users, :provider, :string, {:null=>false, :default=>""})
   -> 0.0218s
-- add_column(:users, :uid, :string, {:null=>false, :default=>""})
   -> 0.0164s
-- add_column(:users, :tokens, :text)
   -> 0.0012s
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

undefined method `delete_if' for "{}":String/usr/local/Cellar/rbenv/0.4.0/versions/2.2.0/lib/ruby/gems/2.2.0/gems/devise_token_auth-0.1.31.beta9/app/models/devise_token_auth/concerns/user.rb:238:in `destroy_expired_tokens'
/Users/sam/Code/goals/db/migrate/20150126183919_devise_token_auth_create_users.rb:11:in `block in change'
/Users/sam/Code/goals/db/migrate/20150126183919_devise_token_auth_create_users.rb:7:in `change'
-e:1:in `<main>'
NoMethodError: undefined method `delete_if' for "{}":String
/usr/local/Cellar/rbenv/0.4.0/versions/2.2.0/lib/ruby/gems/2.2.0/gems/devise_token_auth-0.1.31.beta9/app/models/devise_token_auth/concerns/user.rb:238:in `destroy_expired_tokens'
/Users/sam/Code/goals/db/migrate/20150126183919_devise_token_auth_create_users.rb:11:in `block in change'
/Users/sam/Code/goals/db/migrate/20150126183919_devise_token_auth_create_users.rb:7:in `change'
-e:1:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)

It seems that for some reason Rails isn’t deserializing the tokens JSON column into a Ruby hash. DeviseTokenAuth::Concerns::User is included in my user modal as it should be. It if makes a difference, I am running Postgres 9.4 as my database.

Any idea what could be causing this?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 18 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Just in case anyone else stumbles upon this issue, instead of overriding destroy_expired_tokens you can simply set user tokens to nil during the initial migration. The User model will then call the set_empty_token_hash callback defined in the provided model concern.

This was run on rails 4.2.4, devise_token_auth 0.1.34, mysql server 5.5.40. Here’s my modified migration:

class AddDeviseTokenAuthFieldsToUsers < ActiveRecord::Migration
  def change
    add_column :users, :provider, :string, null: false, default: "email"
    add_column :users, :uid, :string, null: false, default: ""
    add_column :users, :tokens, :text

    reversible do |direction|
      direction.up do
        User.find_each do |user|
          user.uid = user.email
          user.tokens = nil
          user.save!
        end
      end
    end

    add_index :users, [:uid, :provider], unique: true
  end
end

I dont believe that simply having a json column fixes this issue entirely. I had a tough time this afternoon trying to figure out how to get a seeded User to work with this gem. CLI generated User, and one created through the application (POST registrations…) work fine.

User.where(email: 'something@something.com').first_or_create do |user|
  some_attr = some_val
  tokens = nil # or an actual tokens val, any JSON object, any Hash, or any JSON-like String
end

The above will always result in some kind of String value that throws the error in the OP on login. I also tried overriding #destroy_expired_tokens, but that led to another error due to expectation of a JSON/Hash object. I found a way around that, but was not comfortable with 2 hacks (overrides…) to make this work.

Any form of one-line or block-based #create on my User, when run through ./bin/rails db:seed, will always result in some invalid value that throws an error on login (usually, the value is '{}', where it should be {}).

What does work for me, is to first create the User record, then reload it, set #tokens to nil, and then save again:

seeded_user = User.create(email: ...)

seeded_user.reload
seeded_user.tokens = nil
seeded_user.save

This actually sets #tokens to nil, which in turn allows the devise_token_auth internals to set it to {} correctly.