How do I create a custom flash message for Devise? - ruby-on-rails

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 %>

Related

Undefined local variable or method 'current_user' for Namespaced Controller

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.

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 %>

Using a different layout file for devise login

I want to use a different layout file for logging into devise.
I've tried a static page but I get the error undefined local variable or method 'resource'.
Currently, in my application controller I have:
layout :layout_by_resource
protected
def layout_by_resource
if devise_controller?
"signin"
else
"application"
end
end
The problem with this is that every devise view uses the layout file 'login', which isn't good because I currently use devise's edit registration form for a account page.
Anybody know the best way to use a different layout file for signing into devise?
You might need to do two things to make this work.
1) Create seperate controllers inheriting from the Devise controllers, with your stated layout call. Instructions
2) To customize the views themselves further even copy over the views. Instructions Might not be nescessary.
Regarding 1) Controller(s): If you just want to customize login, you'd need to target the sessions controller.
# app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
layout :layout_for_action
protected
def layout_for_action
if params[:action] == '...' # See what the action is called internally beforehand
"signin"
else
"application"
end
end
end
You then need to instruct Devise to use your controller in config/routes.rb:
devise_for :admins, :controllers => { :sessions => "sessions" }
You can run this command
rails generate devise:views
This will generate devise views files for you to customize your layout.

How to DRY up controller code which involves views code as well?

In my Rails 3 app, I have a number of models with a boolean field disabled. In controllers for these models, I use a custom action disable to toggle the disabled field using Ajax.
Example (For client),
# routes.rb
resources :clients do
member do
get :toggle_disable, to: 'clients#disable', as: :disable
end
end
# clients_controller.rb
def disable
#client = Client.find(params[:id])
#client.update_attribute :disabled, !#client.disabled
render 'clients/update_client', format: :js
end
# update_client.js.erb
$('#client-<%= #client.id %>-details').html("<%= escape_javascript(render 'clients/client', client: #client) %>");
I have this code for at least ten resources in my application.
QUESTION
How do I go about DRYing up this code and add actions for these boolean fields dynamically? I could have gone with creating a parent controller or module but I am not sure how will I take care of the views code.
I should be able to do something like this
#clients_controller.rb
class ClientsController < ApplicationController
add_toggle_action :disable
end
Two main ways to share methods:
inheritance: define your action in ApplicationController
mixins: add your method in a module and include the module in the appropriate controllers
Since you want only some controllers to get the method, I'll head towards mixin.
Your controller action must use a view with a full path, not relative, something like:
render '/shared/clien/update', format: :js
Lastly, you'll have to define all routes.

Trouble using a 'before_filter' method and rendering partials

I am using Ruby on Rails 3 and I am trying to understand the behavior of the before_filter method in a controller.
In my controller I have
class UsersController < ApplicationController
before_filter :authorize
def show
...
end
end
If I browse, for example, the page http://<my_web_site>/user/1 (that loads the users/show.html.erb view file populated of data from the User with ID 1) the before_filter works as well. That is, the authorize method does what it must do.
If I render the users/show.html.erb view file as a template for another controller (example: the PostsController) this way
# This code is in the `post/show.html.erb` file
<%= render :template => "/users/show", :locals => { :user => #user } %>
the before_filter doesn't work. That is, the authorize method seams do not run.
Why?! There is a reason for that behavior or I am wrong somewhere?
UPDATE (after the #brad comment)
Are you rendering that view as a
partial template from within the users
controller? If not the before_filter
won't apply
If it is as #brad say in his comment, how can I make the before_filter to work rendering that view for another controller than UsersController?
Move authorize method to ApplicationController
Add before_filter to each controller where you want to check user authorization.
When you render a template or a view file/partial, it is not really treated as a request on your controller, hence the filters dont apply.
Firstly you should understand the routing in the Rails. When you type in the browser http://<my_web_site>/user/1 then it goes to route file, after this to proper controller's action, after that controller initiate the render view. And controller has these callbacks, when some action is initiated then these callbacks should act before or after controller's action. So in your case you're calling partial template without any controller's involving
before_filter applies to controller actions, not rendering actions.
One solution, then, is to abstract your authorization logic out into a helper that can be used when you're rendering your partial:
if authorized?
render :partial => 'users/show'
end
Another solution is to implement authorization at the model level, using something like the declarative_authorization gem (https://github.com/stffn/declarative_authorization)

Resources