Rails - Build does not create a record in database - ruby-on-rails

I'm trying to add authentications controller for my current devise system, in order to provide multiple logins with facebook and twitter.
To do that, I'm following this tutorial: http://railscasts.com/episodes/236-omniauth-part-2
My problem is, for the person, who hasn't registered yet, and trying to register with twitter.
So I need to create both user and authentication for that.
My code is the following:
user = User.new
token = omni['credentials'].token
token_secret = omni['credentials'].secret
user.provider = omni.provider
user.uid = omni.uid
user.authentications.build(:provider => omni['provider'], :uid => omni['uid'], :token => token, :token_secret => token_secret)
if user.save
flash[:notice] = "Logged in."
sign_in_and_redirect(:user, user)
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_path
end
So at the end of the registration process, the new user is created. However in the database, I don't see any twitter authentication record with respect to that user.
Is that because of the user.authentications.build ?
That would be great if you can help me.
Thanks.

As a data point: The railscasts you're referring to references Omniauth pre-1.0, which had a slighly different strategy than what that railscsts reference. (Note: I'm using the exact method you're referencing on a live site ). In this case, the build calls "apply_omniauth" -
Make sure you've created (as they reference in the video), a registrations controller which builds the resource. Here is my current working example:
class RegistrationsController < Devise::RegistrationsController
def create
super
session[:omniauth] = nil unless #user.new_record?
end
private
def build_resource(*args)
super
if session[:omniauth]
# apply omniauth calls the user model and applies omniauth session to the info
#user.apply_omniauth(session[:omniauth])
#
#user.valid?
end
end
end
However, you still need to create the authentication record, here is my exact call:
current_user.authentication.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
Hope it helps.

Yes, it is because of build
User.build # allocates a new record for you
User.create # allocates and then saves a new record for you
So I think you want
user.authentications.create(:provider => omni['provider'],
:uid => omni['uid'],
:token => token,
:token_secret => token_secret)
In addition, you should handle the case where the create does not save (validation problem)

I suppose if you are using Devise+Omniauth , you could take a look at this more recent Railscast. There is a native support of OmniAuth in the new version of Devise gem .

Yes it is because of build, it is use to build a record without saving it in the database (like new).
If in your model you have a User has_many :authentications , you can set the autosave option to true to automatically save the authentications when you are saving the user :
has_many :authentications, autosave: true

Related

multi step registration with omniauth

I've gotten the omniauth to work with google by following this tutorial. The problem I'm currently having is that instead of creating the user when they sign up, I want to route them to a finish registration page where they have to enter additional data. This is similar to how pastebin handles their oauth registration.
UserModel (taken from tutorial):
def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:provider => access_token.provider, :uid => access_token.uid ).first
if user
return user
else
registered_user = User.where(:email => access_token.info.email).first
if registered_user
return registered_user
else
user = User.create(name: data["name"],
provider:access_token.provider,
email: data["email"],
uid: access_token.uid ,
password: Devise.friendly_token[0,20],
)
end
end
end
omniauthCallBacksController
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
It looks like the problem is that your find_for_google_oauth2 is creating the user. This means that the else in your google_oauth2 callback isn't getting hit, so it'll never go to your new_user_registration_url. One solution is to separate your find and create steps rather than combining them in find_for_google_oauth2. Then, when the find doesn't find an existing user, you hit your else in your google_oauth2 callback and the omniauth data is put in the session and you get sent to new_user_registration_url. Then once they've entered the extra info and submit the form, you can use that, combined with the omniauth data you saved in the session, to create the user.
Two things about putting the omniauth data in the session:
You probably want to pick what you want to keep from the omniauth hash (especially if you're using CookieStore for the session storage) as it's quite large.
If you're using the CookieStore and Rails < 4 then the omniauth data being stored in the session (i.e. in the cookie on the user's computer) is unencrypted.

NoMethodError in SessionsController#create - Undefined method '[]' for #<Oauth::Access_Token...>

I'm having some trouble storing a user's Twitter access token in the User model. I have installed the Omniauth gem as per Railscast #241 and was successful in setting up the Twitter authentication, storing the "uid" and "name". In order to make authenticated Twitter API calls, I wanted to store the user's access token and access token secret and thus created a migration to create those fields. I did that successfully and can assign those fields successfully in the Rails Console to records that do not have the stored. When trying to authenticate a new user, however, and pull this information in from the start, I get the error listed in the title. HEre is additional error information:
app/models/user.rb:13:in `block in create_from_omniauth'
app/models/user.rb:10:in `create_from_omniauth'
app/models/user.rb:6:in `from_omniauth'
app/controllers/sessions_controller.rb:5:in `create'
I follow down this path from the sessions controller to the User model but can't figure out what is causing the error. I have included those documents below.
Sessions Controller
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url, notice: "Signed in"
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Signed out"
end
end
User Model
class User < ActiveRecord::Base
attr_accessible :name, :uid, :access_token, :access_token_secret
has_many :events
def self.from_omniauth(auth)
where(auth.slice('uid')).first || create_from_omniauth(auth)
end
def self.create_from_omniauth(auth)
create! do |user|
user.uid = auth["uid"]
user.name = auth["info"]["nickname"]
user.access_token = auth["extra"]["access_token"]["token"]
user.access_token_secret = auth["extra"]["access_token"]["secret"]
end
end
end
Can anyone help me troubleshoot this error? I know it has to do with setting the access_token fields in the create_from_omniauth method as it works fine without them. I've been banging my head trying to figure out why these won't work. Thanks in advance for any help.
I doubt why you need to save these tokens as they looks of no use. In this case, the most important thing is the Twitter returned uid for identifying or create user.
Anyway, the reason of your error is there are not such keys.
auth['extra']['access_token'] is a string, there is no further sub keys.
:extra => {
:access_token => "", # An OAuth::AccessToken object
To access token and secret, you can use auth['credentials']
:credentials => {
:token => "a1b2c3d4...", # The OAuth 2.0 access token
:secret => "abcdef1234"
},
Reference:
https://github.com/arunagw/omniauth-twitter#authentication-hash
https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later

Redirect to Page after Facebook Sign Up

I'm trying to Redirect a user after successful Facebook SIGN UP (not sign in).
I want to redirect to /getstarted/welcome after a user Registers for the first time.
My omniauth callback is :
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user ||=
User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
if #user.persisted?
# This will throw if #user is not activated
sign_in_and_redirect #user, event: :authentication
if is_navigational_format?
set_flash_message(:notice, :success, kind: "Facebook")
end
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
For devise i use
def after_sign_up_path_for(source)
'/getstarted/welcome'
end
My User Model:
Facebook Settings
def self.find_for_facebook_oauth(auth, signed_in_resource = nil)
user = User.where(provider: auth.provider, uid: auth.uid).first
if user.present?
user
else
user = User.create(first_name:auth.extra.raw_info.first_name,
last_name:auth.extra.raw_info.last_name,
facebook_link:auth.extra.raw_info.link,
user_name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20])
end
end
Can someone help me set this up ?
I solved it by adding to my User model
attr_accessor `just_signed_up`
and setting it in User.find_for_facebook_oauth in this part of the block where I create a new user (first_or_create block).
EDIT: more explanation
So in Ruby (not Rails) there is a class method/macro called attr_accessor (actually theres also attr_reader and attr_writer, attr_accessor is a shorthand for calling the other two)
If you, in your User model write
class User
attr_accessor :some_attribute
then you're able to perform
u = User.first
u.some_attribute = 'asdf'
u.some_attribute # => 'asdf'
but this attribute is not going to be saved to DB, so it may be used as a temporary storage of some value in Rails model.
Another thing to know is that there are only two "values" that are false in Ruby, those are false and nil.
Using those two tricks you may create a new user and temporarily set this flag on the object
u = User.create
u.just_signed_up = true
u.just_signed_up # => true
u.reload! # fetches record from DB
u.just_signed_up # => nil
and since nil is false, this check will fail for every user except for ones you just created!

Ruby on Rails no such column: authentication.provider: Omniauth

I was following this tutorial on Omniauth: http://railscasts.com/episodes/235-omniauth-part-1?view=asciicast
I keep getting this error:
no such column: authentication.provider:
Now the main thing I want to know is why "provider" isn't being accepted. It exists in the class... the authentications database exists... so why is it saying it isn't there?
Here's my authentications controller:
class AuthenticationsController < InheritedResources::Base
def index
#authentications = current_user.authentications if current_user
end
def create
#user = User.where(authentication: auth).first_or_create(:provider => auth['provider'], :uid => auth['uid'])
self.current_user = #user
# auth = request.env["omniauth.auth"] current_user.authentications.create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
def auth
request.env['omniauth.auth']
end
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
flash[:notice] = "Successfully destroyed authentication."
redirect_to authentications_url
end
end
I can assure you I have a model called authentication and that this model has a provider and uid field. I've also tried where(authentications: auth) and where(auth: auth)
each with no luck.
Any ideas would be appreciated.
UPDATE
authentication.rb (model)
class Authentication < ActiveRecord::Base
attr_accessible :create, :destroy, :index, :provider, :uid, :user_id
belongs_to :user
end
UPDATE 2
I'm basically attempting to adapt this tutorial to rails 3.2.
The original line from the tutorial is commented out above.
UPDATE 3
Here is the entire first line of error:
SQLite3::SQLException: no such column: authentication.provider: SELECT "users".* FROM "users" WHERE "authentication"."provider" = 'facebook' AND "authentication"."uid" = '2222222' AND "authentication"."info" = '--- !ruby/hash:OmniAuth::AuthHash::InfoHash
Hate to be a burden... but the clock's really ticking, my ass is on the line, and I'm about to go completely insane trying to figure this out. If you can tell me just why provider isn't being accepted I'm sure I can figure out the rest.
your create action has not sense
User.where(authentication: auth) converts to SELECT * FROM users WHERE authentication = a_hash
You shoul do something like
auth1 = Authentication.where(provider: auth['provider'], uid: auth['uid']).first
if !auth1.nil?
current_user = auth.user
else
user = User.new
user.authentications.build(provider: auth['provider'], uid: auth['uid'])
user.save!
current_user = user
end
Since you are just adding a record in the authentications table, I am unable to understand why you are reassigning this.current_user. Also is current_user a helper method or a member, if it's a member where is it declared?
Don't you just want to create an authentication for the current user as such?:
def create
current_user.authentications.first_or_create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
This finds the first authentication record by provider and uid, if not found then creates that authentication record.
Also by that error, I hope you have figured out the answer to this question:
Now the main thing I want to know is why "provider" isn't being
accepted. It exists in the class... the authentications database
exists... so why is it saying it isn't there?
It is because you are calling first_or_create() on User object, not Authentication.
I also faced this issue recently. At first I thought I had forgotten to add a provider column to users table, but that wasn't it.
This is how I eventually solved it:
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.logo = auth["info"]["image"]
# if you use confirmable, since facebook validates emails
# skip confirmation emails
user.skip_confirmation!
end
end
auth is a hash like the one below, so instead of auth.provider, I used auth["provider"] etc:
omniauth.auth: {"provider"=>"facebook", "uid"=>"11111111111111", "info"=>{"email"=>"some#email.com", "image"=>"http://graph.facebook.com/v2.6/11111111111111/picture"}, "credentials"=>{"token"=>"sometoken", "expires_at"=>1506680013, "expires"=>true}, "extra"=>{"raw_info"=>{"email"=>"some#email.com", "id"=>"11111111111111"}}}

Omniauth+Facebook lost session

In a recent project, facebook Users can login using their Facebook UID to upload picture submissions based on file uploads or uploads from their personal albums etc.
Everything works quite nice on my local system in the development environment. Login via Facebook, Logout, Upload - all great.
In production though I'm facing a unknown and hard to debug problem. It seems that every once in a while (actually reproducable when uploading a new Submission to the system) the session is lost, the picture is NOT uploaded and the facebook user is logged out (!).
I'm using devise and omniauth. Omniauth is integrated into Devise.
Following is all the code that touches Devise/Omniauth or the User.
app/models/user.rb
class User < ActiveRecord::Base
devise :omniauthable, :rememberable, :omniauth_providers => [:facebook]
def self.create_with_omniauth(auth)
u = User.find_by_uid(auth["uid"])
return u unless u.nil?
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["user_info"]["name"]
user.email = auth['user_info']['email']
end
end
def after_signin_path
'/competition'
end
end
Database contains all needed fields for :rememberable, I hope.
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model
#user = User.create_with_omniauth(env["omniauth.auth"])
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
#user.update_attributes!(:current_auth_token => env["omniauth.auth"]['credentials']['token'], :last_language => I18n.locale.to_s, :updated_at => Time.now, :remember_created_at => Time.now)
sign_in_and_redirect(:user, #user)
else
redirect_to '/competition'
end
end
protected
def after_omniauth_failure_path_for resource
'/competition'
end
end
config/initializers/devise.rb
OmniAuth.config.full_host = "http://#{APP_CONFIG[:domain]}"
Devise.setup do |config|
config.mailer_sender = "devise#myapp.host.com"
require 'devise/orm/active_record'
config.stretches = 10
config.encryptor = :bcrypt
config.timeout_in = 3.days
config.pepper = "2a4b8b2ed9e12e553a7a542176f2ace1af62c062f3ba203a590b8b6307f33042b394922807a840004a3dcdf1c4e97ae085fe2c29654ddaeab7c60f431a8078abb"
config.omniauth :facebook, APP_CONFIG[:facebook_app_id], APP_CONFIG[:facebook_app_secret], {
:scope => "email,user_photos,user_photos,publish_stream,offline_access",
:client_options => {
:ssl => {
:ca_file => "/etc/pki/tls/certs/ca-bundle.crt"
}
}
}
end
There are no auth-related methods in application_controller.rb.
routes.rb:
The interesting part below:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
match '/logout_fb' => 'start#logoutfb'
authenticate :user do
get '/users/connect/:network', :to => redirect("/users/auth/%{network}")
end
Somehow I cannot get to understand the authenticate block, which according to another post should be helpful.. ideas on this too?
So many theories:
One is that the facebook function in the omniauth_callbacks_controller runs aside of the users' session, and hence sign_in_and_redirect won't work. So I had the idea of redirecting to another page like '/auth?uid=xxx' but this sounds both wrong, insecure and not stable.
Any help or hints are appreciated!
A bit of a long shot but try turning off protect_from_forgery - I had some issues with sessions disappearing and it turned out to be the issue discussed here https://github.com/intridea/omniauth/issues/203
In my config/initializers/omniauth.rb, I had to add the following:
OmniAuth.config.full_host = "http://yourdomain.com" # Or have an environment specific URL.
You are using devise but you are not using it's own helpers. For instance, you've defined your own current_user method. To be honest, I can't see any obvious mistakes you've made, so it's just a desperate tip.
what kind of a session store do you use locally and what in production?
When you say "facebook user is logged out", this user is still logged in to facebook, but lost his session at yourapp.com ?
Are you sure that user.id is never nil or that you anywhere else than in .destroy set session[:user_id]= some_nil_variable ?

Resources