I'm using Devise along with CanCan/Rolify for authentication for my rails app and I'm having a hard time understanding how to pass Devise methods to namespaced controllers.
I'm using a standard users model and controller for the main part of my app, everything on that side works well and good. However, I have set it up that if an account with a specific role (which is determined after registration) logs in using the main login page, it will redirect them to namespaced views and controllers.
Lets say that user_id: 3 logs in and is redirected to 0.0.0.0/external/dashboard, I can call Devise's current_user method on the dashboard page after redirection and returns the correct information. However, when I set a link_to for that user to be able to edit his profile at 0.0.0.0/external/users/3/edit I receive the following error:
Started GET "/external/users/3/edit" for 127.0.0.1 at 2017-08-31 21:40:59 -0400
ActionController::RoutingError (undefined local variable or method `current_user' for External::UsersController:Class)
routes
devise_for :users, controllers: { confirmations: 'confirmations', registrations: 'registrations' }
# External routes
namespace :external do
resources :properties, :clients, :orders, :deeds, :mortgages, :users, :owners, :dashboard
end
external/dashboard view
<%= user_name_helper(current_user) %>
user_name_helper (produces user edit link)
def user_name_helper(user)
if user.name.blank?
link_to "Update your profile here.", edit_external_user_path(user)
else
"Hello, #{user.name}"
end
end
Error happens when I click the link produced by this helper.
But <%= link_to fa_icon("sign-out", text: "Logout"), destroy_user_session_path, method: :delete %> works fine from 0.0.0.0/external/dashboard.
So I am confused as to why I can call current_user within the view for the External::DashboardController but can't call it within the External::UsersController. All namespaced controllers inherit directly from ApplicationController and I want to keep only using a single users model. I've searched through a bunch of different articles but can't seem to find an answer with a configuration similar to mine, so can anyone explain to me what I might be doing wrong and how to get this to work as well as allowing me to have the ability to use any other Devise methods in my namespaced views? Thanks in advance.
Welp, I figured it out. The issue was that I was calling a conditional before_filter in my external/ controllers as follows:
before_filter :client_only unless current_user.has_role? :admin
Once I removed that everything worked as intended. I still have the following before_filter in place which works.
before_filter :client_only
...
private
def client_only
redirect_to root_path unless current_user.is_client?
end
Not entirely sure why the conditional filter wouldn't work the way I originally had it so if anyone has any insight and feels like sharing then I'm all ears, thanks.
Related
I tried the following but it's not working. It's still using the default flash message.
class Users::SessionsController < Devise::SessionsController
after_action :custom_welcome, :only => [:create]
def custom_welcome
flash.notice = "Welcome back "+current_user.name+"." if flash.keys.include?(:notice)
end
end
Reference: https://github.com/plataformatec/devise#configuring-controllers
https://stackoverflow.com/a/5513172/148844
Docs say (in general):
1. Set custom flash messages with :key in locales: config/locales/devise.en.yml
2. Call DeviseController method set_flash_message(key, kind, options = {})
Example from devise/sessions_controller:
set_flash_message!(:signed_in, :notice)
This worked.
flash.notice = "Welcome back #{current_user.name}." if flash.key?(:notice)
You're overriding a devise controller, therefor you need to tell your app that you want to use that controller. Do the following in routes.rb (if you haven't done so already):
devise_for :users, controllers: { sessions: 'users/sessions' }
To expand on this, if you're still getting the default flash message, it's because Devise is still using the original controllers, by defining the customized controller you are telling it to use it.
Two ways:
Use your own layout for Devise pages (layout keyword with parameter in controller). In layout you can have separate partial to show your devise messages.
If your app is bit complicated and you don't want to introduce layouts, create devise controllers (it can be done by running devise rake command), then just have a flag in your flash.
For example:
flash[:devise] = true
Check for this flag in your partial view responsible for generating html for your flash messages:
<% if flash[:devise] %>
#...
<% else %>
# ...
<% end %>
I got the devise invitable installed and working. Trying to figure out how to redirect the user after he/she sent an invitation out. Right now it's redirecting me to the root. I thought you can just set your custom path in the method below but it didn't work. Thanks in advance if anyone know where to customize the path after invite sent.
def after_invite_path_for(resource)
new_profile_path
end
I stumbled upon your question because I was having the same issue. As far as I can tell the intended way for you to override after_invite_path_for is to override Devise::InvitationsController.
class Users::InvitationsController < Devise::InvitationsController
def after_invite_path_for(resource)
new_profile_path
end
end
routes.rb
devise_for :users, :controllers => { :invitations => "users/invitations" }
It would be nice if devise invitable worked like devise proper and you could override its after invite/accept paths in application controller. I modified devise_invitable to work that way and submitted a pull request. I'm not sure if it will be accepted or not, but you can have a look here: https://github.com/scambra/devise_invitable/pull/240.
If that feature is accepted, you could patch your current version of invitable to respect definitions of after invite/accept paths in application controller by putting this in an initializer:
#make invitable's path functions overridable in application controller
[:after_invite_path_for, :after_accept_path_for].each do |method|
Devise::InvitationsController.send(:remove_method, method) if ApplicationController.method_defined? method
end
Not sure if its a good thing... or the worse to do however you can put:
def after_invite_path_for(resource)
new_profile_path
end
in your application controller... seems to work OK!
i created a Devise with CanCan integration like told on:
http://starqle.com/articles/rails-3-authentication-and-authorization-with-devise-and-cancan-part-1/
http://starqle.com/articles/rails-3-authentication-and-authorization-with-devise-and-cancan-part-2/
now i have two resources for my User class. Devise and a RESTful resources :users.
as mentioned in the tutorial, i included in the RESTful edit_user_path a form for editing the rights for the user.
now i don't understand how i can restrict normal users to access that edit function and use devise edit function for that.
Is it possible to just restrict a user to
can :manage, User
but he still can manage devise controller?
Solved
Just can add an in ability.rb
can :assign_roles, User
and then in _form for RESTful edit
<% if can? :assign_roles, current_user %>
and then let Users edit either over RESTful _form or Devise form, doesn't matter then
Edit
_form.html.erb (or haml)
<% if can? :assign_roles, #user %>
may work too. depends on your controller. should work better since i have made a bit workaround to fit it to current_user
If you want to be sure that your abilities get checked also on controller level, you should add load_and_authorize_resource to it.
class ProductsController < ActionController::Base
load_and_authorize_resource
end
https://github.com/ryanb/cancan/wiki/authorizing-controller-actions
if you just show / hide the links in the view depending on the <% if can? %> method, a user might still type the direct link to e.g. edit action in the adress bar
Trying to redirect users to their associated 'home' page after successful login w/out nil'ing out stored_location_for(resource_or_scope)...which is giving me some endless redirect loops (pretty sure I've set it up wrong).
Regardless, I'm looking for a better approach...
Devise's docs state: After
signing in a user, confirming the
account or updating the password,
Devise will look for a scoped root
path to redirect. Example: For a
:user resource, it will use
user_root_path if it exists,
otherwise default root_path will be
used. This means that you need to set
the root inside your routes: root :to => "home"
I'm sorta a newbie...how does one go about generating this home_root_path for each user?
rDocs also mention:
-- (Object) after_sign_in_path_for(resource_or_scope)
The default url to be used after
signing in. This is used by all Devise
controllers and you can overwrite it
in your ApplicationController to
provide a custom hook for a custom
resource.
By default, it first tries to find a resource_root_path, otherwise
it uses the root path. For a user
scope, you can define the default url
in the following way:
map.user_root '/users', :controller => 'users' # creates user_root_path
map.namespace :user do |user|
user.root :controller => 'users' # creates user_root_path
end
but these just gives me undefined local variable or methodmap' for #ActionDispatch::Routing::Mapper:…` errors.
If you would like to redirect using a route in answer to your question below:
how does one go about generating this home_root_path for each user?
This will work if you place it in your config/routes file. It will redirect a user to articles#index after (for example) a successful confirmation.
# Rails 4+
get 'user_root' => 'articles#index', as: :user_root
# Rails 3
match 'user_root' => 'articles#index', as: :user_root
See Devise: Controllers Filters and Helpers
You could try something like this:
application_controller.rb:
def after_sign_in_path_for(resource_or_scope)
# return home_page_path for user using current_user method
end
Dug around a bit to figure out the same thing. #polarblau's answer is correct,
def after_sign_in_path_for(resource_or_scope)
user_info_path(current_user)
end
where user_info_path is the path to the page you wish to display.
Also, I would allow this to fall back to super just in case, although I'm not entirely sure if that is necessary...
def after_sign_in_path_for(resource)
if resource.is_a(User)
user_info_path(resource)
else
super
end
end
I spent several hours trying to get the same functionality, and this is the code that ended up working for me:
def after_sign_in_path_for(resource)
current_user
end
If I ever tried current_user_path, I always got undefined local variable or method current_user_path errors.
Also, I'm using Rails 3.2.8 and Devise 2.1.2.
Hope that helps.
Based on #SnapShot answer, this worked for me. I'm using multiple devise models, trying to redirect back to the users profile edit page.
get 'user_root', to: redirect('/users/edit'), as: :user_root
ROR 7 answer
get '/users/home' => 'application#test', as: :user_root
I'm trying to add an action link to an active scaffold controller using
if current_user.can? :update, Post
config.action_links.add 'export', :label => 'Export', :page => true
end
but whatever controller I try to use, I always get undefined method current_user
So, how can I check if the logged user can/cannot do something?
I've even tried with
def ability
#ability ||= Ability.new(self)
end
delegate :can?, :cannot?, :to => :ability
as suggested here, but without success.
What's wrong with this?
Note that in the normal case ActiveScaffold config is done in the class not the instance so there is no current_user set because there is no request yet.
current_user is a typically defined method, but it is one that you must add. it is not supplied by rails. So, you need a current_user method in your ApplicationController. There are tons of examples but I would look at the authlogic example app on github