Devise 3.5.2, Rails 4.2.3
While logging in, I'm trying to pass a hidden role_id along with the email/password combination. I am allowing the same email to register again, on a different subdomain, which causes a different role_id to be passed. The email+role_id is the unique index for the user.
I can create a user, but cannot log in. When I submit the log in form, I am faced with the following error:
undefined method 'email' for #<ActionDispatch::Request:0x007fa21628bda0>
If anyone can explain the process of changing the email uniqueness validation to email+role_id (not either/or, but and), that's all I need to accomplish. Following that process properly may avoid this error.
The POST parameters are as follows:
{"utf8"=>"✓",
"authenticity_token"=>"[FILTERED]",
"member"=>{"role_id"=>"1",
"email"=>"some.user@email.com",
"password"=>"[FILTERED]",
"remember_me"=>"0"},
"commit"=>"Log in"}
Here is my Member model:
class Member < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable, :omniauthable
belongs_to :role
def self.find_for_authentication(warden_conditions)
where(:email => warden_conditions[:email], :role_id => warden_conditions[:role_id]).first
end
end
In config/initializers/devise.rb , the following is set:
config.authentication_keys = [:email, :role_id]
config.request_keys = [:email, :role_id]
My views/devise/sessions/new.html.erb includes:
<%= f.hidden_field :role_id, :value => Role.find_by_name(current_subdomain).id %>
I adjusted vendor/bundle/ruby/1.9.1/gems/devise-3.5.2/lib/devise/models/validatable.rb by changing this line:
validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
to:
validates_uniqueness_of :email, :scope => :role_id, allow_blank: true, if: :email_changed? #using subdomains for validation
The relevant database migrations for the member are found here:
class DeviseCreateMembers < ActiveRecord::Migration
def change
create_table(:members) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :members, :email, unique: true
add_index :members, :reset_password_token, unique: true
add_index :members, :confirmation_token, unique: true
add_index :members, :unlock_token, unique: true
end
class AddColumnsToMember < ActiveRecord::Migration
def change
add_reference :members, :contact, index: true
add_reference :members, :role, index: true
add_reference :members, :ownership, index: true
add_column :members, :account_status, :string
end
end
class ReindexMembersEmailAndRole < ActiveRecord::Migration
def change
add_index :members, [:email, :role_id], :unique => true
end
end
The last item on the trace is:
vendor/bundle/ruby/1.9.1/gems/devise-3.5.2/lib/devise/strategies/authenticatable.rb:152:in `block in request_values'
keys = request_keys.respond_to?(:keys) ? request_keys.keys : request_keys
values = keys.map { |k| self.request.send(k) } <--ERROR THIS LINE
Hash[keys.zip(values)]
end
To fix this, I changed my config/initializers/devise.rb
to reflect the following:
config.request_keys = { role_id: false }
This fixed the issue, but still prevented the same email from signing up with a different role ID. To fix this, I removed :validatable
from my User model and added:
validates_uniqueness_of :email, :case_sensitive => false, :scope => :role_id, :allow_blank => true, :if => :email_changed?
validates_format_of :email, :with => Devise.email_regexp, :allow_blank => true, :if => :email_changed?
validates_presence_of :password, :on=>:create
validates_confirmation_of :password, :on=>:create
validates_length_of :password, :within => Devise.password_length, :allow_blank => true
This allows the same email address to sign up with a different role_id.
I also changed the following in authenticatable.rb:
def request_values
keys = request_keys.respond_to?(:keys) ? request_keys.keys : request_keys
values = keys.map { |k| self.request[self.scope][k] }
# values = keys.map { |k| self.request.send(k) }
Hash[keys.zip(values)]
end
UPDATE
I got tired of always having to re-hack the devise library, especially after I updated gems or transferred the app. I found this page that offered a better work-around (still follow the step regarding validations the User model listed above):
(From https://github.com/plataformatec/devise/pull/3965 )
Comment out the following line we edited above:
# config.request_keys = { role_id: false }
Edit the config.authentication_keys
line as follows:
config.authentication_keys = { email: true, role_id: false }
The issue is that request_keys
honors only predefined keys such as :subdomain
.
That should work now for creating a combination of custom keys to authenticate with.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.