Rails: How to render a layout around a collection? - ruby-on-rails

Rails supports :layout option when rendering collection partials, but the layout applies to the every element in the list.
Is there a way to add a layout around the whole collection?
Details
Let's say I have a collection #promoted_stories. I want to render some HTML around the rendered collection, but only if #promoted_stories is not empty. What I want can be achieved in this way:
<% if #prometed_stories.present? %>
<div class="promoted-stories>
<%= render #promoted_stories %>
</div>
<% end %>
Can I do the same and avoid the if? I'm a fan of logic-less layouts, so I would like to avoid as much branching in my views if possible. I'd prefer if something like this was possible:
# View:
<%= render collection: #promoted_stories, collection_layout: 'promoted_stories' %>
# _promoted_stories.html.erb
<div class="promoted-stories">
<%= yield %>
</div>

While not as elegant as imagined solution, this should achieve similar results:
# Helper
def render_collection_template(template, collection)
render template: "layouts/#{template}", locals: { collection: collection } if collection.present?
end
# View
<%= render_collection_template 'promoted_stories', #promoted_stories %>
# Template
<div class="promoted-stories">
<%= render collection %>
</div>
There is now a pull request for rails implementing collection layout for collection render.

Related

Rails Partials For Resources

I have a resource called Exercises in my application. I currently have a partial called _exercise.html.erb that I use to render them. I have an outlying case where I'd like to render them in a much different way. Can I make another partial for exercises that has this other format and still be able to use <%= render #exercises %>?
If not what is the best approach? Should I out a variable in the controller that tells the partial which layout to use, this would have both layout in one file and one if to decide. Or is there some better way?
If you'd like to use business logic to determine when to show what partial for your #exercises collection you should use the to_partial_path method in the Exercise model to define that. See #4 in this post: http://blog.plataformatec.com.br/2012/01/my-five-favorite-hidden-features-in-rails-3-2/
Or, if this is more of a view-related decision (i.e. one view will always use the regular _exercises.html.erb and another view would always use e.g. _alternate_exercises.html.erb) then you can specify as such:
<%= render partial: 'alternate_exercises', collection: #exercises, as: :exercise %>
This will render the _alternate_exercises.html.erb partial once for each item in #execrises passing the item in to the partial via a local_assign called exercise.
In this case, I suppose you have two options:
1) Put the conditional code inside of _exercises.html.erb
eg.
<% if #exercise.meets_some_condition %>
you see this stuff
<% else %>
you see other stuff
<% end %>
This way, you can still make use of <%= render #exercises %>
2) Otherwise, your other option is to have separate partials and render them outside.
eg.
<% #exercises.each do |exercise| %>
<% if exercise.meets_some_condition %>
<%= render "exercises/some_condition_exercise" %>
<% else %>
<%= render "exercises/exercise" %>
<% end %>
<% end %>
This is the best approach for rendering partial. You can wrap that partial with if else statement in your code. Here is my example
rendering with form called _victim.html.erb
<%= render :partial => "victim", :locals => {:f => f }%>
rendering without form
<%= render :partial => "victim"%>

Rails: Correct way of making exception of "Yield" by using "Content_for"?

I have a Rails app, in which I fit my entire website into a 980width container with the following code in my 'application.html.erb' file:
<div class="container_980 white shadow-horizontal">
<div class="container">
<%= render 'layouts/flashes' %>
<%= yield %>
</div>
</div>
Now, I want to make 2 file exceptions for fitting the content within the container. I want the index page and another page to expand across the entire page, so I need to get those two pages outside of the common 'yield' set above.
I tried doing so with:
<% if current_page?(root_url) %>
<%= yield :index %>
<% elsif current_page?(:controller => "tracks", :action => "show", :id => params[:id])) %>
<%= yield :show_track %>
<% else %>
<div class="container_980 white shadow-horizontal">
<div class="container">
<%= render 'layouts/flashes' %>
<%= yield %>
</div>
</div>
and
<% content_for :show_track do %>
blah blah blah
<% do %>
THE PROBLEM: The show_track page doesn't load. I did some searching, and it seems like the above method should work, but it's not, and I was wondering if I needed to do something else as the "show" page was made through scaffoldaing(RESTful).
Is there a better way to take out the 2 pages from the container than using if..else conditions?
Is there a better way to take out the 2 pages from the container than using if..else conditions?
This is subjective, but I would use nested layouts, then define the layouts for each page type in the controller.
First your basic top level layout. I'm calling it "application", the default, but you could call it whatever. Note how if there's content_for? :application it will yield it, otherwise it will just yield. This is key to the setup. All nested layouts should follow a similar pattern; in this way they can render further nested child layouts, or be used as layouts themselves.
<!-- layouts/application.html.erb -->
<html>
<body>
<%= content_for?(:application) ? yield(:application) : yield %>
</body>
</html>
Then for the container, you'd define layout which can be nested inside "application", this one setting up your container HTML and rendering content inside.
<!-- layouts/container.html.erb -->
<%= content_for :application do %>
<div class="container_980 white shadow-horizontal">
<div class="container">
<%= render 'layouts/flashes' %>
<%= content_for?(:container) ? yield(:container) : yield %>
</div>
</div>
<% end %>
<%= render :file => "layouts/application" %>
Then just move your conditional logic to the controller, like:
layout :determine_layout
protected
function determine_layout
# pseudocode here, you get it
(index or tracks) ? "application" : "container"
end
You could stop there. Continue to see how you might further nest layouts.
However you could go further, and use the nested layout setup to nest arbitrary numbers of different layouts. Say, for example, that tracks had another content block you needed to fill. You could define another nested layout, like:
<!-- layouts/tracks.html.erb -->
<%= content_for :some_other_block do %>
// stuff that should be in some other block
<% end %>
<%= content_for :container do %>
// stuff that should be in the container
<% end %>
<%= render :file => "layouts/container" %>
Then in your controller, you'd change your determine_layout to set the "tracks" layout for tracks, e.g.:
function determine_layout
# pseudocode here, you get it
if index
"application"
elsif tracks
"tracks"
else
"container"
end
end

Render rails partial multiple times on same page

I have a partial that I'm rendering twice on the same page, but in two different locations (one is shown during standard layout, one is shown during mobile/tablet layout).
The partial is rendered exactly the same in both places, so I'd like to speed it up by storing it as a variable if possible; the partial makes an API call each time, and the 2nd call is completely unnecessary since it's a duplicate of the first API call.
Is there any way to store the HTML from the returned partial as a variable and then use that for both renders?
Edit: I'm hoping to do this without caching, as it is a very simple need and I'm looking to keep the codebase lean and readable. Is it possible to store the partial as a string variable and then reference that twice?
<% content_for :example do %>
<%= render :your_partial %>
<%end%>
then call <%= yield :example %> or <%= content_for :example %> wherever you want your partial called.
One option would be to use fragment caching. After you wrap the partial with a cache block, the second call should show the cached version of the first. For example:
<% cache do %>
<%= render(:partial => 'my_partial') %>
<% end %>
... later in the same view ...
<% cache do %>
<%= render(:partial => 'my_partial') %>
<% end %>
To store the result of the render to a string, you could try the render_to_string method of AbstractController. The arguments are the same as for render.
partial_string = render_to_string(:partial => 'my_partial')
I'm adding an answer to this old question because it topped Google for a search I just made.
There's another way to do this now (for quite a while), the capture helper.
<% reuse_my_partial = capture do %>
<%= render partial: "your_partial" %>
<% end %>
<div class="visible-on-desktop"
<%= reuse_my_partial %>
</div>
<div class="visible-on-mobile"
<%= reuse_my_partial %>
</div>
This is simpler and slightly safer than using content_for because there is no global storage involved that something else might modify.
The rails docs linked to use instance #vars instead of local vars because they want it to be available to their layout template. That's a detail you do not need to worry about, because you're using it in the same template file.

Rails 3 and partials layouts

I'm trying to render a collection of different objects in a same format for each. I want to keep it DRY, so I want to use partials and partial layouts.
Edit - brief clarification : That I need is not to display common items on all publication pages, but to display common properties/fields on each item. This is why I need partial layouts e.g. a layout for the the partial, not a layout for the page.
I have a collection of different objects :
#publications = Publications.all
# Publication is the parent class
# #publications = [ImagePost, VideoPost, TextPost, ...]
I want to render all publications in a list. Each publication have some common properties : author, date, ... I want to put this properties in a partial layout.
So in my view, to render the collection, I do :
<%= render :partial => 'publications', :locals => {:publications => #publications} %>
In the first level partial views/publications/_publications.html.erb, I loop on the item and try to render each item in its partial and with a common partial layout :
<ul class='publications_list'>
<% publications.each do |p| %>
<%= render p, :layout => 'publications/publication_layout' %>
<% end %>
</ul>
The partial layout, views/publications/_publication_layout.html.erb :
<li>
<h2><%= link_to publication.title, publication %></h2>
... Other common properties that I want to display on each item, independently of its type ...
<p><%= yield %></p>
</li>
And finally for each object type, I have a partial (e.g. image_posts/_image_post.html.erb and so) containing the code to display properly each.
My problem : I don't manage to render each publication in the common partial layout publication_layout. This layout is simply ignored by rails. Each item is correctly rendered but without this layout which include the common properties and the <li> tag.
Any suggestion about why my partial layout is ignored ?
ANSWER AND WORKAROUNDS
Thanks to #MarkGuk to have spotted this line in the doc :
Also note that explicitly specifying :partial is required when passing
additional options such as :layout.
So it's just not possible to simply render a polymorphic collection within the same partial for each item.
Workaround 1 : I first tried to compute partial path for each item, store it in the model for convenience, and so render each item in the good partial with the good layout. But I realize that this method, I can't refer to the object publication inside the layout...
<ul class='publications_list'>
<% publications.each do |p| %>
<% # p.partial = p.class.to_s.underscore.pluralize +'/'+ p.class.to_s.underscore %>
<%= render :partial => p.partial, :object => p, :as => :publication, :layout => 'publications/publication_layout' %>
<% end %>
</ul>
Workaround 2 :
Finally I used nested partials.
<ul class='publications_list'>
<% publications.each do |p| %>
<%= render :partial => 'publications/publication_layout', :object => p, :as => :publication %>
<% end %>
</ul>
and replaced yield with a render publication inside the layout.
I wonder if a nested layout might serve you better here. The guide should point you in the right direction and I found it trivially easy to get working, but as a start:
In views/layouts/application.html.erb, change yield to:
<%= content_for?(:publication_content) ? yield(:publication_content) : yield %>
Eliminate the partial views/publications/_publication.html.erb and instead create the nested layout views/layouts/publication.html.erb:
<% content_for :content do %>
# Put common items here
<%= content_for?(:content) ? yield(:content) : yield %> # this is for specifics
<% end %>
<%= render :template => 'layouts/application' %>
Then specific layouts can be nested further or specified with additional tags in the view, depending on the rest of your setup.
See comments to the question and marked answer from the author.
Documentation
Answer from the author

Avoiding repetitive "content_for" in views

I have a submenu placed in my layout wich differs from controller to controller, but not between each controllers method views. What I am currently doing is the following:
<% content_for( :submenu ) do %>
<%= render :partial => 'submenus/correct_submenu' %>
<% end %>
In every view for a method
My applications layout then has this in it
<%= yield :submenu %>
However, this feels kind of repetitive, doing it for each view. Is there some way to do this per controller?
My suggest is to have a convention for this, so if you have a ProductsController then the submenu would be submenus/products_menu. This way you can write a helper that looks like:
def render_submenu
content_for(:submenu) { render :partial => "submenus/#{controller.controller_name}_menu" }
end
You can then call this by doing:
<%= render_submenu %>
You could then make this the default content_for the submenus and only specify the content if it needs to be different.
I hope this helps!
Use nested layouts to nest a specific controller's layout under the application layout, by creating a file like so:
# app/view/layouts/<controller_name>.html.erb
<% content_for( :submenu ) do %>
<%= render :partial => 'submenus/correct_submenu' %>
<% end %>
<%= render template: "layouts/application" %>
With this method, you don't have to modify a bunch of view files.

Resources