How to manually create a new user and user session in Devise? - ruby-on-rails

I have a form where I collect a lot of information in Rails. Part of this form is fields for a new user to register. Since Devise has controllers/actions specifically to create a new user, I don't know how to programmatically create a user in an entirely different action that also creates another record. I really can't have the user registration form separate. I can't figure out how to create a user, and then log the user in, like I could easily do in Authlogic.
I have used both Authlogic and Devise, and think each has their strengths and weaknesses. With Devise, I love how quick it is to "get going" with a new project, but customizing it seems to be a pain. Authlogic had so many problems with Rails 3 a while back, that I switched to Devise. I'm now working on a new project and get to start from scratch.
So I think there are 2 potential answers to this question: (a) how to do this in Devise, or (b) why I should just switch to Authlogic with Rails 3 instead.

You can create a new Devise user simply by creating a new user model (see https://github.com/plataformatec/devise/wiki/How-To:-Manage-users-through-a-CRUD-interface)
#user = User.new(:email => 'test#example.com', :password => 'password', :password_confirmation => 'password')
#user.save
To sign in your newly created user, use sign_in #user

Saving the newly created user before sign_in will fail because devise has not yet filled the required fields for User object yet. So David's code will work for the current page, but the next page won't get the signed in user, since the user is only saved after the session is set. This happens when I use Mongoid, and I don't know if it is a problem specific to Mongodb.
To address this problem, I have a very imperfect solution to call sign_in twice. The first sign_in will save the user. It just works but people can improve it certainly.
#user = User.new(:email => 'test#example.com',
:password => 'password',
:password_confirmation => 'password')
# This will save the user in db with fields for devise
sign_in #user
# :bypass is set to ignore devise related callbacks and only save the
# user into session.
sign_in #user, :bypass => true

For new people seeing this question...
A simple way to do this is in your
config/routes.rb
you should have a line like the following :
devise_for :users
so, you just have to add a path prefix that devise will use:
devise_for :users, :path_prefix =>'auth'
Hope it helps!

Related

Manually instantiating devise user models

This question is related to one that I asked a couple years ago:
Instantiating Devise user models manually using contents of params hash
I am not sure if this is a rails 4 issue, but I am finding that I cannot manually instantiate devise user in my controller code. This used to work in rails 3.
class RegistrationsController < Devise::RegistrationsController
...
def schema_test
#user = User.new(:email => 'jhw#ausd.k12.edu', :password => 'asdf123', :password_confirmation => 'asdf123')
#user.save
end
...
end
This is the devise-specific part of my routes.rb:
devise_for :users, :controllers => {:registrations => "registrations"}
devise_scope :user do
get '/schema_test', to: 'registrations#schema_test'
end
When I call schema_test, I am finding that the user object is not getting saved to the database. Does anyone have any suggestions?
Best practice is to check the behavior in the rails console before you integrate it into your rails application.
The simplest way to figure out your issue is to read the error messages that devise returns. This could be caused by not meeting the password requirements and/or that the email already exists in your database.
From the root of your application, run rails c.
#user = User.new(:email => 'jhw#ausd.k12.edu', :password => 'asdf123', :password_confirmation => 'asdf123')
# Check if the user object is valid
#user.valid?
# If it comes back false, read the error messages
#user.errors.messages
=> {:password=>["is too short (minimum is 8 characters)"]}
In your example, the user is not being saved because the password is too short.

Rails - Build does not create a record in database

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

Devise sending forgot password instructions to any email id

I am looking for a customization in devise where if we click on forgot password it should send the mail to any e-mail id . Something like it happens in Gmail, irrespective of the email id exists or not.
Screen 1
Screen 2
Currently what i have is this in which it tries to validate with the valid users in the system.
The Devise, recoverable module takes care of this
def send_reset_password_instructions(attributes={})
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions if recoverable.persisted?
recoverable
end
How can i remove this validation and have email sent to any Email id?
There is a Devise configuration called paranoid that, when set to true, would change the message in a way to avoid e-mail enumeration. Just set config.paranoid = true in your Devise configuration.
My solution would be to extend/override Devise's passwords controller. To do this, create a controller (let's call it passwords) that inherits from Devise's passwords controller, like this:
class PasswordsController < Devise::PasswordsController
Then edit your routes file so this change takes effect:
devise_for :users, :controllers => { :passwords => 'passwords' }
Now, you'll want to override the create action. There are several ways you could do this but since I'm not sure of what you want to do, I'll show you 2 things you could do:
You only want to prevent the "Email not found" error so that people can't find which emails exist or not in your database:
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
end
You really want to send emails to any entered email:
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
unless successfully_sent?(resource)
Devise::Mailer.reset_password_instructions(resource).deliver
end
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
end
Now, the problem with this last solution is that you are sending an email to a user that doesn't exist... And so when the user comes back, he won't be able to enter his new password since his user account can't be found. But if you really want to do this, hopefully I set you on the right track.

Integrating Devise with Twitter

I am currently using this guide to try to integrate twitter into Devise.
It is a little challenging because twitter's OAuth does not provide email addresses. Hence the flow of the sign up should be:
User clicks "Sign in with twitter"
Oauth call back to twitter's callback
Ask for the user for email (I need that for my site)
Sign in user.
I realized that if the user already has an account on my system with Twitter, I must be able to find the account. Hence I have added 2 extra field to the user model: oauth_provider, oauth_uid.
In omniauth_callbacks_controller:
def twitter
#user = User.find_for_twitter_oauth(env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
sign_in_and_redirect #user, :event => :authentication
else
flash[:warn] = "We still need a little more info!"
redirect_to new_user_registration_url
end
end
In user.rb
# The trick here is that twitter does not give you an email back
# So we should make use of uid and provider
def self.find_for_twitter_oauth(oauth_hash, signed_in_resource=nil)
uid = oauth_hash['uid']
if user = User.find_by_oauth_provider_and_oauth_uid('twitter', uid)
user
else
User.create(:password => Devise.friendly_token[0,20],
:oauth_provider => "twitter",
:oauth_uid => oauth_hash['uid'])
end
end
However, I have debugged this thoroughly and realized that if I redirect a user to new_registration_url, the User created in user.rb will be wiped.
How can I do the following:
If user cannot be found via oauth_provider and oauth_uid, create a User object with these credentials
direct user to new_registration_url
When the user have submitted his/her email, create the user with the same user object created in 1)
I have tried using session, but it gets really messy as I have to monkey patch devise's new and create for registrationscontroller.rb.
Please someone provide me a way to do this.
I have not been successful yet. Let me show you what I have written.
I followed these 2 screencasts and it is exactly what you want.
You can try it out! He is using the omniauth gem, which is very easy and awesome :-)
http://railscasts.com/episodes/235-omniauth-part-1
http://railscasts.com/episodes/236-omniauth-part-2

Create a new user with new openid login authlogic

I've implemented authlogic in a rails site, and I'm trying to get openid to work correctly. So far, you can login just fine as long as you have an existing account, but not so much if you don't. I'd like to be able to automagically create a new account if the identity_url is not already in the database.
The problem is that I also need to store some additional info. if the user is logging in for the first time with their openid, I'd like to ask them to fill in basic info (name, email), BEFORE the account is created.
I've played around with a few methods, but nothing seems to be working.
Thanks in advance for any input!
acts_as_authentic do |c|
c.openid_required_fields = [:email,"http://axschema.org/contact/email"]
end
Will allow you to require an email. I'm unsure of how to require other fields, but maybe check that axschema.org page. There is no need for the user to fill anything out other than their OpenID provider URL.
Combining login and registration could be done with something like this (untested create method from UserSessions controller, like from the authlogic tutorial stuff)
def create
if User.find_by_openid_provider(params[:user_session]).nil? # or something like that
#user = User.new(params[:user_session])
if #user.save
redirect_to whatever_path
else
# handle error
end
else
#user_session = UserSession.new(params[:user_session])
if #user_session.save
add_message 'Login successful!'
redirect_to whatever_path
else
render :action => :new
end
end
end
Maybe try putting the additional information into a temp table of some kind, and keep track of the session the user is in. Once they have authenticated, connect the previously entered information with the OpenID information to create the real user.

Resources