11

Say I have the following in a Rails 4 app:

class Person < ActiveRecord::Base
  has_many :email_addresses, as: :emailable
  has_one :user_account
end

class EmailAddress < ActiveRecord::Base
  belongs_to :emailable, polymorphic: true
  # There is an :address column
end

class UserAccount < ActiveRecord::Base
  belongs_to :person
end

A person can have multiple email addresses. A person can also have a user account. (I've moved this into it's own model because not all people would be users.) Any of the person's email addresses could be used as the "username" when logging in.

Given this, I would like to use the Devise gem. I see you can specify the model to which the authentication is applied. User is widely used, but I would be using UserAccount. However, Devise then expects the email (username) field to be in this model.

When a person registers a user account, there would actually be three associated records created (Person, EmailAddress, and UserAccount). I can't figure out how to to get Devise to work with this setup. Any ideas?

Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
robertwbradford
  • 6,181
  • 9
  • 36
  • 61

2 Answers2

5

One option would be, to delegate the email method from your UserAccount to your email model and override the finder def self.find_first_by_auth_conditions(warden_conditions) used by the devise login procedure. I found a pretty nice blog post that describes this in depth and another stackoverflow answer that has the same approach. There is also a section in the docs about how to confirm a devise account with multiple emails.

As your setup is a bit more complicated, you could also maybe use EmailAddress as your primary devise model and delegate the password methods to the UserAccount.
This would potentially be useful if you have to confirm every email address with confirmable and not only the user account. This setup would you from overriding that finder, but you may run into other issues with the delegated password as is never tried that before.

Community
  • 1
  • 1
smallbutton
  • 3,377
  • 15
  • 27
2

If you are using ActiveRecord,

First, add

attr_accessor :email

to user_account (i think this is the easiest way to deal with devise form)

next, you need to modify devise login procedure. Still, in your user_account, override the devise method such

  def self.find_for_database_authentication(warden_conditions)
    conditions = warden_conditions.dup
    if email = conditions.delete(:email)
      where(conditions.to_h).includes(:email_addresses).where(email_addresses: {email: email}).first
    else
      where(conditions.to_h).first
    end
  end

you may also need to define following to get work the code above

class UserAccount < ActiveRecord::Base
  belongs_to :person
  has_many :email_addresses, through: :person

That should just work, i have tested this using active record but if you are using mongoid, probably the solution will be different.

Note that, i have modified the code from devise's How To: Allow users to sign in using their username or email address documentation to get the solution.

Alper Karapınar
  • 2,694
  • 1
  • 25
  • 36