wrong number of arguments (given 0, expected 1) when calling update_attributes - ruby-on-rails

I have a method that is called every time the user signs in to my app using facebook or gplus, where I update all the user fields on each sign in.
user = where(:email => email).first_or_create do |user|
user.uid = uid
user.email = email
user.provider = provider
user.save!
end
user.first_name = auth["first_name"]
user.last_name = auth["last_name"]
user.nickname = auth["first_name"]
user.name = auth["name"]
user.gender = auth["gender"]
user.role = "user"
user.latitude = 0
user.longitude = 0
user.update_attributes(user.attributes)
user
but i get the following error everytime this method is called, in a similar note, I get this error even when I try updating a single attribute
user.upate_attributes(:token => token)
wrong number of arguments (given 0, expected 1)
can't seem to figure out why

Why don't you just save the user instance
# ...
user.longitude = 0
user.save
or try this way
user.update_attributes(
first_name: auth["first_name"],
last_name: auth["last_name"],
nickname: auth["first_name"],
name: auth["name"],
gender: auth["gender"],
role: "user",
latitude: 0,
longitude: 0
)
Also, In your second command, there is a typo
user.upate_attributes(:token => token)
# Change to
user.update_attributes(:token => token)

Related

Instance variables in ActiveSupport::Concern

I have read that modules (in this case ActiveSupport::Concern) in ruby are shared amongst all instances of the class initialised. So if that were true it would mean any instance variables would be shared among all of the instances in memory.
module SetPassword
extend ActiveSupport::Concern
attr_reader :password
def password=(new_password)
#password = new_password
self.crypted_password = password_digest(new_password, salt)
end
end
class User
include SetPassword
end
u = User.new; u.password = 'abcdef'
u2 = User.new; u2.password = '123456'
Is the code above safe to use? Or would the second user override the first user?
Module#include is under the hood calling Module#append_features. That means, nothing is shared and this might be easily checked (ActiveSupport::Concern has nothing to do with the code you are to check.)
module SetPassword
attr_reader :password
def password=(new_password)
#password = new_password
puts <<~EOS
Setting password to: #{#password} (id: #{#password.__id__}).
Object: #{self.class.name} (#{self.__id__}).
Method: #{__callee__} on #{self.method(__callee__).__id__}.
EOS
end
end
class User
include SetPassword
end
u1 = User.new; u1.password = 'abcdef'
u2 = User.new; u2.password = '123456'
The code above demonstrates that everything (the password itself, the instance variable and even the method on it’s own) is different:
▶ u1 = User.new; u1.password = 'abcdef'
#⇒ Setting password to: abcdef (id: 46968741459360).
# Object: User (46968741459420).
# Method: password= on 46968741459040.
▶ u2 = User.new; u2.password = '123456'
#⇒ Setting password to: 123456 (id: 46968734231740).
# Object: User (46968734232020).
# Method: password= on 46968734230640.

first_or_initialize bug or what happened

I try to insert record but Active Record do same magick that i don't understand !?!?!?
My test code:
UserToken.where(:user_id=>2).first_or_initialize.tap do |user|
user.token = 'token',
user.type_id = 0,
user.user_id = 2
user.save!
end
Result:
UserToken Load (56.9ms) SELECT `user_tokens`.* FROM `user_tokens` WHERE `user_tokens`.`user_id` = 2 LIMIT 1
(56.4ms) BEGIN
(56.4ms) UPDATE `user_tokens` SET `type_id` = 0, `token` = '---\n- token\n- 0\n- 2\n', `updated_at` = '2013-06-27 20:19:22' WHERE `user_tokens`.`id` = 19
(56.3ms) COMMIT
=> #<UserToken id: 19, user_id: 2, token: ["token", 0, 2], type_id: 0, created_at: "2013-06-27 20:14:11", updated_at: "2013-06-27 20:19:22">
Why update token token = '---\n- token\n- 0\n- 2\n', token: ["token", 0, 2] i just try to record 'token' not array ?!?!?!?
you shouldn't have those commas there
UserToken.where(:user_id=>2).first_or_initialize.tap do |user|
user.token = 'token'
user.type_id = 0
user.user_id = 2
user.save!
end
or with semicolon line enders:
UserToken.where(:user_id=>2).first_or_initialize.tap do |user|
user.token = 'token';
user.type_id = 0;
user.user_id = 2;
user.save!;
end
The way you have it you're passing the other two assignments to user.token. What you did ends up like this because ruby expression always have a return value and variables always return themselves:
user.token = 'token', 0, 2

inject method to retrieve data from hash

I'm having trouble getting the method below in my user model to handle a hash ('auth') I'm getting from LinkedIn for user signin:
def self.deep_get auth, *fields
auth.inject(auth) { |acc, e| acc[e] if acc }
end
I call the 'deep_get' method later in my user model as I create a user using omniauth/linkedin gem. However, it's returning nil values for the provider/uid/headline/email user fields that I know are not nil.
I included first_name and last_name fields as an example because this approach is working (not returning nil values), but (as I realize) bad style/exception handling. Any ideas as to why my deep_get inject method isn't working to retrieve the data in the hash as I'd like it to?
def self.create_from_omniauth(auth)
create! do |user|
# i'd like to retrieve user information from linkedin per the following with my inject method, but i am getting nil values when i should be getting data.
# :provider and :uid are on the same branch level of data. first_name,last_name,email,etc. are on a branch just below called 'info'
user.provider = deep_get(auth, :provider)
user.uid = deep_get(auth, :uid)
user.headline = deep_get(auth, :info, :headline)
user.email = deep_get(auth, :info, :email)
# the below is working but i know pokemon exception handling is not good style.
begin
user.first_name = auth["info"]["first_name"]
rescue
end
begin
user.last_name = auth["info"]["last_name"]
rescue
end
try this
def deep_find(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=deep_find(a.last,key) }
r
end
end
or try this
class Hash
def deep_fetch(key, default = nil)
default = yield if block_given?
(deep_find(key) or default) or nil
end
def deep_find(key)
if key?(key)
self[key]
else
self.values.inject(nil) do |memo, v|
memo = v.deep_find(key) if v.respond_to?(:deep_find)
memo unless memo.nil?
end
end
end
end

generate unique username (omniauth + devise)

I have an app with user authentication with devise + omniauth.
In my model that username in my app is unique. I dont want duplicate username in my app.
Some users in facebook has not a defined username in his profile.
I want generate an unique username if the user has not username defined in facebook.
For example for generate password I have this:
:password => Devise.friendly_token[0,20]
How can I generate a unique username for my app if the facebook user has not username in facebook?
Thank you
You can create a nice readable username (eg generated from the first part of the email) and then ensure it is unique by adding numbers until it is. eg
#in User
def get_unique_login
login_part = self.email.split("#").first
new_login = login_part.dup
num = 2
while(User.find_by_login(new_login).count > 0)
new_login = "#{login_part}#{num}"
num += 1
end
new_login
end
One problem here is that someone could potentially bag that login inbetween you getting it and you saving it. So, maybe it's best to combine it into a before_create filter:
#in User
before_create :ensure_login_uniqueness
def ensure_login_uniqueness
if self.login.blank? || User.find_by_login(self.login).count > 0
login_part = self.email.split("#").first
new_login = login_part.dup
num = 2
while(User.find_by_login(new_login).count > 0)
new_login = "#{login_part}#{num}"
num += 1
end
self.login = new_login
end
end
You can take a part of email before the # sign and add there smth like user_id, or just take the email itself. Or you can combine somehow the first and last names from the fb response.
Here is how i created Login with combination of first name and last name field.. Improvements on this code is welcome.
before_create :ensure_login_uniqueness
def ensure_login_uniqueness
if self.login.blank?
self.name = self.first_name + " " + self.last_name
firstnamePart = self.first_name.downcase.strip.gsub(' ', '').gsub(/[^\w-]/, '')
lastnamePart = self.last_name.downcase.strip.gsub(' ', '').gsub(/[^\w-]/, '')
login_part = firstnamePart+lastnamePart
new_login = login_part.dup
num = 1
while(User.where(:login => new_login).count > 0)
new_login = "#{login_part}#{num}"
num += 1
end
self.login = new_login
end
end
It did not work for me,but change:
while(User.find_by_login(new_login).count > 0)
to
while(User.where(login: new_login).count > 0)
Here's my methods that I use for Facebook
def ensure_username_uniqueness
self.username ||= self.email.split("#").first
num = 2
until(User.find_by(username: self.username).nil?)
self.username = "#{username_part}#{num}"
num += 1
end
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.username = auth.info.name.downcase.gsub(" ", "")
user.username = user.username[0..29] if user.username.length > 30
user.ensure_username_uniqueness
end
end
Here is the way how you can generate "username" from "full name"
def self.generate_username(full_name)
generated = ActiveSupport::Inflector.transliterate(full_name) # change ñ => n
.downcase # only lower case
.strip # remove spaces around the string
.gsub(/[^a-z]/, '_') # any character that is not a letter or a number will be _
.gsub(/\A_+/, '') # remove underscores at the beginning
.gsub(/_+\Z/, '') # remove underscores at the end
.gsub(/_+/, '_')
taken_usernames = User
.where("username LIKE ?", "#{generated}%")
.pluck(:username)
# username if it's free
return generated unless taken_usernames.include?(generated)
count = 2
while true
# username_2, username_3...
new_username = "#{generated}_#{count}"
return new_username if ! taken_usernames.include?(new_username)
count += 1
end
end

semicolon as statement separator in Rails Console

The Rails console doesn't seem to like multiple ruby statements on the same line separated by a semicolon. Whenever I do this, the next line starts with ?> and I find that only the first statement was executed. Do you have to put each statement on a separate line?
>> user = User.new
user = User.new
=> #<User id: nil, username: "", hashed_password: "", first_name: "", last_name: "", email: "", display_name: "", user_level: 0, created_at: nil, updated_at: nil, posts_count: 0>
>> user.username = "John"; hashed_password = "John"; first_name = "John"; last_name = "coltrane"; email = "John#coltrane.com"; display_name = "Johndispay"; user_level = 9;
user.username = "John"; hashed_password = "John"; first_name = "John"; last_name = "coltrane"; email = "John#coltrane.com"; display_name = "Johndispay"; user_level = 9;
?> user.save
user.save
=> true
Everything except user.username = "John"; was ignored
You need to say "user." so Ruby knows you mean to call the attribute assignment methods of the instance of user. Otherwise, you are just setting local variables called "hashed_password", etc.
>> user.username = "John"; user.hashed_password = "John"; user.first_name = "John"; user.last_name = "coltrane"; user.email = "John#coltrane.com"; user.display_name = "Johndispay"; user.user_level = 9;
Although, you could just pass a hash of the attributes you want to set on the new instance, like so
>> user = User.new(:username => "John", :hashed_password => "John", ...
It's the trailing ; on your input. When you put a ';' on the end IRB will assume you want to add another statement. If you leave it off, it will evaluate all the statements and return the return value of the last one.
Sometimes if the method I'm calling is going to return a large array I will do something like this...
a = Account.entries; a.size
This will save the values I need and just output the size of the array instead of trying to dump it to the console, which can take a long time if it's large.
are you sure you didn't mean
user.username = "John"; user.hashed_password = "John";
i tried
>> a = 1; b= 2
=> 2
>> a
=> 1
>> b
=> 2
when something doesn't work, you can use one rule: always reduce it to the simplest case.

Resources