Ruby on Rails layouts...except and only bug - ruby-on-rails

I have a controller with the following layout logic
layout 'sessions', :except => :privacy
layout 'static', :only => :privacy
The issue is that Rails seems to ignore the first line of code and the layout "sessions" is not applied for any actions. It simply thinks to render the static layout for privacy and no layout for the rest.
Anyone know how to fix this?

The reason this doesn't work is because you can only have global one layout declaration per controller. The :only and :except conditions just differentiate between actions that should get the specified layout and the ones that are excluded get rendered without a layout. In other words, a layout declaration always affects all actions that use default rendering.
To override you simply specify a layout when you render like one of the following examples inside an action:
render :layout => 'static'
render :action => 'privacy', :layout => 'static'
render :layout => false # Don't render a layout

Another option is to define a method for your layout call, like so:
layout :compute_layout
and then
def compute_layout
action_name == "privacy" ? "static" : "sessions"
end
However this is really only useful when you want to determine the layout at runtime based on some runtime parameter (like a variable being set). In your example, that does not seem to be necessary.

You can just specify layout :static where you need it.

You can also dynamically determine the layout within your controller:
class SampleController < ApplicationController
layout Proc.new { |controller| (controller.action_name == 'privacy') ? 'static' : 'sessions' }
...
end
If more actions within the controller are sharing the same layout:
class SampleController < ApplicationController
layout Proc.new { |controller| ['action1', 'action2'].include?(controller.action_name) ? 'layout1' : 'layout2' }
...
end
Source: https://guides.rubyonrails.org/layouts_and_rendering.html#using-render

Related

Rails Set Layout Based on URL Params

So I am trying to change the layout of a view based on url params.
So far, I figured out I have to set the layout in the controller. In my controller under the show action I have:
if params['iframe'] == 'true'
render :layout => 'vendored'
end
The layout 'vendored' exists in views/layouts. I am getting the dreaded rendering multiple times. Here is the rest of the show action in my controller:
def show
#event = Event.find(params[:id])
#user = current_user
#approved_employers = current_user.get_employers_approving_event(#event) if user_signed_in?
respond_with(#event)
The problem is that I don't see another render. I don't see another one in the entire controller. Of course, there is a render somewhere because it is rendering my default application layout, is that causing the problem? I read in the rails docs that I can add
and return
to the end and that should fix the problem, but not sure where to put that since the two renders are not next to each other. I also don't see any other redirect_to's either. Where should I be looking for this other render? Is that the problem?
Alternatively, I think this is easier to understand:
class YourController < ApplicationController
layout :iframe_layout
private
def iframe_layout
params['iframe'] ? "vendored" : "application"
end
end
See this answer. For your case:
before_filter :set_layout, :only => [:show]
private
def set_layout
self.class.layout ( params['iframe'] == 'true' ? 'vendored' : 'application')
end

bypass application.html.erb in Rails app

I have an application.html.erb file which sets out layout for every pages in my app (header, footer etc.) like a typical Rails app.
However, I would like to have a landing page which I don't want to use this file. How should I bypass application.html.erb?
Thank you.
Use
render :layout => false
or
render :layout => 'whatever'
in your action. If you are using a separate LandingController you simply can create a app/views/layouts/landing.html.erb which will be picked up or you can override the layout via
class LandingController < ApplicationController
layout 'whatever'
...
end
You can set a layout in your render function:
render {other arguments}, :layout => :homepage
You can also set that option to false to not use any layout at all.
You can do something similar if you want an entire controller to use a custom layout:
class MyController < ApplicationController
layout :homepage
#...
end
Hope that helps!
In the controller that renders the view, change the render to:
render :layout => false
You can read more about options to render and how to work with layouts at the Rails guide to render and layouts.

why does this cause :show to get no layout at all: layout 'admin', :except => [:show]

why does this cause :show to get no layout at all: layout 'admin', :except => [:show]
Is this the intended behavior? I'm forced to put a render :layout => 'application' in an otherwise empty def show end.
Shouldn't the show action default to the base layout?
This actually surprised me a bit and I had to go to the source to check. The answer is no, it does/should not default to the application layout. The conditions passed instead are used to determine if the action has a layout at all, hence the name of the mixed in method action_has_layout? (Rails 3).
I'd half expected it to behave like respond_to, which you can call multiple times to build up the conditions for different actions.
In any case you can easily handle this by sending a method to layout containing your logic (via a proc or a symbol referencing method), rather than defining an empty action simply to render a different layout.
For example:
layout :determine_layout
...
def determine_layout
# show gets application, the rest get admin
params[:action] == 'show' ? 'application' : 'admin'
# or, returning true would probably do it (and be more flexible in case
# the rest of your app swapped layouts to something other than application)
params[:action] == 'show' || 'admin'
end

undefined method `layout' for #<HelloController:0x101d7edb0> from Controller

I was trying to switch the layout using Ruby on Rails but I am getting the error: undefined method `layout' for #. I am using Rails 2.3.5 Am I missing an include?
Here is the code:
class HelloController < ApplicationController
def index
layout 'standard'
#message = "Goodbye!"
#count = 3
#bonus = "This is the bonus message!"
end
end
If you are using layout as such, it goes in the Class definition, not an action.
class HelloController < ApplicationController
layout 'standard'
def index
...
This is saying that you want to use this layout for rendering all actions in this controller.
If you want a specific layout for that one action, you would use render :layout as so:
def index
#message =
...
render :layout => 'standard'
end
EDIT: the docs (towards the bottom) seem to suggest that you need to specify the action, as well as the layout when using a specific layout for one action. I don't remember that being the case, but if it is, the above would be render :action => 'index', :layout => 'standard'.

Is there a variable in Rails that equates to the template that is being rendered?

I can do request.path_parameters['controller'] and request.path_parameters['action'], but is there anything like request.path_parameters['template'] so I can discern which template file (such as index.html.erb) is being rendered?
I'm writing a method that automatically sets the body id to the template being rendered, for easy css manipulation:
class ApplicationController < ActionController::Base
...
after_filter :define_body_selector
...
def define_body_selector
# sets #body_id to the name of the template that will be rendered
# ie. if users/index.html.erb was just rendered, #body_id gets set to "index"
#body_id = ???
end
...
If you have different template files you could use content_for in them (check out guide about layouts and templates) to set id in layout file, or just stick to params[:action] (it should be enough -- choice of template is based on action called).
You can make universal before_filter for all (or not all) actions with
before_filter :set_id_for_body, :only => [...]
def set_id_for_body
#body_id = params[:action]
end
Always think how to keep your code DRY!
EDIT:
You could define hash that will link actions with matching templates:
ActionClasses = {
:update => "show",
:show => "show,
:new => "new",
:edit => "new",
...
}
Within your layout file just add
<body id="<%= ActionClasses[params[:action]] %>">
EDIT:
It is possible to access template via ActionBase::template method, but it won't work the way you would like it to. If you call filename or name method in layout file, you will get path to layout file, not template. AFAIK It is impossible to check what template is being rendered, as multiple of them can be used to render single action.
#body_id = params[:action]

Resources