Devise. Registration and Login at the same page - ruby-on-rails

I'm trying to integrate Devise into my application. I need implement login form at top of the page (I've implemented this form into layout page) and I've implemented registration which contains registration form.
But it shows validation errors for both form when I tried submit incorrect registration data.

Without more information, it's hard to guess what the problem is. I've found the Wiki pages to be really helpful (and increasingly so), though you may have already looked them over:
Devise Wiki Pages
Two pages that might be relevant to your needs:
Display a custom sign_in form anywhere in your app
Create custom layouts
Hope this helps!
-- ff

The problem of seeing the validation errors for both forms stems from the 2 things. First, devise forms use a generic 'resource' helper. This creates a User object, and that same user objet gets used for both the sign up and the sign in form. Second, devise errors are typically displayed using the 'devise_error_messages!' helper which uses that same shared resource.
To have sign in and sign up on the same page you need to create different user objects for each form, and a new way of displaying the error messages.
First off, you'll need to create your own registration controller (in app/controllers/users/)
class Users::RegistrationsController < Devise::RegistrationsController
include DevisePermittedParameters
protected
def build_resource(hash=nil)
super
# Create an instance var to use just for the sign up form
#sign_up_user = self.resource
end
end
And update your routes file accordingly
devise_for :users, controllers: {
registrations: 'users/registrations'
}
Next you'll need your own error messages and resource helpers. Create a new helper like devise_single_page_helper.rb and add the following:
module DeviseSinglePageHelper
def devise_error_messages_for_same_page(given_resource)
return "" if given_resource.errors.empty?
messages = given_resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
sentence = I18n.t("errors.messages.not_saved",
count: given_resource.errors.count,
resource: given_resource.class.model_name.human.downcase)
html = <<-HTML
<div id="error_explanation">
<h2>#{sentence}</h2>
<ul>#{messages}</ul>
</div>
HTML
html.html_safe
end
def sign_up_user
#sign_up_user ||= User.new(username: 'su')
end
def sign_in_user
#sign_in_user ||= User.new(username: 'si')
end
end
Finally, in your views, update your forms like so:
-# The sign up form
= simple_form_for(sign_up_user, url: registration_path(resource_name)) do |f|
-#...
= devise_error_messages_for_same_page(sign_up_user)
-# The sign in form
= simple_form_for(sign_in_user, url: sessions_path(resource_name)) do |f|
#...
= devise_error_messages_for_same_page(sign_in_user)
All of this together gives you 2 different objects - 1 for sign up and 1 for sign in. This will prevent the error messages from one showing in the other. Please note that recommend putting both forms on your sign in page (and perhaps having the default sign up page redirect to the sign in page) because by default a failed sign in attempt will redirect to the sign in page.

You should have two forms on the page — one for signing up and one for registering. If you want a single form and multiple potential actions you are going to need a couple buttons that get handled client side and change the form's action & method to the appropriate route depending you want to create a user or a session.
If you think you did this already, the problem almost certainly lies in your code. If you were to share it with us we could perhaps point out something you may have missed.

Related

Rails devise registration two ways after sign_up

I want devise to redirect to specific controller index action( or specific page) after user is sign_up.
In detail, i have an app with 2 ways to sign-up : for the client and for the Professor . Actually, the 2 type use the same registration page, it works very nice but when they sign_up they are automatically redirect to the same page ...
So, i want to change the way like this :
- when user click on I want to become Professor i want to register (full name, mail, password) and redirect after sign_up to the Professor dashboard
- and when user click on Client i want to register with the "same way" BUT in the end, i want to redirect to the client's dashboard.
So, i started to change devise.rb to change this
config.allow_unconfirmed_access_for = 2.days (automatically login after sign_up)
And in my RegistrationsController, i override the after_sign_up_path_for to have something like
def after_sign_up_path_for(resource)
if 'user is Prof'
redirect_path_professor
else
redirect_path_client
end
end
but how can i check (in condition) if user click on Prof. or Client ?. Its the same resource (registration) ^^
Thanks
Why not using only one link, then let the user selects his role in the registration view?
If you really want two links for the same view, you should pass the role in a param (something link link_to 'Becoming a Professor', new_registration_path(role: 'professor'), then add an hidden_field in your registration form with the value of the param.
= f.input :role, as: :hidden, input_html: { value: params[:role] }
But don't forget to validate the content of the params to avoid the bad values sent by teasing users
Also, after_sign_in_path_for(resource) should be in application_controller. You'll need a column in users to specify the role, and your method will looks like something like this:
def after_sign_in_path_for(resource)
stored_location_for(resource) || (resource.role.professor? ? professor_dashboard_url : user_dashboard_url(resource))
end

Rails Devise Signup: Separate Registration Form for User Type

I'm trying to create a new signup page in Rails specific to an invitation URL such as this:
http://localhost:3000/users/sign_up?school=Elementary&principal=true
When a user invites their principal to sign up, I'd like a difference experience (new template that hides certain fields from the regular signup page).
I'm able to pass in the params into the form like this:
<%= f.text_field :school, :value => params[:school] %>
But I can't get logic in the view that hides certain inputs depending on the URL that the user comes in from. Essentially, I'm looking to fork new.html.erb into newprincipal.html.erb
The simplest way to accomplish it is checking value of the principal param in the controller and render a correct template based on it, e.g.:
class RegistrationsController < Devise::RegistrationsController
def new
render :new_principal if params[:principal] == true
end
end
Ruby on Rails follows convention over configuration paradigm, so unless principal param is set to true (or is not present) the default new template will be rendered. You can read more about layouts and rendering in the official Rails guide.

Gracefully handling InvalidAuthenticityToken exceptions in Rails 4

I've just upgraded an app from Rails 3 to Rails 4, and I'm seeing a bunch of InvalidAuthenticityToken exceptions popping up. Digging in it looks like it is fairly common for our users to have multiple long-lived tabs open on our site. So I think what's happening is something like this: user Alice has three tabs open and her session expires. She logs back in on one of the tabs, which updates the authenticity token stored in her session. Then she returns to one of the other open tabs and tries to submit data, but she gets a 500 error from the InvalidAuthenticityToken error we raised.
It would clearly be nice to do some error handling for Alice so she doesn't get a 500 error. I'm wondering about best practices for this kind of situation. What would be a nice way to handle Alice's submission from the expired tab? I don't want to expire the current session, because that would be super annoying from the user's perspective ("I just logged in, you dolt!"). Ideally, I just want the user to reload the page, which would result in the correct authenticity token being present in the form. Or should I be doing something different so that the long-lived tabs that are open notice that the session has expired and force a reload? This would probably be sub-optimal from the user's point of view, because they liked having that page ready and easily accessible to keep referencing, which is why they left it open in the tab in the first place.
In my rails 4.1.1 app I had the same problem. I solved it by adding this code to ApplicationController. I found this solution here.
rescue_from ActionController::InvalidAuthenticityToken, with: :redirect_to_referer_or_path
def redirect_to_referer_or_path
flash[:notice] = "Please try again."
redirect_to request.referer
end
This way any controller that inherits from ApplicationController will handle the error with a redirect to the page the form was submitted from with a flash message to give the user some indication of what went wrong. Note this uses the hash syntax introduced in Ruby 1.9. For older versions of Ruby you will need to use :with => :redirect_to_referer_or_path
The solution to this problem can be divided into 2 phases. Phase 1 addresses the issue of ActionController::InvalidAuthenticityToken error and phase 2 deals with the issue of long tabs waiting idly.
Phase 1(1st variation)
One way to go about is redirect the user back to their location before the error. For ex. if Alice has 3 tabs open, the first one expires and Alice logs in again in it because she was browsing on it. But when she moves to tab 3 which has URL 'http://example.com/ex' and submits a form. Now instead of displaying her an error we can redirect her back to 'http://example.com/ex' with her submitted form values already pre-filled in the form for easy use.
This can be achieved by following this approach:
1) ApplicationController - Add this function:
def handle_unverified_request
flash[:error] = 'Kindly retry.' # show this error in your layout
referrer_url = URI.parse(request.referrer) rescue URI.parse(some_default_url)
# need to have a default in case referrer is not given
# append the query string to the referrer url
referrer_url.query = Rack::Utils.parse_nested_query('').
merge(params[params.keys[2]]). # this may be different for you
to_query
# redirect to the referrer url with the modified query string
redirect_to referrer_url.to_s
end
2) You need to include a default value for all your form fields. It will be the name of that field.
...
<% f.text_field, name: 'email', placeholder: 'Email', value: params[:email] %>
...
This way whenever Alice will submit a form with wrong authenticity_token she will be redirected back to her form with the original values she submitted and she will be shown a flash message that kindly retry your request.
Phase 1(2nd variation)
Another way to go about is just redirect Alice back to the form which she submitted without any pre-filled values.
This approach can be achieved by:
1) ApplicationController - Add this function:
def handle_unverified_request
flash[:error] = 'Kindly retry.'
redirect_to :back
end
Phase 2
To tackle the problem of long awaited tabs you can take the help of SSEs. Rails 4 has ActionController::Live for handling SSEs.
1) Add this to any controller:
include ActionController::Live
...
def sse
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream, retry: 2000, event: 'refresh') # change the time interval to your suiting
if user_signed_in? # check if user is signed-in or not
sse.write('none')
else
sse.write('refresh')
end
ensure
sse.close
end
2) Give the above function a GET route in your routes file. Lets call this route '/sse'
3) Add this in your layout:
<% if user_signed_in? %> # check if user is signed-in or not
<script>
var evtSource = new EventSource("/sse");
evtSource.addEventListener('refresh', function(e){
if(e.data == 'refresh'){
window.location = window.location.href;
}
});
</script>
<% end %>
Note: using EventSource is not supported by all browsers. Please check out the Browser compatibility section.
Source:
rails 4 redirect back with new params & MDN: Using server-sent events
Answer by Durrell is perfectly alright, I am just providing an alternative way to write the same thing. This needs to go in ApplicationController.
rescue_from ActionController::InvalidAuthenticityToken do |_exception|
flash[:alert] = 'Please try again.'
redirect_back fallback_location: root_path
end

Keeping a query-string in devise sign up

I am using rails with devise for signing up. Also I added an invite code, so not everybody can sign up.
The invite code gets transmitted via query-string like "/users/sign_up?invite_code=wajdpapojapsd" and gets added to a hidden field of the sign-up form with "f.hidden_field :invite_code, :value => params[:invite_code]".
This works pretty well. The only problem is that if the sign up doesn't get validated and rejected, devise redirects to "/users" and loses the query string with the invite_code in it.
As the email stays in the sign up form after the failed attempt, I believe that this should also work for the invite code. As a worst case solution redirecting :back after a failed sign up and losing the email, but keeping the invite code, would be better than the way it works now.
EDIT: By now I ve set up a registration controller for devise, but don't have any idea how to get the desired behavior.
Any help on how to keep the query string or just the invite code would be awesome.
I found a working solution by now.
I used jstr's answer to set up the controller for devise and added the "session" line.
class MyRegistrationsController < Devise::RegistrationsController
prepend_view_path "app/views/devise"
def create
super
session[:invite_code] = resource.invite_code
end
def update
super
end
end
Afterwards I added following to the devise/registrations/new.html.erb
<% if params[:invite_code]
#invite_code = params[:invite_code]
else
#invite_code = session[:invite_code]
end %>
And changed the hidden_field to
<%= f.hidden_field :invite_code, :value => #invite_code %>
You might need to make your own subclassed Devise controllers to get this to work.
This answer has a good description of how to to this.
The basics:
Install the Devise views if you haven't already with rails generate devise:views
Create a subclassed Devise::RegistrationsController
Update your Devise routes declaration to get Devise to use your subclassed controller
#James Lever 's solution has one disadvantage. invite_code remains in session. In some solutions it can cause problems. The alternative soultion would be:
def create
# set invite code to session:
session[:invite_code] = resource.invite_code
# here the form is rendered and invite code from session would be used:
super
# delete invite code from session:
session.delete('invite_code')
end
why don't use instance variable such
#invite_code = params[:invite_code]
Then when a sign up didn't pass validation, you can use that variable in the view which will be displayed for failed sign up.
I mean before, it's redirected, you should keep the invite code parameters with instance variable. sorry if i'm mistaken.

'Cannot redirect to nil' error after adding a field to devise registration view

After adding this line to the devise/registrations/new.html.haml file (view):
%div
= f.label :account_type
%br/
= f.select(:account_type, [["Usertype1","usertype1"],["Usertype2","usertype2"]], {:selected => nil, :prompt => 'Pick One'})
I get the following error after clicking on the confirmation link in the confirmation e-mail:
ActionController::ActionControllerError in Devise::ConfirmationsController#show
Cannot redirect to nil!
It only happens if I select Usertype2 upon registration. I also made the account_type attr_accessible. The account_type seems to be getting assigned (I checked in the rails console) and the development logs don't have any further information.
I think this is the line in the devise confirmations controller where the error is occurring:
respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
Also, the account is being confirmed, but when trying to log in, I get the following:
undefined method `user_url' for #<Devise::SessionsController:0x9d1659c>
which is in the create action of the devise sessions controller.
Any help would be appreciated. Thanks!
John
The two errors you mentioned are one and the same, essentially when you are signing-in successfully Devise is unable to detect where to redirect you. This problem often occurs when you have multiple models or try to setup a custom redirect (post sign in) in the routes file.
Try to define the path in the ApplicationController.
Devise docs say that the after_sign_in_path_for method takes the actual model object (ie: the model being signed-in)
def after_sign_in_path_for(resource)
signed_in_path_for_user
end
Note: You can do the same for several Devise paths / variables (override them). Also for more information on doing this for multiple Devise models in the same app, you can look at this question and it's answer.

Resources