Custom Devise controller - ruby-on-rails

I would like to customize my registrations controller for Devise in Rails. I understand that you must create a controller like this:
class AccountsController < Devise::SessionsController
def create
super
end
end
Well, that's all very good. But then let's say I want to fully control what happens in my #create action. How do I do that? How do I manually create a model and pass it all the params? Would Account.create(params[:account]) handle it smoothly? Is there some internal stuff going on I should know about or is my only option to call #super inside the action?

As long as you fulfil your required fields you can call Account.create in your example, I'm pretty sure the default Devise required fields are login, password and password_confirmation
We do this in a CRUD screen for creating devise users,
#admin = Admin.new(params[:admin])
if #admin.save
redirect_to admin_admins_path, :notice => 'New Administrator has been added'
else
render :action => "new"
end
and you don't want to extend the Devise session controller, a normal controller extending ApplicationController is fine or you can extend Devise::RegistrationsController and overwrite the methods you want to tweak in a registrations_controller.rb file

You can also have a look at the source on Github, if you want to be sure you're overriding things properly, and be sure you're not missing any processing...
https://github.com/plataformatec/devise/tree/master/app/controllers

Related

Overriding devise registration controller for redirect

Devise functionality needs to be customized and the the RegistrationsController is created.
However, the default set-up for the create action is
super do |resource|
end
which in itself is a bit of a black box, as it goes to the superclass. It obvious is wired up for redirection. Thus:
super do |resource|
[...]
if #user.save?
redirect_to some_user_attribute_path
else
redirect_to a_parameter_based_path
end
end
is not possible as it will naturally create a
AbstractController::DoubleRenderError in Users::RegistrationsController#create
Devise wikis only deal with successful actions or all-encompassing approaches.
It is a goal to avoid ApplicationController methods, as this use-case has very specific behaviours for only user creation according success or failure (in practice the return is to the same page, but in the case of the failure, is defined via a params[:company][:id] value in lieu of #user.company_id
How can this be achieved?
I think you should override the method completely since you don't want the redirection handling that is after the yield. So, you could do this:
class RegistrationsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
if resource.save?
redirect_to some_user_attribute_path
else
redirect_to a_parameter_based_path
end
end
end
Note: If you need to sign up the user or any other stuff that devise does, you should copy it from the original method

Devise Session Controller. Custom routing on failure

Right now I'm extending the Devise sessions controller. Everything is working fine except that when the password is typed wrong or it can't find the user. it tries to redirect_to the sessions#new. I do not want that for my case I want it to redirect to a custom route because this is coming from a different view than the sessions new view. I know about after_sign_in_path_for but I'm not sure that is what I want because my particular case is the warden.authenticate! method. In the auth_options there is a recall hash. That is where I want to customize my route. Here is my sessions controller.
Session Controller
class SessionsController < Devise::SessionsController
def create
super do |user|
if params[:user][:invitation_id].present?
invitation = Invitation.find(params[:user][:invitation_id])
if !user.accounts.exists?(id: invitation.account.id)
user.accounts << invitation.account
flash[:tip_off] = "You now have access to your project \"#{invitation.account.name}\""
else
flash[:tip_off] = "You are already a member of #{invitation.account.name}"
end
cookies[:account_id] = invitation.account.id
invitation.destroy
user.save
end
end
end
end
As I stated before this works fine I just need a custom routes upon failure of password or email. Right now it goes to sessions#new, that does not work for my case. Any help would be great! Thanks
You can override recall value on auth_option method to redirect to another route on failure.
def auth_option
{ scope: resource_name, recall: "#{controller_path}#another_new_path" }
end

Devise's current_user nil in ApplicationController but not in a different controller (using Simple Token Authentication)

I have a Rails 3.2.22 app running in production for +1 year which uses Devise to authenticate users.
I'm trying to implement token authentication, so I can send transactional e-mails with URL params that can log in the user automatically, using a Gem named Simple Token Authentication https://github.com/gonzalo-bulnes/simple_token_authentication
After following all the instructions, I replaced before_filter :authenticate_user! in my controllers with acts_as_token_authentication_handler_for User.
The gem has integration with and a default fallback to Devise, so devise doesn't need to be called in the controllers anymore; if the token is missing from the params (or wrong), Devise will take over.
In my tests, if I add this line to ApplicationController, everything works fine and I can log in users using the authentication_token= secret the gem generates.
But I don't need auth for ApplicationController, I need it for other controllers (like DashboardController), url being /dashboard
If I put acts_as_token_authentication_handler_for User in that controller (replacing Devise's call), I get the most bizarre of situations.
Using binding.pry, I can confirm that current_user is correctly set during the loading of the template.
But there comes a point in the template where it uses #last_emails, which is defined inside a method in ApplicationController.
Using binding.pry, I can confirm current_user is nil there.
This is the code:
class DashboardController < ApplicationController
layout 'material'
acts_as_token_authentication_handler_for User
And in ApplicationController:
class ApplicationController < ActionController::Base
layout 'omega'
before_filter :populate_last_contacts_for_menu
private
def populate_last_contacts_for_menu
if current_user
#last_contacts = Contact.where("user_id" => current_user.id).where('blocked != ? or blocked is null', true).last(10).reverse
end
end
Funny thing is: using binding.pry, like I said, I can check that current_user is defined in the template (which means sign_in was a success). It even is defined in the better errors console. But, if I go to homepage, I see that user is not logged in ...
I've looked all over the web for this: read all the issues inside the Gem's github and all posts in SO about current_user being nil, but no light at all.
My devise_for :users is not inside any scope in routes.rb and, as I said, I have many calls to current_user all over the app and this is the first time I have issues with Devise.
When you call the acts_as_token_authentication_handler_for directive in the DashboardController it declares some before_filters for the controller to authenticate a user.
But the problem is that when you inherit rails controllers, at first, filters of a parent controller are executed, then filters of a child controller.
The parent controller is ApplicationController. At the moment when it's populate_last_contacts_for_menu filter is called, the user is not authentacated, because the authenticating filters given by the acts_as_token_authentication_handler_for directive have not called yet, they are declared in the child controller.
Possible solutions:
1) Try to append the populate_last_contacts_for_menu filter:
append_before_filter :populate_last_contacts_for_menu
I am not sure it will work in your case, but you can try and find it out.
2) Call the acts_as_token_authentication_handler_for directive in the ApplicationControoler and somehow skip it for the controllers that don't need it. (I don't like this way, but it may help if the first one will not work. )
3) Move the populate_last_contacts_for_menu filter logic into helpers. I think it is the best solution. This logic doesn't belong to a controller. When requests are not 'get', this filter executes for nothing, because you don't need to render views in that case.
module ApplicationHelper
def last_contacts
#last_contacts ||= if signed_in?
Contact.where("user_id" => current_user.id).where('blocked != ? or blocked is null', true).last(10).reverse
else
[]
end
end
...
end
# View:
<% if last_contacts.present? %>
....
<% end %>

What's the point of the Devise Registrations controller?

I presume there's some value-add from using the Devise Registrations controller but I haven't yet managed to figure it out.
Other than signing the user in after they're created, why would you use the Devise Registrations controller rather than simply having
class UsersController < ApplicationController
...
def new
end
def create
#user = User.create params[:user]
sign_in :user, #user
redirect_to... # whatever's next
end
end
What does the core Devise Registrations controller do that the Users controller doesn't?
Take a look for yourself
https://github.com/plataformatec/devise/blob/master/app/controllers/devise/registrations_controller.rb
If you think it's not worth it, then don't use it.

About overriding Devise or Clearance controllers

Since authentication gems such as Devise or Clearance uses their own built in controllers, I have a few questions when overriding them. Everytime I've tried to override it, something seems to go wrong and I don't know what it is exactly that caused the error.
For example, to create a new user controller with Devise I understand I have to create a controller like this:
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
All good. Now let's say I want to add certain things to the def new parts of the controller.
1.) To leave the def create part of the controller alone, I have to put in
def create
super
end
Is that right? Or do I even need to reference it in the new controller at all?
2.) If I type
def new
#my custom code here
end
Does that replace the def new part of the original Devise controller, or does it just add to it? Meaning to say, do I also have to put in
resource = build_resource({})
respond_with_navigational(resource){ render_with_scope :new }
which is the default behavior for the def new part of the Devise registrations_controller.rb?
3.) There's a filter in Devise that prevents you from signing up if you're logged in, but I need to override this. How do I do this? I'm guessing it has something to do with the prepend_before_filter :require_no_authentication, :only => [ :new, :create, :cancel ] part of registrations_controller.rb, but I'm not too sure.
The same questions apply to Clearance, although with slightly different routes and files.. (I'm asking for Clearance too because I haven't decided which authentication gem to use yet -- Clearance appeals to me because of the lightweight code, but Devise has additional features that I would need too).
1) That's correct.
2) If you want to call the parent's logic, you can call super at the appropriate point in your sub-class logic.
3) If you override the RegistrationsController, you can call skip_before_filter :require_no_authentication. This should skip it entirely, so if you need the before filter in certain conditions, you would have to add another before_filter.

Resources