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?
Our out-of-the box Devise setup is now working with Rails 7. Once again, if you'd like to refer to any of the code for this setup, or use the template wholesale for a new app, the code is available on GitHub, and you may also use it as a template repo to kick off your own Rails 7 devise projects.
Resource is an abstraction name of instance of a user. It can be configured in devise settings to work with Admin model or any other. By default it's the first devise role declared in your routes devise :users # resource is instance of User class devise :admins # resource is instance of Admin class.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With