86

I'm trying to use a different/custom layout named "devise" for the sign_in action. I found this page in the devise wiki, and the second example even says you can do it per-action (in this case, sign_in action), but it shows no example of doing that. Someone on IRC told me I could try this:

class ApplicationController < ActionController::Base
  protect_from_forgery

  layout :layout_by_resource

  def layout_by_resource
    if devise_controller? && resource_name == :user && action_name == 'sign_in'
      "devise"
    else
      "application"
    end
  end
end

But it does not seem to be working as it's still loading the default application layout. I would appreciate any help.

Jorge Israel Peña
  • 36,800
  • 16
  • 93
  • 123

8 Answers8

98

Another way to apply custom layout for an action is as following.

According to How To: Create custom layouts "You can also set the layout for specific Devise controllers using a callback in config/environment.rb (rails 2) or config/application.rb (rails 3). This needs to be done in a to_prepare callback because it's executed once in production and before each request in development."

config.to_prepare do
    Devise::SessionsController.layout "devise"
    Devise::RegistrationsController.layout proc{ |controller| user_signed_in? ? "application"   : "devise" }
    Devise::ConfirmationsController.layout "devise"
    Devise::UnlocksController.layout "devise"            
    Devise::PasswordsController.layout "devise"        
end

Usually a layout distinction is made between pages behind login and pages which do not require authentication, so the above approach works most of the time. But I also experimented with using action_name helper to set a layout for a particular action and it worked like charm:

config.to_prepare do
    Devise::SessionsController.layout proc{ |controller| action_name == 'new' ? "devise"   : "application" }
end

I think this is the better and built in way to change the layout based on devise controller/action instead of creating a helper in ApplicationController.

Zeeshan
  • 3,462
  • 2
  • 25
  • 28
  • 3
    Also don't forget to restart the server every time you make a change in any file in the config folder, in this case config/application.rb for Rails3 or config/environment.rb for Rails 2, for the changes to take affect. – Zeeshan Aug 21 '11 at 17:13
  • **Beware** I tried this method in rails 3.1 and it makes the loading of assets from the assets folder significantly slower. This will not affect production servers, but when you have more than a few css/js files, you will notice it. – Gazler Feb 17 '12 at 16:55
  • in the above example, is it possible to configure layouts for separate devise resources (e.g., let's pretend we have two different types of devise users and each needs their own layout) – ckarbass Jun 30 '12 at 01:37
  • When I try this, I get an error that it is now trying to get a template from two locations. How do you get Rails to override Devise's previous settings? – Adam Grant Jun 28 '13 at 18:55
  • For those who missed it - Rails 3 setup is different - do this in: config/application.rb (rails 3). – Stone Jun 30 '13 at 03:28
  • I can't figure this out... I want all devise partials to be rendered inside my views/layouts/application.haml so I followed the documentation and added into application.rb config.to_prepare block, but my /users/sign_in simply renders the partial without any layout... Anyone had this problem? – luigi7up Mar 19 '14 at 21:33
  • Is this really better than using a `layout_by_resource`?? – Kevin Brown Apr 16 '14 at 02:05
67

I just created app/views/layouts/devise/sessions.html.erb and put my layout in there.

Josh
  • 679
  • 5
  • 2
  • 28
    Great solution! You can also put a layout in /app/views/layouts/devise.html.erb and have it apply to _all_ your devise views – Basti Jan 24 '14 at 17:36
45

I figured it out, but I'll keep this question here in case other people are curious.

It was a stupid mistake. The fact is sign_in is the path, not the action. Looking at the relevant source, I can see that the required action is new, i.e., creating a new Devise Session. Changing my above code's conditional to:

if devise_controller? && resource_name == :user && action_name == 'new'

Works beautifully.

Hope that helps someone out there.

Jorge Israel Peña
  • 36,800
  • 16
  • 93
  • 123
15

The by far simplest solution is to just create a layout called devise.html.haml in your app/views/layouts folder. and the Rails magic takes care of the rest.

app/views/layouts/devise.html.haml
John
  • 1,823
  • 2
  • 13
  • 14
9

Surprised to not see this answer anywhere, but you can also do this:

In routes.rb, change your devise config to look something like this:

  devise_for :users, controllers: {
    sessions: 'sessions'
  }

Then in app/controllers/sessions_controller.rb

class SessionsController < Devise::SessionsController
  layout 'devise', only: [:new]
end

This is especially useful if you need to do additional logic overrides in any of the Devise controllers.

streetlogics
  • 4,640
  • 33
  • 33
8

This is how I did it. I wanted a different layout if the user had to sign in, but a different layout if the user had to edit his/her profile.

I am using Rails 4.1.1

In the application controller, add this :

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  layout :layout_by_resource

  # Define the permitted parameters for Devise.
  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:firstname, :lastname, :email, :password, :password_confirmation)}
    devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:avatar, :firstname, :lastname, :email, :password, :password_confirmation, :current_password) }
  end

  def layout_by_resource
    if devise_controller? and user_signed_in?
      'dashboard'
    else
      'application'
    end
  end
end
Sankalp Singha
  • 4,461
  • 5
  • 39
  • 58
1

Here's a one-liner for those who want all devise actions to use a new layout:

class ApplicationController < ActionController::Base
  protect_from_forgery

  layout Proc.new { |controller| controller.devise_controller? ? 'devise' : 'application' }
end
vlad
  • 497
  • 4
  • 6
1

Just incase you didn't know, you can also use rake routes to see the routes in your rails app along with the action/controller they map to.

 new_user_registration GET    /accounts/sign_up(.:format)       {:action=>"new", :controller=>"devise/registrations"}
edit_user_registration GET    /accounts/edit(.:format)          {:action=>"edit", :controller=>"devise/registrations"}
                       PUT    /accounts(.:format)               {:action=>"update", :controller=>"devise/registrations"}
                       DELETE /accounts(.:format)               {:action=>"destroy", :controller=>"devise/registrations"}
Dty
  • 12,253
  • 6
  • 43
  • 61
  • Thanks, I actually did/do know about rake routes, I just hadn't thought for a second that 'sign_in' might not be the name of the actual action, I figured it would be, then I realized that it all revolves around sessions which is why it corresponds to the new action. – Jorge Israel Peña Feb 13 '11 at 06:16