Experimenting with Devise 3.0 for Rails 4 and thinking about integrating into an existing app to replace my own Authentication system.
My own users_controller communicated with an API (Stripe) during registration and I'm not sure how I would include this functionality if I were to use Devise?
Am I suppose to override/extend the Devise Controller in some way? Any help would be much appreciated.
For future googlers: it is possible to call super in an action of inherited controller with block and do whatever you want, without overwriting Devise code:
class Users::RegistrationsController < Devise::RegistrationsController
def create
super do |user|
NotificationMailer.user_created(user).deliver
end
end
end
You can define the actions in the your controller and call super inside it, which will include the functionality of the corresponding devise action. And you can also add your own functionality into it.
Eg: Consider the Registrations controller (Registrations Controller)
Here, you can use the devise's create action code of Registration Controller in you own create action of Registration Controller and add your functionality as commented in the code.
class RegistrationsController < Devise::RegistrationsController
.
.
.
def create
build_resource(sign_up_params)
if resource.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_up(resource_name, resource)
# Here the user successfully signs up, so you can use your stripe API calls here.
respond_with resource, :location => after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
respond_with resource
end
end
end
Related
I have a create method in my RegistrationsController, which inherits from Devise::Registrations controller. It is supposed to call Stripe and if the creation of a customer is successful, it saves the user and sends a confirmation email, which is handled by '#create' in Devise. If the call to Stripe fails, it is supposed to set a flash and not save the user or send an email, i.e. suppress the Devise 'create' method. The method works fine if the call to Stripe is successful, but if it is not successful, the user is still saved and the confirmation email is still sent.
class RegistrationsController < Devise::RegistrationsController
def create
super
#user = resource
result = UserSignup.new(#user).sign_up(params[:stripeToken], params[:plan])
if result.successful?
return
else
flash[:error] = result.error_message
# TODO: OVERIDE SUPER METHOD SO THE CONFIRM EMAIL IS
# NOT SENT AND USER IS NOT SAVED / EXIT THE METHOD
end
end
I have tried skip_confirmation!, this just bypasses the need for confirmation. resource.skip_confirmation_notification! also does not work. I have also tried redefining resource.send_confirmation_instructions; nil; end; My thought was to exit the create method altogether in the else block. How can I exit the create method or suppress 'super' in the else block, or would another approach be better? Thanks.
By calling super at the top of your override, the whole registration process will take place, signing up your user, and only then executing your code.
You need to override Devise's registrations_controller.rb create action code by copy and pasting the whole and inserting your call like this:
class RegistrationsController < Devise::RegistrationsController
# POST /resource
def create
build_resource(sign_up_params)
# Here you call Stripe
result = UserSignup.new(#user).sign_up(params[:stripeToken], params[:plan])
if result.successful?
resource.save
else
flash[:error] = result.error_message
end
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
set_minimum_password_length
respond_with resource
end
end
end
Notice that resource.save is only called if result.successful?.
I created a custom Registration controller for Devise. I inserted a param in my form that says if it's true I should do some extra stuff after registration, for example, create a related company for the user.
class RegistrationsController < Devise::RegistrationsController
before_filter :authenticate_user!, :only => :token
def new
super
end
def create
super
reg_type = params['reg_type']
if reg_type=='1'
current_user.create_default_company!
end
end
def update
super
end
end
However, my current_user is coming up nil.
undefined method `create_default_company!' for nil:NilClass
What can I use to reference the current_user immediately after super is called, which creates the user.
Params:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"E1urosfkmekwweklo/HZaEVrrmxQVKO9E=", "user"=>{"name"=>"", "email"=>"4#Gmail.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Sign up", "reg_type"=>"1"}
I am passing the parameter, using two links:
new_user_registration_path(reg_type: '0')
new_user_registration_path(reg_type: '1')
The action of the create depends on which link the user chooses.
You can check this reg_type param in after_create callback and then create default company if it is equal to '1'.
If you open devise gem , you will find create method as
(devise-3.2.4)
def create
build_resource(sign_up_params)
if resource.save
yield resource if block_given?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
respond_with resource
end
end
you should first check which version of devise you are using then you can override that method in your controller.
In short using resource instead current_user may solve your problem, but it may generate inappropriate result as you will only assigning attributes (not saving) after rendering template.
You should be able to access the new user by passing a block to super:
def create
super do |resource|
if params['reg_type'] == "1"
resource.create_default_company!
end
end
end
This is explained (well, at least mentioned) in the docs under "Configuring controllers".
The new user is accessible via the block parameter resource, although it might not be saved at this point.
Firstly, thank you for all the comments. My solution was to use andrey deineko suggestion with a after_create call back. I added an attr_accessor to reference the permission, which I added to the form as a hidden field.
I used..
class User < ActiveRecord::Base
attr_accessor :reg_type
after_create :create_default_company!, if: :is_reg?
def is_reg?
#reg_type == '1'
end
...
end
It seems to work well. thank you for all the recommendations.
I'm trying to figure out how to use wisper with Devise.
When a new user account is registered, I want to create some sample data for that user.
So in my user model I would have:
include Wisper::Publisher
after_create :notify
def notify
publish(:user_created, self)
end
and my listener would be something like:
class SampleDataCreator
def user_created(user)
user.widgets.create!(name: "Your First Widget")
end
end
But I can't figure out how to tie this into Devise. How can I configure the SampleDataCreator to listen for events from the Devise user model?
UPDATE
I've tried attaching the listener in the Devise registration controller as follows:
class RegistrationsController < Devise::RegistrationsController
def create
super do |resource|
resource.subscribe(SampleDataCreator.new)
end
end
end
but it seems the listener never gets triggered.
UPDATE 2
I realised the above approach wasn't working because the record is saved before yield is called. It seems tricky to hook into Devise before this point, so instead I overrode the whole method:
def create
build_resource(sign_up_params)
resource_saved = resource.save
yield resource if block_given?
resource.subscribe(SampleDataCreator.new) # <-------------------- my addition
if resource_saved
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
respond_with resource
end
end
This now works, although it's not very elegant.
UPDATE 3
I found a much simpler way, I can just hook into build_resource:
class RegistrationsController < Devise::RegistrationsController
def build_resource(sign_up_params)
super.subscribe(SampleDataCreator.new)
end
end
I would subscribe the SampleDataCreator globally.
I saw a complex project where the listeners were unsubscribing and subscribing all the times. It was almost chaotic. I would recommend to avoid the subscribe/unsubsribe dynamics.
In routes i have the root-path pointing "home#index" but when i try to override that with after_sign_up_path_for keeps redirecting me to the root path when I sign in or sign up. I have tried to put it in both in devise subclassed controller and application_controller, but it didn't work. What do I need to do here?
Application controller
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_up_path_for(resource)
show_cities_path(resource)
end
end
registration controller
class RegistrationsController < ApplicationController
def after_sign_up_path_for(resource)
show_cities_path(resource)
end
end
routes
root :to => "home#index"
If you also have the Confirmable module enabled, you have to override after_inactive_sign_up_path_for since a new sign-up is "inactive" until it's confirmed. after_sign_up_path_for doesn't seem to get called when Confirmable is active.
Although I am late to the game, I just ran into this problem and had trouble finding the solution.
If you are using your own RegistrationsController to customize Devise, then you need to add the after_sign_up_path_for(resource) method to that controller instead of ApplicationController.
In registrations_controller.rb:
private
def after_sign_up_path_for(resource)
new_page_path
end
https://github.com/plataformatec/devise/blob/master/app/controllers/devise/registrations_controller.rb
I struggled with this problem until realizing I had forgotten to declare that I was overriding devise's registrations controller. In my case, I'm using devise with the :user resource, so I added this to routes.rb:
devise_for :users, :controllers => {:registrations => "registrations"}
After that, the redirect that I specified in after_inactive_sign_up_path_for worked.
Override devise registrations controller has a more complete discussion on this topic, with alternative ways of declaring overrides.
Have you checked your show_cities_path exists, by executing rake routes? Might be worth having a look at https://github.com/plataformatec/devise/wiki/How-To:-Change-the-redirect-path-after-destroying-a-session-i.e.-signing-out
Actually, we can view the source code of devise to solve the problem and it's easy.
devise-3.4.1 $ vim app/controllers/devise/registrations_controller.rb
# POST /resource
def create
build_resource(sign_up_params)
resource_saved = resource.save
yield resource if block_given?
if resource_saved
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
#validatable = devise_mapping.validatable?
if #validatable
#minimum_password_length = resource_class.password_length.min
end
respond_with resource
end
end
As code show:
if resource.active_for_authentication?
...
respond_with resource, location: after_sign_up_path_for(resource)
else
...
respond_with resource, location: after_inactive_sign_up_path_for(resource)
I've just blown about 2 hours on this, but LiveReload was my issue. I was being redirected successfully but LiveReload was picking up the change on development.sqllite and overriding the request.
If using OmniAuth custom callback controllers with Rails 5.2:
In my particular case, the after sign up path was not working: but I was using OmniAuth with a custom callback controller, which was invoking the after_sign_in_path_for rather than the former:
def google_oauth2
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
# Here's the guilty line your honour:
sign_in_and_redirect #user, event: :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, kind: "Google") if is_navigational_format?
else
session["devise.google_oauth2_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
........And the sign_in_and_redirect path redirects to the after_sign_in_path_for method. So I generated a new devise controller for sessions and simply overrode that method. problem solved!
I'm trying to redirect users that have failed the sign up form (e.g. they entered a username that is already taken, they left a field blank, etc...)
I have custom failure set up for users that fail the sign in form, code below:
class CustomFailure < Devise::FailureApp
def redirect_url
root_path
end
def respond
if http_auth?
http_auth
else
redirect
end
end
However, I've been stuck on how to set this up for sign up failure. Ideally I would just like to redirect them back/to root_path, any ideas? Thank you!
You will probably need to subclass Devise::RegistrationsController and override the create action. Just copy over the create method from here and modify the redirect on failure to save.
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def create
# modify logic to redirect to root url
end
end
Change your routes to tell Devise to use your controller:
# config/routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}
It's a bit tedious to modify certain parts of devise to suit your needs and I suspect it's because the gem does a good job to cover most common cases. However, edge-cases for use of devise are a lot and your question points to one of them. I had to do something similar, that is, make sure devise redirects to a specific page when a user does one of the following:
submits form on an empty form
submits an already existing email.
Below is how I handled it.
First, create a controller called RegistrationsController that inherits from Devise::RegistrationsController like so:
class RegistrationsController < Devise::RegistrationsController
end
Inside this controller you will have override the create method in devise. Go to the devise github page here, https://github.com/plataformatec/devise/blob/master/app/controllers/devise/registrations_controller.rb to view the create method and copy the code in that method. Then create a private method to override the returning statment of the last block of the if statement. Your controller should look like so,
class RegistrationsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
resource.save
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
set_flash_message! :notice, :signed_up
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
set_minimum_password_length
response_to_sign_up_failure resource
end
end
private
def response_to_sign_up_failure(resource)
if resource.email == "" && resource.password == nil
redirect_to root_path, alert: "Please fill in the form"
elsif User.pluck(:email).include? resource.email
redirect_to root_path, alert: "email already exists"
end
end
end
It should work.
Tip:
To keep flash error messages add this line before the redirect_to in your override
resource.errors.full_messages.each {|x| flash[x] = x}
So in your registrations_controller.rb :
def create
build_resource(sign_up_params)
if resource.save
yield resource if block_given?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
resource.errors.full_messages.each {|x| flash[x] = x} # Rails 4 simple way
redirect_to root_path
end
end
In config/routes.rb:
devise_scope :user do
get '/users', to: 'devise/registrations#new'
end