Method not detected in model " undefined method `process_uri' " - ruby-on-rails

Im trying to figure out why i cant get my method to be identified. Maybe im not seeing things clearly and made a little mistake that someone can catch? Here is my code and when i click on the button i get this error.
User Model: when if auth.info.image.present? gets passed the process_uri gets called but my method isn't identified.
def self.from_omniauth(auth)
anonymous_username = "NewUser#{User.last.id + 1}"
generated_password = Devise.friendly_token[0,20]
user = User.where(:email => auth.info.email, :username => anonymous_username).first
if user
return user
else
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.confirmed_at = Time.now
user.fullname = auth.info.name
user.provider = auth.provider
user.uid = auth.uid
user.username = anonymous_username
user.email = auth.info.email
user.password = generated_password
end
if auth.info.image.present?
avatar_url = process_uri(auth.info.image)
user.update_attribute(:avatar, URI.parse(avatar_url))
end
end
end
private
def process_uri(uri)
require 'open-uri'
require 'open_uri_redirections'
open(uri, :allow_redirections => :safe) do |r|
r.base_uri.to_s
end
end
As you can see I have a private method underneath def process_uri(uri) .. even when i take out private this still isn't being noticed... thank you!!

The problem here is that the private method process_uri is an instance method, whereas from_omniauth is a class method. In the context of a class method, the self object that the method would be called on is the class, not the instance, so you get an undefined method error because there is no class method process_uri. You can either define process_uri as a class method, or you can make it public and call it on the object itself (e.g. user.process_uri).

Related

NoMethodError undefined method `persisted?' for nil:NilClass when signing in with Facebook thru Omniauth/Devise?

I am putting the finishing touches on a Rails app, and everything was working fine just yesterday, (most of it still is). But now, when I try to sign in with Facebook I get the error NoMethodError undefined method persisted? for nil:NilClass I haven't changed any code in the omniauth_callbacks_controller, or in the User Model, so I do not understand why this is happening all of the sudden.
Here's the code in omniauth_callbacks_controller
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
#user = User.from_omniauth(env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:twitter, :facebook].each do |provider|
provides_callback_for provider
end
And in User.rb
def self.from_omniauth(auth)
identity = Identity.where(provider: auth.provider, uid: auth.uid).first_or_create do |identity|
if identity.user == nil
user = User.new
user.email = auth.info.email || "#{auth.uid}##{auth.provider}.generated"
user.password = Devise.friendly_token[0,20]
user.provider = auth.provider
if auth.provider == "facebook"
user.name = auth.info.name
user.username = "FacebookUser" + (1 + rand(1000)).to_s
elsif auth.provider == "twitter"
user.name = auth.info.name
user.username = auth.info.nickname
end
end
identity.user = user
end
identity.access_token = auth['credentials']['token']
identity.refresh_token = auth['credentials']['refresh_token']
identity.expires_at = auth['credentials']['expires_at']
identity.timezone = auth['info']['timezone']
identity.save
identity.user
end
I cannot figure out why it WAS working yesterday, but now it's not. Nothing has changed! I'm still accessing it from the same URL, I haven't messed with the API settings on Facebook, or in devise.rb.
the problem is that identity.user is nill. It could happen, because user not valid(not saved in db), in user model did not pass validations.
I think problem can be in user.email = auth.info.email || "#{auth.uid}##{auth.provider}.generated" - wrong email type. You can add something like puts user.errors in block wehe you created user.
Also i don't understand why you need a model - identity. Much easier add field to user model
Here is my code(it's work):
def self.from_omniauth(auth)
email = auth.info.email
user = User.find_by(email: email) if email
user ||= User.create do |user|
user.provider= auth.provider
user.uid= auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.name = auth.info.name
user.email ||= "#{auth.uid}_#{auth.uid}#email.com"
end
end

Gender capture the User with omniauth - facebook

I'm with a doubt here. I am using the gem ' omniauth - facebook ' and could bring up on the screen the name of the User , photo and email. I would like to get new data as gender for example. Dai created a migration add the field to the users table , put the method in view , all right. However I can not figure out how to write the method name and is giving indisponivel method . Does anyone have any ideas? I know that the error is in the method name but not know how to call it. Thanks.
Error Stack Trace
Erb Code Block
# app/models/user.rb
class User < ActiveRecord::Base
def self.omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create
do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.image = auth.info.image
user.email = auth.info.email
user.public_profile.gender = auth.public_profile.gender
user.token = auth.credentials.token
user.expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
end
It seems like you've created your model based on a tutorial and that tutorial is out of date for facebooks current API, which you can see here
Facebook Auth Hash, complete
So in this, you're looking for gender in under info, but it's under extra->raw_info - meaning your code would look more like:
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.image = auth.info.image
user.email = auth.info.email
user.public_profile.gender = auth.extra.raw_info.gender
user.token = auth.credentials.token
user.expires_at = Time.at(auth.credentials.expires_at)
user.save!

Why User.count inside this block is always zero?

Only the first created user can be an admin.
I was expecting to achieve that with user.admin = User.count == 0 ? true : false inside the self.from_omniauth(auth) method, but for some reason, it doesn't matter how many users I have, admin is always set to true.
To be sure there are no users:
from rails console
User.destroy_all
User.count
=> 0
user.rb
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.email = auth.info.email
user.image = auth.info.image
user.name = auth.info.name
user.nickname = auth.info.nickname
user.admin = User.administrator?
end
end
def self.administrator?
count == 0
end
schema.rb
create_table "users", force: true do |t|
...
t.boolean "admin", default: false
end
Thanks!
Now it's working in the expected way, but the initial question is still unanswered.
Even when I don't really like this solution, at least is working now.
Here I'm creating this user_qty variable outside the block.
def self.from_omniauth(auth)
user_qty = User.count
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.password = Devise.friendly_token[0,20]
user.email = auth.info.email
user.image = auth.info.image
user.name = auth.info.name
user.nickname = auth.info.nickname
user.admin = user_qty == 0
end
end
The code inside of the first_or_create block executes within the current scope. In ActiveRecord::Relation, the first_or_create method calls the create method which uses scoping:
def create(*args, &block)
scoping { #klass.create(*args, &block) }
end
And the scoping method says to run within the current_scope.
A call to User.count within this block is like calling:
User.where(provider: auth.provider, uid: auth.uid).count
If you debug with:
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
puts "current_scope: #{current_scope}"
You should see something like:
current_scope: SELECT "users".* FROM "users" WHERE "users"."provider" = '<value1>' AND "users"."uid" = '<value2>'
If you wanted to ask for the full count of all users within the first_or_create block you can do something like:
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
...
user.admin = User.unscoped.count == 0 ? true : false

Rails omniauth not refreshing data

I'm using the omniauth-twitter gem to authenticate users and to fill in names, avatars, etc. with this in my User.rb file
def self.from_omniauth(auth)
where(auth.slice("provider", "uid")).first || create_from_omniauth(auth)
end
def self.create_from_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
user.nickname = auth["info"]["nickname"]
user.location = auth["info"]["location"]
user.image = auth["info"]["image"].sub("_normal", "")
user.description = auth["info"]["description"]
end
end
end
Works great, except I happened to change my avatar in Twitter and noticed that the data never changes even after I log out and reauthorize. It would be nice if data like location, image, description got refreshed each time a user logged in.
Well, the workings of that logic are up to you. Here's an example of a possible solution:
def self.from_omniauth(auth)
user = find_by(auth.slice(:provider, :uid)) || initialize_from_omniauth(auth) # Rails 4
user = where(auth.slice(:provider, :uid)).first || initialize_from_omniauth(auth) # Rails 3
user.update_dynamic_attributes(auth)
end
def self.initialize_from_omniauth(auth)
new do |user|
user.provider = auth[:provider]
user.uid = auth[:uid]
user.name = auth[:info][:name]
end
end
def update_dynamic_attributes(auth)
self.location = auth[:info][:location]
self.image = auth[:info][:image]
self.description = auth[:info][:description]
save!
self
end
Also, you don't have to do this:
auth["info"]["image"].sub("_normal", "")
As the omniauth-twitter gem can already do that for you if you use the image_size option:
OmniAuth::Builder do
provider :twitter, ENV["TWITTER_KEY"], ENV["TWITTER_SECRET"], {
:image_size => 'original'
}
end

connect with facebook for existing users

I am using omniauth-facebook gem with devise to authenticate with Facebook in my rails application in my user model
def self.from_omniauth(auth)
# immediately get 60 day auth token
oauth = Koala::Facebook::OAuth.new("App Key", "App secrets" )
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
begin
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.username = auth.info.first_name
user.lastname =auth.info.last_name
user.email =auth.info.email
user.authentication_token = new_access_token
user.oauth_expires_at = new_access_expires_at
user.save!
end
rescue ActiveRecord::RecordInvalid
end
end
#interact with facebook
def facebook
#facebook ||= Koala::Facebook::API.new(authentication_token)
block_given? ? yield(#facebook) : #facebook
rescue Koala::Facebook::APIError => e
logger.info e.to_s
nil # or consider a custom null object
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new(session["devise.user_attributes"], :without_protection=> true) do |user|
user.attributes = params
user.valid?
end
else
super
end
end
and on my omniauth_callbacks controller I have this method:
def all
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
alias_method :facebook, :all
These methods are used to authenticate user from scratch via Facebook. I need a way to connect existing users with their Facebook accounts not new ones if they registered via normal devise registration method
When an existing user is trying to sign in via Facebook the following error occurs:
A `NoMethodError` occurred in `omniauth_callbacks#facebook`:
undefined method `persisted?' for nil:NilClass
app/controllers/omniauth_callbacks_controller.rb:4:in `all'
You can find user by email first and just update provider and uid fields in case he is already exists.
So your User.from_omniauth may looks like that:
def self.from_omniauth(auth)
# immediately get 60 day auth token
oauth = Koala::Facebook::OAuth.new("", "" )
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
user = where(provider: auth.provider, uid: auth.uid).first
unless user
# in that case you will find existing user doesn't connected to facebook
# or create new one by email
user = where(email: auth.info.email).first_or_initialize
user.provider = auth.provider # and connect him to facebook here
user.uid = auth.uid # and here
user.username = auth.info.first_name
user.lastname = auth.info.last_name
user.authentication_token = new_access_token
user.oauth_expires_at = new_access_expires_at
# other user's data you want to update
user.save!
end
user
end
upd:
In case you faced password validation error you can override User#password_required? method to skip validation for user's signed in via Facebook.
That behavior described in following episode of RailsCasts
Just for add an updated version of the answer for rails 4 and devise 3.4.x
Like this is how an updated omniauth would look like
def self.from_omniauth(auth)
if !where(email: auth.info.email).empty?
user = where(email: auth.info.email).first
user.provider = auth.provider # and connect him to facebook here
user.uid = auth.uid # and here
user.save!
user
else
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.first_name = auth.info.name # assuming the user model has a name
user.avatar = process_uri(auth.info.image) # assuming the user model has an image
end
end
end
Just look for user by email, if there is one, just add the provider and uid, if there is not one, just create it as suggested in the documentation
You can have your from_omniauth be something like this:
def self.from_omniauth(auth)
where(auth.info.slice(:email)).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.username = auth.info.name
user.description = auth.extra.raw_info.bio
end
end
In this way, if there's an existing user with email same as the facebook account, then the first_or_create will return it and then the user can sign in (it won't be update).

Resources