Rails 3 vs 2.3.5 render oddities with :partial and :layout - ruby-on-rails

I've come across an oddity that I can't quite explain with regards to Rails 3 and rendering partials with layouts (from the controller). I'm hoping someone can provide a little insight into what's happening.
First off, we'll call this controller a "legacy" controller. It's been around for a long time and is doing a lot of things wrong, but I'm not looking to refactor it at this point so I'm trying to find ways to work with what we have.
The new action is something like this (in the BarsController)
def new
if something
render :partial => "foo", :layout => "bars"
elsif something_else
render :partial => "foo2", :layout => "bars"
elsif something_else_else
render :partial => "foo3", :layout => "bars"
else
render :partial => "foo4", :layout => "bars"
end
Now, in Rails 2.3.5, this worked fine. It would render the appropriate partial inside the appropriate layout -- I realize the layout option is redundant here as it would default to the bars layout regardless. When we upgraded to Rails 3.0.x, we started getting errors as follows:
Missing partial layouts/bars with {:handlers=>[:erb, :rjs, :builder, :rhtml, :rxml], :formats=>[:html]
Clearly the layouts/bars.html.erb file is and always has been there, so I couldn't figure it out. I was able to render with :layout => false, but that of course wasn't going to work. Eventually I figured out that if I do either of the following, it works:
1) Rename my layout to _bars.html.erb instead of bars.html.erb and:
render :partial => 'foo2', :layout => 'bars'
2) Keep my layout as bars.html.erb (what I want) and :
render '_foo2' # :partial option is redundant here anyway
It seems as though by using the :partial option instead of the string as first parameter is causing rails to apply the _name.html.erb convention to both the partial AND the layout. If I put in the underscore on my own, it falls back to the behaviour I expected which is to not prepend an _ infront of the name of the layout.
Does anyone know why this is the case?
EDIT Alright, not sure how I missed this... but here's something in the docs making mention of this. It seems as though it's been around since 2.3.8, perhaps it was done differently in 2.3.5 (what we were running on)?
3.4.3 Partial Layouts
A partial can use its own layout file, just as a view can use a
layout. For example, you might call a partial like this:
<%= render "link_area", :layout => "graybar" %> This would look for a
partial named _link_area.html.erb and render it using the layout
_graybar.html.erb. Note that layouts for partials follow the same
leading-underscore naming as regular partials, and are placed in the
same folder with the partial that they belong to (not in the master
layouts folder).

Here's my own answer to the question based on what I've edited above:
Since Rails 2.3.8, it would appear as though the default behaviour when rendering a partial with render :partial => 'foo', :layout => 'bars' is to expect a "partial layout" file as well as a partial view file. In this case it will expect
app/views/_foo.html.erb as well as app/views/layouts/_bars.html.erb
For anyone encountering this problem upgrading from Rails 2.3.5, here's the solution I found to have the least amount of impact:
render '_foo', :layout => 'bars'
This solution does not assume you're rendering a partial and therefore does not expect a partial layout. The other option would be to duplicate your layout to
app/views/layouts/_bars.html.erb
and using
render :partial => 'foo', :layouts => 'bars'
but that results in some duplication of code.
RAILS 2.3.8+ DOC REGARDING THIS:
3.4.3 Partial Layouts
A partial can use its own layout file, just as a view can use a
layout. For example, you might call a partial like this:
<%= render "link_area", :layout => "graybar" %> This would look for a
partial named _link_area.html.erb and render it using the layout
_graybar.html.erb. Note that layouts for partials follow the same
leading-underscore naming as regular partials, and are placed in the
same folder with the partial that they belong to (not in the master
layouts folder).

Related

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 implicit render in templates from multiple controllers

I have code like render #posts to render my posts collection in an index template which the PostsController renders.
Now I have an Admin::PostsController that also should render the collection but when my posts controller renders #posts it looks for the admin/posts/_post.html.erb partial. Do I now have to write the partial path explicity? Is this feature by design or a bug? It doesn't seem to make sense.
Yes, you need to supply the path explicitly. And yes, this is by design.
It actually makes sense because Rails is a MVC framework and if you create a controller under a different namespace one would expect separate views for that controller too. Think about convenience, if you wanted to quickly bootstrap an application with a few simple commands, an application where there's a public view of posts and an admin view where all of the admin goodies for editing are, you would EXPECT to have a different directory to store all that admin views.
render #posts is a shortcut for a longer method signature.
In case of PostsController, it is a short cut for render :partial => "post", :collection => #posts; the partial is _post.html.erb and it is expected to be in app/views/posts folder.
In case of Admin::PostsController, it is a short cut for render :partial => "admin#post/post", :collection => #posts; the partial is _post.html.erb, and it is expected to be in app/views/admin/posts folder.
If you want a different partial to be used, you should specify it explicitly.
See the Rendering Collections section of Rails Guides page on Layouts & Rendering for detailed explanation.

Rails >= 3.0.8 render inline code with layout not working

It seems the render method has changed.
In the view I used to be able to do the following:
= render :layout => 'some_layout' do
some stuff to be rendered
It seems the best fix is to move the content into a partial and call the layout
= render :partial => 'some stuff to be rendered', :layout => 'some_layout'
I was just wondering if anyone had come across this and if it is a bug or an intended change?
EDIT
Rendering a block inline with a layout works. Check out the part about applying a layout to a block within any template at http://api.rubyonrails.org/classes/ActionView/Partials.html
The issue I am having is with the latest version of HAML not rendering nested render calls properly.
https://github.com/nex3/haml/issues/412
From your post, it appears you are trying to do this in a view.
Is it possible that you are confusing ActionController's render and ActionView's render? Looking # the API documentation for 2.3.8 & 3.x, it doesn't seem there was ever a :layout option within ActionView's render.
UPDATE
Actually, I may have been off-base. It does seem that there is an :inline option as described here.
render(options = {}, locals = {}, &block)
Returns the result of a
render that’s dictated by the options hash. The primary options are:
:partial - See ActionView::Partials.
:update - Calls update_page with the block given.
:file - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
:inline - Renders an inline template similar to how it’s done in the controller.
:text - Renders the text passed in out.
This is fixed in the most recent version of HAML v3.1.3

Specify namespace when using "render" with an object in rails 3

So, you can do something like that in rails:
#features.each do |feature|
render feature
end
and it will look for a partial called _feature.html.erb in the views/features folder, based on the class name.
But what if features are in a cms namespace?
Is it possible to specify the namespace? Doing something like this (it doesnt work, obviously)
render [:cms, feature]
Thx
You'll have to be more explicit:
render :partial => '/cms/feature', :object => feature
This will render the 'app/views/cms/_feature.html.erb' partial with the object being 'feature'.

How can you render a template within a layout using Liquid template language?

I'm trying to render a liquid template within a liquid layout (Liquid Template lang, not CSS liquid layout stuff). I can't seem to get the layout part to render. Currently using:
assigns = {'page_name' => 'test'}
#layout = Liquid::Template.parse(File.new(#theme.layout.path).read)
#template = Liquid::Template.parse(File.new(self.template.path).read)
#rend_temp = #template.render(assigns)
#rend_layout = #layout.render({'content_for_layout' => #rend_temp})
render :text => #rend_layout, :content_type => :html
The resulting HTML of the page shows that the 'template' rendered in liquid fine, but it isn't wrapped with the layout (replacing 'content_for_layout' in the layout with the rendered template)
Just to let anyone else know who comes across this problem, the code posted above actually does work, the issue is with the variable named #template. I renamed #template, and #layout to #_tempalte, and #_layout and everything works as expected.
For using liquid in ruby on rails (especially rails 3) - I believe the proper way to render your liquid templates (and also maintain all the work rails is doing for you) is as follows...
The liquid gem itself provides a liquid_view for rails so you can wire up the rails to look for "liquid" templates when you call #render. This liquid_view only works fully with rails 2.3
but can easily be updated to work with rails 3 by making the following update
if content_for_layout = #view.instance_variable_get("#content_for_layout")
assigns['content_for_layout'] = content_for_layout
elsif #view.content_for?(:layout)
assigns["content_for_layout"] = #view.content_for(:layout)
end
assigns.merge!(local_assigns.stringify_keys)
This can be seen here --> https://github.com/danshultz/liquid/commit/e27b5fcd174f4b3916a73b9866e44ac0a012b182
Then to properly render your liquid view just call
render :template => "index", :layout => "my_layout", :locals => { liquid_drop1 => drop, liquid_drop2 => drop }
In our application, since we have a handful of common liquid attributes we have overriden the "render" method in our base controller to automatically include the default locals by referencing #liquid_view_assigns which roll up additionally added liquid drops for the render call
def render(...)
options[:locals] = options.fetch(:locals, {}).merge(liquid_view_assigns)
super
end

Resources