Devise `find_first_by_auth_conditions` method explanation - ruby-on-rails

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.

Related

Devise `find_first_by_auth_conditions` explanation [duplicate]

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

rspec assigns converts hash keys to string

I have a controller action where i am assigning a hash to an instance variable. In my rspec test file, i am using assigns to test it the instance variable is assigned to the value i expect. For some reason, assigns gives me a hash with string keys. If i print the instance variable in the controller, i has symbol keys
Please find the code below. It is simplified.
class TestController < ApplicationController
def test
#test_object = {:id => 1, :value => 2, :name => "name"}
end
end
My test file:
describe TestController do
it "should assign test_object" do
get :test
assigns(:test_object).should == {:id => 1, :value => 2, :name => "name"}
end
end
The above test fails with the error message
expected: {:id=>1, :value=>2, :name=>"name"}
got: {"id"=>1, "value"=>2, "name"=>"name"}
Please help me understand why it is doing that.
RSpec borrows assigns from the regular Rails test/unit helpers and it's using with_indifferent_access to return the requested instance variable as in assigns(:my_var).
Hash#with_indifferent_access returns a key-stringified version of the hash (a deep copy), which has the side effect of stringfiying the keys of instance variables that are hashes.
If you try to match the entire hash, it will fail, but it works if you are checking the values of specific keys, whether they're a symbol or a string.
Maybe an example will help clarify:
{:a => {:b => "bravo"}}.with_indifferent_access => {"a"=>{"b"=>"bravo"}}
{:a => {:b => "bravo"}}.with_indifferent_access[:a][:b] => "bravo"

case insensitive name, while preserving capitalization, in Devise

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.

RoR: attr_encrypted assignment doesn't get written?

I'm using attr_encrypted (v 1.2.0) in RoR 3.0.5 to encrypt credentials that I don't want appearing as plain text in my db. When I update the encrypted field, it appears that it's not getting saved into the db.
My model is essentially:
class Service << ActiveRecord::Base
attr_encrypted :credentials_aux, :key => KEY, :attribute => 'encrypted_credentials', :encode => true, :marshal => true
def credentials
credentials_aux
end
def credentials=(c)
h = {}.update(c) # coerce HashWithIndifferentAccess to a vanilla hash
credentials_aux = h
end
...
end
(Note that the 'credentials=' method exists simply to coerce a Rails-generated HashWithIndifferentAccess into a vanilla hash. It also gives me a place to interpose debugging printout to verify my data.)
But when I try updating credentials via the console, it doesn't take:
>> s = Service.find(19)
=> #<Service id: 19, encrypted_credentials: "10VfHU7IkdrFb4Q6Hj18YtY81rbRp3sIuoVUl8CHNj88cq1XFo2...",>
>> s.credentials
=> {"user_id"=>"fred.flintstone", "password"=>"supersecret"}
>> s.credentials = {"user_id" => "barney.rubble", "password" => "notsosecret"}
=> {"user_id" => "barney.rubble", "password" => "notsosecret"}
>> s.credentials
=> {"user_id"=>"fred.flintstone", "password"=>"supersecret"}
Why didn't s.credentials get updated to the new value?
I believe you're just setting a local variable called "credentials_aux" in your "credentials=" method, try explicitly using "self"
def credentials=(c)
h = {}.update(c) # coerce HashWithIndifferentAccess to a vanilla hash
self.credentials_aux = h
end
I'm actually the maintainer of that project, so if the fix above doesn't work then I'll open up a new ticket for this.

How can I pass multiple attributes to find_or_create_by in Rails 3?

I want to use find_or_create_by, but this statement does NOT work. It does not "find" or "create" with the other attributes.
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property.id, :value => d[descname])
There seems to be very little, or no, information on the use of dynamic finders in Rails 3. "and"-ing these together gives me a an unknown method error.
UPDATE:
Originally I couldn't get the following to work. Please assume I'm not an idiot and "product" is an instance of Product AR model.
product.product_properties.find_or_create_by_property_id_and_value(:property_id => 1, :value => "X")
The error methods was:
no such keys: property_id, value
I couldn't figure that out. Only this morning did I find the reference to passing the values like this instead:
product.product_properties.find_or_create_by_property_id_and_value(1, "X")
And voilá, it works fine. I would have expected a hash to work in the same situation but I guess not.
So I guess you get a down vote if you miss something on the internet?
If you want to search by multiple attributes, you can use "and" to append them. For example:
productproperty = ProductProperty.find_or_create_by_product_id_and_property_id_and_value(:product_id => product.id, :property_id => property.id, :value => d[descname])
There is one minor catch to be aware of. It will always return the object you've specified, even if that object can't be saved due to validation errors. So make sure you check to see if the returned object has an id (or is_valid?). Don't assume its in the database.
Alternatively, you can use the 'bang' version of the method to raise an error if the object cannot be saved:
http://guides.rubyonrails.org/active_record_querying.html#find-or-create-by-bang
This applies to Rails 3.
See http://api.rubyonrails.org/classes/ActiveRecord/Base.html:
With single query parameter:
productproperty = ProductProperty.find_or_create_by_product_id(product.id) { |u| u.property_id => property_id, u.value => d[descname] } )
or extended with multiple parameters:
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property_id, :value => d[descname]) { |u| u.property_id => property_id, u.value => d[descname] } )
Would work with:
conditions = { :product_id => product.id,
:property_id => property.id,
:value => d[descname] }
pp = ProductProperty.find(:first, :conditions => conditions) || ProductProperty.create(conditions)
In Rails 4, you can use find_or_create_by(attr1: 1, attr2: 2) to find or create by multiple attributes.
You can also do something like:
User.create_with(
password: 'secret',
password_confirmation: 'secret',
confirmation_date: DateTime.now
).find_or_create_by(
email: 'admin#domain.com',
admin: true
)
If you need to create the user with some attributes, but cannot search by those attributes.
You could also use where(...).first_or_create - ActiveRecord::Relation#first_or_create.
product_property_attrs = { product_id: product.id,
property_id: property.id,
value: d[descname] }
product_property = ProductProperty.where(product_property_attrs).first_or_create
I've found in Rails 3.1 you do not need to pass the attributes in as a hash. You just pass the values themselves.
ProductProperty.find_or_create_by_product_id_and_property_id_and_value(
product.id, property.id, d[descname])

Resources