This question already has answers here:
Devise `find_first_by_auth_conditions` method explanation
(3 answers)
Closed 8 years ago.
I am having a tough time understanding this code from Devise, even though I've read the documentation and done some research.
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
signin = conditions.delete(:signin)
where(conditions).where(["lower(username) = :value OR lower(email) =
:value", {:value => signin.downcase }]).first
end
Please explain the components of this portion of the above method:
where(conditions).where(["lower(username) = :value OR lower(email) =
:value", {:value => signin.downcase }]).first
# arg is Hash, so assign to variable and downcase
x = warden_conditions[:signin].downcase
# create duplicate to preserve orig
c = warden_conditions.dup
# delete `:signin`
c.delete(:signin)
# if email, only search for email
if x =~ /^[a-zA-Z0-9._-]+#[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/
y = self.where(c).where(:email => x) # self is implied, but optional--so we use it here for clarity
# if not email, only search for name
else
y = self.where(c).where(:username => x)
end
# y is array, so should be only one AR obj or empty array considering they are unique
# `Array#first` will return `nil` or AR obj
return y.first
regex via:
validate email with regex jquery
The above code considers all previous records for columns email and username to be stored as lowercase as follows:
before_save :downcase_fields
def downcase_fields
self.email.downcase
self.username.downcase
end
Related
My two methods using Devise:
Method1
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
where(conditions).where(["lower(username) = :value OR lower(email)
= :value", {:value => signin.downcase }]).first
end
Method2
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:signin)
where(conditions).where(["lower(username) = :value OR lower(email) =
:value", {:value => login.strip.downcase }]).first
end
My questions:
What does this code perform/do? login = conditions.delete(:signin)
Without the above code I get an error undefined local variable or method signin
The following answers question 1)—specifically A) and B) below. The following code is an example and does not mirror the actual methods or arguments generated by Devise:
Here: the Hash contains :signin key-value pair and other valid ActiveRecord's #where syntax
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where
devise_conditions = {:signin => "cool#gmail.com", :deleted => false, :role => 'basic'}
#=> {:signin=>"cool#gmail.com", :deleted => false, :role => 'basic'}
This duplicates original argument to prevent modification in order to use it in subsequent methods or queries
http://ruby-doc.org/core-1.9.3/Object.html#method-i-dup
conditions = devise_conditions.dup
#=> {:signin=>"cool#gmail.com", :deleted => false, :role => 'basic'}
Here, the code: A) deletes the :signin key-pair from the Hash; and
B) assigns new variable signin with value of :signin key-pair from Hash
http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-delete
signin = conditions.delete(:signin)
#=> "cool#gmail.com"
The immediately above code could be rewritten to clarify both operations using additional "Element Reference" of Hash
http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-5B-5D
signin = conditions[:signin]
#=> "cool#gmail.com"
conditions.delete(:signin)
#=> "cool#gmail.com" # deleted value from Hash is returned
conditions
#=> {:deleted => false, :role => 'basic'}
The method's original argument has been preserved by using dup
devise_conditions
#=> {:signin=>"cool#gmail.com", :deleted => false, :role => 'basic'}
The following answers question 2):
Method1 does not create a variable signin. undefined local variable or method signin results from no signin variable being created when the code which creates it is removed.
Method2 creates a variable login which has the value from the original Hash named conditions with the key :signin.
This deletes signin key from conditions hash and assigns its value to login local variable.
2.. I guess you mean that signin is not defined in find_first_by_auth_conditions? Then I also guess that signin is an attribute of warden_conditions so you can try: warden_conditions.signin.
Using a name as key, how do we validate the name when registering by ignoring case while still remembering the case when displaying?
In config/initializers/devise.rb, setting config.case_insensitive_keys = [ :name ] seems to lowercase the entire name before registering.
Example: some dude names himself TheFourthMusketeer.
The views will display TheFourthMusketeer, not thefourthmusketeer
No new user can register under, say, tHEfourthMUSKETEER
What you might try is to not set :name as case insensitive, which will properly save the case-sensitive name in the database:
config.case_insensitive_keys = []
Then, override the find_first_by_auth_conditions class method on User to find the user by their name. Note that this code will vary depending on the database (below is using Postgres):
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where("lower(name) = ?", login.downcase).first
else
where(conditions).first
end
end
Doing this, a User.find_for_authentication(login: 'thefourthmusketeer') will properly return the record with a name of "TheFourthMusketeer".
See https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address for an explanation of overriding this method.
The accepted answer is incomplete because it's still case-sensitive on registration. So for example, 'username' and 'USERNAME' could both register successfully, but only the first would be able to login.
Disable case-insensitive keys in config/initializers/devise.rb (this can also be model-specific so check there too):
config.case_insensitive_keys = []
Overwrite the find_first_by_auth_conditions method of models/user.rb:
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:username)
where(conditions).where(["lower(username) = :value", { :value => login.downcase }]).first
else
where(conditions).first
end
end
...and also set validates_uniqueness_of in models/user.rb:
validates_uniqueness_of :username, :case_sensitive => false
So there you have it: case-insensitive authentication, with case-insensitive registration, that preserves case, in the database.
My Rails app uses data from legacy database. Imported users from this DB contain duplicate emails. Authentication doing with email+password (and it's a unique combination in DB).
Devise uses method find_for_database_authentication to find user. However params don't contain password (just login name).
What can I do?
try this :
find_user = User.where("email = ?", params["user"]["email"]).first
find_user.valid_password?(params["user"]["password"])
You can search this way
in the User model override the find method:
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
email = conditions.delete(:email)
pwd = conditions.delete(:password)
encrypted_pwd = User.new(password: pwd).encrypted_password
where(conditions).where(["lower(email) = :email AND encrypted_password = :pwd", { :email => email.strip.downcase, :pwd => encrypted_pwd }]).first
end
And probably config/initializers/devise.rb will require smth like:
config.authentication_keys = [ :email, :password ]
I want to use this function from mongoid:
person.update_attributes(first_name: "Jean", last_name: "Zorg")
But I want to pass in all the attributes from another variable. How do I do that?
Edit: Thanks everyone for your reply. I'm new to ruby so at first I thought I just made a silly mistake with this. The bug was in a completely different place, the correct code, for your enjoyment:
def twitter
# Scenarios:
# 1. Player is already signed in with his fb account:
# we link the accounts and update the information.
# 2. Player is new: we create the account.
# 3. Player is old: we update the player's information.
# login with a safe write.
puts "twitter"
twitter_details = {
twitter_name: env["omniauth.auth"]['user_info']['name'],
twitter_nick: env["omniauth.auth"]['user_info']['nickname'],
twitter_uid: env["omniauth.auth"]['uid']
}
if player_signed_in?
#player = Player.find(current_player['_id'])
else
#player = Player.first(conditions: {twitter_uid: env['omniauth.auth']['uid']})
end
if #player.nil?
#player = Player.create!(twitter_details)
else
#player.update_attributes(twitter_details)
end
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
sign_in_and_redirect #player, :event => :authentication
end
The update_attributes method takes a Hash argument so if you have a Hash, h, with just :first_name and :last_name keys then:
person.update_attributes(h)
If your Hash has more keys then you can use slice to pull out just the ones you want:
person.update_attributes(h.slice(:first_name, :last_name))
if you look at the source code of Mongoid, you'll see the definition of update_attributes in the file
.rvm/gems/ruby-1.9.2-p0/gems/mongoid-2.3.1/lib/mongoid/persistence.rb
# Update the document attributes in the datbase.
#
# #example Update the document's attributes
# document.update_attributes(:title => "Sir")
#
# #param [ Hash ] attributes The attributes to update.
#
# #return [ true, false ] True if validation passed, false if not.
def update_attributes(attributes = {})
write_attributes(attributes); save
end
It takes a Hash -- that means you can use a Hash as the variable that's passed in.
e.g.
my_attrs = {first_name: "Jean", last_name: "Zorg"}
person.update_attributes( my_attrs )
What's happening in the update_attributes method and, indeed, across the Rails platform is variables get put into a hash internally, when necessary.
So the following are equivalent:
person.update_attributes(first_name: "Jean", last_name: "Zorg")
person.update_attributes({first_name: "Jean", last_name: "Zorg"})
person.update_attributes(name_hash)
Where name_hash is:
name_hash = {first_name: "Jean", last_name: "Zorg"}
I'm currently using the following to parse emails:
def parse_emails(emails)
valid_emails, invalid_emails = [], []
unless emails.nil?
emails.split(/, ?/).each do |full_email|
unless full_email.blank?
if full_email.index(/\<.+\>/)
email = full_email.match(/\<.*\>/)[0].gsub(/[\<\>]/, "").strip
else
email = full_email.strip
end
email = email.delete("<").delete(">")
email_address = EmailVeracity::Address.new(email)
if email_address.valid?
valid_emails << email
else
invalid_emails << email
end
end
end
end
return valid_emails, invalid_emails
end
The problem I'm having is given an email like:
Bob Smith <bob#smith.com>
The code above is delete Bob Smith and only returning bob#smith.
But what I want is an hash of FNAME, LNAME, EMAIL. Where fname and lname are optional but email is not.
What type of ruby object would I use for that and how would I create such a record in the code above?
Thanks
I've coded so that it will work even if you have an entry like: John Bob Smith Doe <bob#smith.com>
It would retrieve:
{:email => "bob#smith.com", :fname => "John", :lname => "Bob Smith Doe" }
def parse_emails(emails)
valid_emails, invalid_emails = [], []
unless emails.nil?
emails.split(/, ?/).each do |full_email|
unless full_email.blank?
if index = full_email.index(/\<.+\>/)
email = full_email.match(/\<.*\>/)[0].gsub(/[\<\>]/, "").strip
name = full_email[0..index-1].split(" ")
fname = name.first
lname = name[1..name.size] * " "
else
email = full_email.strip
#your choice, what the string could be... only mail, only name?
end
email = email.delete("<").delete(">")
email_address = EmailVeracity::Address.new(email)
if email_address.valid?
valid_emails << { :email => email, :lname => lname, :fname => fname}
else
invalid_emails << { :email => email, :lname => lname, :fname => fname}
end
end
end
end
return valid_emails, invalid_emails
end
Here's a slightly different approach that works better for me. It grabs the name whether it is before or after the email address and whether or not the email address is in angle brackets.
I don't try to parse the first name out from the last name -- too problematic (e.g. "Mary Ann Smith" or Dr. Mary Smith"), but I do eliminate duplicate email addresses.
def parse_list(list)
r = Regexp.new('[a-z0-9\.\_\%\+\-]+#[a-z0-9\.\-]+\.[a-z]{2,4}', true)
valid_items, invalid_items = {}, []
## split the list on commas and/or newlines
list_items = list.split(/[,\n]+/)
list_items.each do |item|
if m = r.match(item)
## get the email address
email = m[0]
## get everything before the email address
before_str = item[0, m.begin(0)]
## get everything after the email address
after_str = item[m.end(0), item.length]
## enter the email as a valid_items hash key (eliminating dups)
## make the value of that key anything before the email if it contains
## any alphnumerics, stripping out any angle brackets
## and leading/trailing space
if /\w/ =~ before_str
valid_items[email] = before_str.gsub(/[\<\>\"]+/, '').strip
## if nothing before the email, make the value of that key anything after
##the email, stripping out any angle brackets and leading/trailing space
elsif /\w/ =~ after_str
valid_items[email] = after_str.gsub(/[\<\>\"]+/, '').strip
## if nothing after the email either,
## make the value of that key an empty string
else
valid_items[email] = ''
end
else
invalid_items << item.strip if item.strip.length > 0
end
end
[valid_items, invalid_items]
end
It returns a hash with valid email addresses as keys and the associated names as values. Any invalid items are returned in the invalid_items array.
See http://www.regular-expressions.info/email.html for an interesting discussion of email regexes.
I made a little gem out of this in case it might be useful to someone at https://github.com/victorgrey/email_addresses_parser
You can use rfc822 gem. It contains regular expression for seeking for emails that conform with RFC. You can easily extend it with parts for finding first and last name.
Along the lines of mspanc's answer, you can use the mail gem to do the basic email address parsing work for you, as answered here: https://stackoverflow.com/a/12187502/1019504