Multiple Level Nested Layout in Rails 3 - ruby-on-rails

I have an application with a global application layout file application.html.haml. I then have multiple "controller stacks": for our main site, our admin portal, and our business site. For each of these, controllers are within a module and all inherit from the same BaseController. Each stack has it's own layout file. Within the stack, some controllers have layout files as well.
I would like all views (unless otherwise specified) to render inside multiple levels of nested layouts : application, "stack", "controller".
For example, for the Site::BlogController#show action, I'd like rails to render:
/site/blog/show.html.haml inside /layouts/site/blog.html.haml inside /layouts/site.html.haml inside /layouts/application.html.haml
I am having difficulty understanding how to insert /layouts/site.html.haml into the stack. It appears as though automatically, rails will render the action inside the controller layout inside the application layout, however, I can't see how to "insert" layouts into the render stack.
Any help is greatly appreciated, however, I have read all the rails guides to no avail, so a link to http://guides.rubyonrails.org/layouts_and_rendering.html#using-nested-layouts will not really be helpful.

I reread the link i posted ( http://guides.rubyonrails.org/layouts_and_rendering.html#using-nested-layouts ) and realized I missed a key detail.
<%= render :file => 'layouts/application' %>
so, in Site::BaseController I have a call to layout 'site' and in /layouts/site.html.haml I have
= content_for :footer do
-#Content for footer
= render :file => 'layouts/application'
Then in Site::BlogController which extends Site::BaseController I have layout 'site/blog' and in /layouts/site/blog.html.haml I have
=content_for :header do
%h1 HELLO WORLD!
= render :file => 'layouts/site'
This then renders the layouts nested as described in the question. Sorry for missing this in my question. I should've read closer.

if you create a helper like this:
# renders a given haml block inside a layout
def inside_layout(layout = 'application', &block)
render :inline => capture_haml(&block), :layout => "layouts/#{layout}"
end
then you can define sublayout like this:
= inside_layout do
# nested layout html here
= yield
these layouts can be used like normal layouts.
more: http://www.requests.ch/blog/2013/10/30/combine-restful-rails-with-nested-layouts/

I've done similar, but only used 1 level of sublayouts. Can easily be tweaked to allow multiple levels.
In controllers/application_controller.rb:
def sub_layout
nil
end
In controller (for example blog_controller.rb):
def sub_layout
"blog"
end
In layouts/application.html.erb rather than <%=yield%>:
<%= controller.sub_layout ? (render :partial => "/layouts/#{controller.sub_layout}") : yield %>
Make a partial layouts/_blog.html.erb:
...code
<%=yield%>
...code
Repeat for other controller & sub layouts.
EDIT:
If you need to do this on a per-action basis:
def sub_layout
{
'index' => 'blog',
'new' => 'other_sub_layout',
'edit' => 'asdf'
}[action_name]
end

I guess the simplest way to do it is by adding this line of code in the parent of a nested layout:
((render "layouts/#{controller_name}" rescue nil)|| yield )
you could add as many nested layouts as you want by only changing the path directory of the next layout to be rendered.
note: make sure your nested layout is named _layoutname.whatever and that your nested layout has a yield inside

You can create a partial with a yield in it.
_my_sub_layout.html.erb:
<h3>I'm a sub layout and here's my content:</h3>
<%= yield %>
In some other view or even in your main layout application.html.erb render the partial as a layout:
<%= render layout: 'my_sub_layout' do %>
<p>I'm the sub layout's content</p>
<% end %>

Related

Rails 5: Rendering a partial to the application layout

Within views/layouts/application.html I have the line <%= yield %>. This line is ultimately replaced by views directly corresponding to controller actions without any problems.
In one of my controllers, I am trying to render a partial instead of the default behaviour:
def show
#service_groups = ServiceGroup.where(deleted_at: nil)
render partial: 'table', locals: {rows: #service_groups, headers: service_group_headers}
end
I'm using a partial in this way so that I can use the same basic table structure for various different database tables (across different controllers).
This render partial code doesn't seem to work with the <%= yield %> line in the application layout. The partial code is just rendered on its own without the surrounding layout.
Why is this?
How do I rectify the problem?
Please let me know if I should be handling this a different way.
Thanks.
(migrating from comments)
What if you created a show.html.erb for that controller and placed render :partial there? It should work.
Explanation: Partials are to be used from "big" views. So, they are not wrapped in the layout on purpose!

ruby on rails yield to new ruby layout

In a page I'm working on , i have overridden the application.html.erb because the layout is complex for pages like signup / login and some others.
So i created a new layout and I'm rendering it through the controller's action.
def new
render :layout => '../path/newlayout.html.erb'
...
end
Question is , can i use another layout as a "secondary" application.html.erb?
Can i <%yield%> to the newlayout.html.erb ?
As i searched further , i stumbled upon partials!
just create a file with the naming convention "_login.html.erb" in the same folder as your newlayout.html.erb
and just use <%= render 'login' %>
in the same position and way that you'd use <%yield%>

Render :layout is searching for partial instead of layout

I understand this is a bad idea, but from what I've seen in ApplicationControllers, using:
render :layout => "something" ...
Should render using a layout located at views/layouts/something.html.erb
However, when I am making this call from inside of a view, it errors out with:
Missing partial my_controller_name/something with ...
Searched in:
* "{path here}/app/views"
Which seems to me its looking for a partial, instead of a layout as I specified. Does anyone know what is going on with that?
A sufficient example small enough to reproduce it:
<%= render :layout => 'something' do %>
<div>Hello</div>
<% end %>
This is all under Rails vs 4.0.2
render works differently in controllers than it does in views. In controllers, it's primarily for rendering action templates, while in views, it's primarily for rendering partial templates. When you want to render a specific layout for an action, you have a few options, but all of them are in the controller.
If you want every action in a particular controller to use that layout, you can either specify layout 'something' in that controller (usually near the top) or for a ApplesController, you can create a new layout in app/views/layouts/apples.html.erb and this will automatically be used as the default layout for the ApplesController.
If you want just a single action in a controller to use that layout, you can use your render layout: 'something' inside of a controller action, where the action to render is implied to be the current action.
Links from the Rails docs:
Action Rendering
Partial Rendering
Nested Layouts

Rails 3, rendering a partial for the given controller if it exists?

In my application.html.erb layout for my app, I want to have a partial that renders if it exists for the given view. for example.
If the visitor is at http://example.com/users/show, I'd want the partial /users/_sidebar.html.erb to render.
But if the visitor were at say, http://example.com/user/locations/san_francisco, I'd want the partial /users/locations/_sidebar.html.erb to render.
So the thing here is that if there were no partial for that controller/action it would render some generic partial in my shared directory, and I'd rather not litter every single view with content_for blocks ya know?
Any ideas guys?
My solution is a bit different. Throw this in your application helper:
def render_partial_if_exists(base_name, options={})
file_name = ::Rails.root.to_s+"/app/views/layouts/_#{base_name}.html.erb"
partial_name = "layouts/#{base_name}"
else_file_name = ::Rails.root.to_s+"/app/views/layouts/_#{options[:else]}.html.erb"
else_partial_name = "layouts/#{options[:else]}"
if File.exists?(file_name)
render :partial => partial_name
elsif (options.key?(:else) and !options[:else].nil? and File.exists?(else_file_name))
render :partial => else_partial_name
end
end
Then in your view:
<%= render_partial_if_exists "page_#{controller.action_name}_sidebar", :else => "page_sidebar" %>
In an edit action, if "layouts/page_edit_sidebar" exists it renders it, otherwise it will render a standby "layouts/page_sidebar"
Sean Behan has a great post on exactly this:
http://seanbehan.com/programming/render-partial-if-file-exists/
I might move it to a helper and tweak it a bit to:
<%= render_sidebar %>
# This method could use either the rescue or the if file exists technique.
def render_sidebar
render(:partial => "/#{controller.name}/sidebar"
rescue
#default side bar
end

creating dynamic helper methods in rails

I am trying to create a bunch of dynamic helper methods like these:
show_admin_sidebar
show_posts_sidebar
show_users_sidebar
So far I have this in my helper.rb file:
#spits out a partial
def show_sidebar(name, show_sidebar = true)
#content_for_sidebar = render :partial => "partials/#{name}"
#show_sidebar = show_sidebar
end
def show_sidebar?
#show_sidebar
end
In my application layout file I have this: (NB - I'm using HAML):
- if show_sidebar?
= yield(:sidebar)
This allows me to say the following in my views:
- show_sidebar(:foo)
- show_sidebar(:bar)
And this renders the desired partial.
The problem with this is that I can only add one sidebar per page. So, I figure I need to have dynamic methods like: show_admin_sidebar, show_foo_sidebar.
So I have tried to do this:
def show_#{name}_sidebar(show_sidebar = true)
#name = name
#content_for_#{#name}_sidebar = render :partial => "partials/#{#name}"
#show_sidebar = show_sidebar
end
and then in my layout:
- if show_sidebar?
= yield("{#name}_sidebar")
But rails does not like this at all.
I have tried almost everything I can think of in my helper file and nothing works.
The reason I am using helper methods for this is because I want my content div to be 100% page width unless there is a sidebar present in which case the main content goes into a smaller div and the sidebar content goes into it's own..
If I can't get this working, then I can easily fix the problem by just adding the partials manually but I'd like to get my head round this....
Anyone got any experience with this kind of thing?
The entire approach to this was bizarrely overcomplicated, didn't follow Rails conventions at all, nor make the slightest bit of sense, and shame on prior respondents for enabling this approach instead of helping him to simplify. My apologies for being 13 months late with the answer.
Your controller should be deciding if a sidebar is to be shown or not, and setting an instance variable #side_bar_name to either nil or a sidebar name string. Then somewhere in shared view code, probably views/layouts/application.html.erb, you would have something as simple as this:
<% if #side_bar_name %>
<%= render :partial => "partials/#{#side_bar_name}" %>
<% end %>
Or better yet:
<%= render(:partial => "partials/#{#side_bar_name}") if #side_bar_name %>
If you want to use a helper (which is not a bad idea for keeping your code DRY and readable) it would basically be the same code, just moved into the helper.
<%= side_bar_helper %>
def side_bar_helper
render(:partial => "partials/#{#side_bar_name}") if #side_bar_name
end
What the controller does is up to you. It would probably do something like this:
if session[:show_side_bar]
# maybe use cookies instead of session, or store user preference in a database
#side_bar_name = session[:side_bar_name]
end
Here is a solution for you, however I wouldn't suggest too much metaprogramming:
#Add the following snippet to the proper helper module:
['admin','user','whatever'].each do |name|
class_eval{
"def show_#{name}_sidebar(show_sidebar = true)
#name = #{name}
#content_for_#{#name}_sidebar = render :partial => 'partials/#{#name}'
#show_sidebar = show_sidebar
end"
}
end
def show_#{name}_sidebar(show_sidebar = true)
That doesn't look like valid Ruby to me. Are you parsing and evaling this yourself or just throwing that right in the file and expecting it to work?

Resources