I'm kinda new to Rails, so if I am doing this totally wrong, just tell me :)
I have two models built with devise in my rails app, one is confirmable, the other is not.
I would like to set the after_sign_up_path_for(resource) on the user home page or on a separated page indicating that a confirmation email has been sent depending on wether my resource has to confirm its registration or not.
I can check existence of the confirm fields in the data model, but is there a cleaner way to do it, something like resource.confirmable?
Is there a cleaner way than overwriting after_sign_up_path to redirect to different pages depending on wether the model is confirmable ?
Thanks
You could do something like the following:
resource.class.devise_modules.include?(:confirmable)
Or
resource.respond_to?(:confirmed?)
Regarding to check if the user is already confirmed, you could use the following:
def after_sign_up_path_for(resource)
if resource.class.devise_modules.include?(:confirmable)
if resource.active_for_authentication?
"path for confirmed user"
else
"path for waiting for confirmation"
end
else
"path for non confirmable model"
end
end
Check out the docs: http://www.rubydoc.info/github/plataformatec/devise/Devise/Models/Confirmable
Related
Actually I am using devise for login and registration and its working fine, it send a confirmation email which is really good. But I need to send confirmation email in specific cases. I am passing user type in URL and on behalf of that I want to set mail. I have two type of users, one will be confirm their account their self reset of the users can not confirm their account only admin can approve their accounts. I have override the create method
def create
super
if params[:type]=='xyz
#user.skip_confirmation_notification!
end
end
but it sends mail in both cases. Please tell where am wrong.
So according to devise confirmable module you can skip to send confirmation and email by following code.
def confirm_your_type_user_without_confirmation_email
# check your condition here and process the following
self.skip_confirmation!
self.confirm!
# condition may end here
end
Now lets call it on create hook.
class User < ActiveRecord::Base
after_create :confirm_your_type_user_without_confirmation_email
....
end
for more reference you may check this:
Devise Confirmable module
The solution should be something similar as I mentioned here above. And its best practice to avoid controller to handle these responsibilities, because its not something your controller should take. :)
I hope my answer will give you some way to solve your problems! Thanks!
So, I am somewhat new to rails and devise, so I apologize in advance if this is a basic question. I couldn't find any information on this anywhere, and I searched thoroughly. This also makes me wonder if Devise is the right tool for this, but here we go:
I have an app where devise user authentication works great, I got it, implemented it correctly and it works.
In my app, users can belong to a group, and this group has a password that a user must enter to 'join' the group.
I successfully added devise :database_authenticatable to my model, and when I create it an encrypted password is created.
My problem, is that I cannot authenticate this! I have a form where the user joins the group, searching for their group, then entering the password for it.
This is what I tried:
def join
#home = Home.find_for_authentication(params[:_id]) # method i found that devise uses
if #home.valid_password?(params[:password]);
render :json => {success: true}
else
render :json => {success: false, message: "Invalid password"}
end
end
This gives me the error: can't dup NilClass
on this line: #home = Home.find_for_authentication(params[:_id])
What is the problem?
The problem will be here:
Home.find_for_authentication(params[:_id])
I've never used database_authenticatable before (will research it, thanks!), so I checked the Devise docs for you
The method they recommend:
User.find(1).valid_password?('password123') # returns true/false
--
Object?
The method you've used has a doc:
Find first record based on conditions given (ie by the sign in form).
This method is always called during an authentication process but it
may be wrapped as well. For instance, database authenticatable
provides a find_for_database_authentication that wraps a call to
this method. This allows you to customize both database
authenticatable or the whole authenticate stack by customize
find_for_authentication.
Overwrite to add customized conditions, create a join, or maybe use a
namedscope to filter records while authenticating
The actual code looks like this:
def self.find_for_authentication(tainted_conditions)
find_first_by_auth_conditions(tainted_conditions)
end
Looking at this code, it seems to me passing a single param is not going to cut it. You'll either need an object (hence User.find([id])), or you'll need to send a series of params to the method
I then found this:
class User
def self.authenticate(username, password)
user = User.find_for_authentication(:username => username)
user.valid_password?(password) ? user : nil
end
end
I would recommend doing this:
#home = Home.find_for_authentication(id: params[:_id])
...
I use devise_invitable in my app to allow users to send invitations. I realized a bad case in which a user has been invited but ignores the invitation and later returns to the app to sign up on their own. Because devise_invitable handles invitations by creating a new user using the provided email address for the invitation, my uniqueness validation on the email field will cause Rails to complain, telling the user that the email address is already taken.
I'm trying to write some logic to handle this case. I see two paths - either figure a way to detect this and destroy the previously created user and allow the new one to be created, or detect the user was invited and execute another flow. I've decided to implement the second option, as I'd like to still utilize the invitation if possible.
My limited experience has me questioning if what I've written will work, but I can't actually fully test it because the Rails validation on the email is triggered. I've made sure Devise's :validatable module is inactive. I created a method that (I think) will detect if a user was invited and in that case the uniqueness validation should be skipped.
#user.rb
...
validates :email, uniqueness: true, unless: :was_invited?
...
def was_invited?
if self.invitation_sent_at.present? && self.sign_in_count == 0
true
else
false
end
end
FWIW, I had originally written this in shorthand rather than breaking out the if/else, but I wanted to be very explicit in an effort to find the bug/failure.
The hope is that once the form passes validation, the create action will do some detection about a user's invitation status and, if they were invited, redirect them to the accept_user_invitation_path. Again, I haven't been able to actually test this yet because I can't get around the validations.
#registrations_controller.rb
def create
if User.find_by_email(params[:email])
#existing_user = User.find_by_email(params[:email])
#existing_user.save(validate: false)
if #existing_user.was_invited?
redirect_to accept_user_invitation_path(:invitation_token => #existing_user.invitation_token)
end
else
super
end
end
In a desperate effort, you'll see I've also added the .save(validate: false) to try to short circuit it there, but it's not even getting that far.
If I comment out the email validation entirely, simply to test the rest of the logic/flow, I get a PG error complaining on uniqueness because of an index on the email address - I don't want to tear all this apart simply to test this method.
I've tried to mess with this for hours and I'm at a loss - any help is appreciated. Let me know if there's any other code you want to see.
Looking at the redirect:
redirect_to accept_user_invitation_path(:invitation_token => #existing_user.invitation_token)
I can see that there is no return which should mean that if that redirect was being called you should be getting an AbstractController::DoubleRenderError error as the parent controller's create method should be trying to render the new view.
From this I would guess that the query you are using to find the existing user is not actually returning a result, possibly because you are using params[:email] whereas if you are using the default views or a properly formatted form it should be params[:user][:email].
Maybe you should give more responsibilities to your controller...
If you find the user, use that, else create a new one. Assuming your form appears with http://yourapp/users/new, change it in your routes to http://yourapp/users/new/:email, making the user input their email before advancing to the form.
def new
#existing_user = User.find_by_email("#{params[:email]}.#{params[:format]}") || User.new
if #existing_user.was_invited? # will only work for existing user
redirect_to accept_user_invitation_path(:invitation_token => #existing_user.invitation_token)
else
render 'new'
end
end
def create
# do maybe something before saving
if #existing_user.save(user_params)
# do your magic
else
render 'new', notice: "Oops, I didn't save"
end
end
I would like to show a error message when a confirmed user tries to resend confirmation. Is this something that is already provided by devise or should i have to override some of their methods? If so which methods?
I got this working by overriding create action of confimations controller
def create
self.resource = resource_class.send_confirmation_instructions(resource_params)
if successfully_sent?(resource)
flash[:notice] = "Confirmed already, Please try signing in" if resource.confirmed?
respond_with({}, :location => after_resending_confirmation_instructions_path_for(resource_name))
else
respond_with(resource)
end
end
I am just overriding the flash notice in the case of confirmed user
The first thing you want to decide is if you really want to put a message for confirmed users. This might allow user enumeration (i.e have a robot try to find the users' email on your site... that's why there's the paranoid mode.
If you truly want to display a confirmation message, you do not need to override the controller. Your user will already have an error in the resource something like: "was already confirmed, please try signing in". You therefore don't need to modify the flash for this, you might simply want to use devise_error_messages! (or your own custom code to display error content).
Hope this helps.
I'm using Devise with my rails 3 app. The app requires users to validate their email before continuing.
How can I redirect users to a specific url like /gettingstarted after they successfully validate their email address via the email confirmation msg they receive?
Thanks
When a user clicks on the confirm link they are taken to a confirm page which checks the confirmation token and if it's valid automatically logs them into the application. You could overwrite the after_sign_in_path_for method in your ApplicationController (as shown on the Devise wiki) and then redirect them to your getting started page the first time a user logs in.
def after_sign_in_path_for(resource_or_scope)
if resource_or_scope.is_a?(User) && first login
getting_started_path
else
super
end
end
For "first login" you could test if the confirmed_at timestamp is within a couple minutes of now, if your also using the trackable module in devise you can check if the sign_in_count is 1 or you could create your own field in the user model that tracks this information.
I'm checking devise source code at https://github.com/plataformatec/devise/blob/master/app/controllers/devise/confirmations_controller.rb
and seems that we have a callback to do it "after_confirmation_path_for"but I couldn't get it working without rewrite Devise::ConfirmationController
I hope that helps and if somebody get it working just defining after_confirmation_path_for just let us know.
I'm using the last_sign_in_at field from the 'trackable' model to achieve this. I've got the following code in my root action:
if current_user.last_sign_in_at.nil? then
redirect_to :controller => :users, :action => :welcome
end
http://rubydoc.info/github/plataformatec/devise/master/Devise/Models/Trackable
Seems to work reasonably well.
inside the 'after_sign_in_path_for' the current_user.last_sign_in_at.nil? will not work since it is alerady after the first sign-in. However this will work
if current_user.sign_in_count == 1
# do 1 thing
else
# do another thing
end