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
Related
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.
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"%>
Problem
My app manages ProjElements, which are subclassed into:
Milestone
Task
Decision
... etc
For a given ProjElement's show.html.erb, you can comment on that project element instance (e.g. you can add a comment on Milestone XYZ or Decision ABC). Like this:
// display project element specific stuff
// - e.g. show.html.erb for Milestone has milestone-specific stuff
// - e.g. show.html.erb for Decision has decision-specific stuff
// provide comment functionality
// - e.g. for Milestone's show.html.erb, code looks like
<%= form_for [#milestone, Comment.new] do |f| %>
<% if #milestone.comments.size > 0 %>
...
<% end %>
<% f.submit %>
<% end %>
Proposed approach
I plan to use a partial for the comment code and use it across the various show.html.erb views for different project elements, as per DRY. But ...
How do I write generic code for the partial, the Rails way, so that the partial can deal with different project elements?
You can pass the element's subclass instance through the locals hash. In app/views/milestones/show.html.erb
render :partial => 'shared/comments', :locals => { :element => #milestone }
In app/views/shared/_comments.html.erb
<% form_for [element, Comment.new] do |f| %>
I've just started to use Partials in my rails application, at the moment i have the following code in my application.html.erb
<%= render 'categories/categorieslist' %>
This links to _categorieslist.html.erb in my views/categories/ folder
At the moment this partial contains hard coded hyperlinks
<ul class="unstyled">
<li style="padding-bottom:5px">Item A»</li>
<li style="padding-bottom:5px">Item B»</li>
</ul>
My aim is to have these categories coming from the database, e.g
<ul class="unstyled">
<% #categories.each do |category| %>
<li style="padding-bottom:5px"><%= category.name %> » </li>
<% end %>
</ul>
I have tried adding a categorieslist method in the categories controller e.g
def categorieslist
#categories = Category.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #categories }
end
end
but this is not being called by the partial (and i don't feel this is even the correct way to do it), and is showing the error
NoMethodError in Store#index
on the line <% #categories.each do |category| %>
My question is how do i pass into the partial in the application.html.erb file, the categories object that usually would come from a controller method in the categories controller?
Any help would be great.
You can send locales with your partial call in your view and pass variables to that partial.
For example (this is a partial shortcut):
Your view from which you call the partial
<%= render 'categories/categorieslist', :all_categories => #categories %>
Your partial categories/_categorieslist.html.erb (note there is no # with the variable)
<ul class="unstyled">
<% all_categories.each do |category| %>
<li style="padding-bottom:5px"><%= category.name %> » </li>
<% end %>
</ul>
For further information (and the long version), see 3.4.4 Passing Local Variables in the Rails Guides.
I'd use a collection for this:
<%= render 'categories/categorieslist', :collection => #categories, :as => :category %>
This renders a collection of items. In this case, all the categories. You can also pass it a custom name with the :as => .
Then in your partial you only include the stuff you want the items in the collection to render:
<li style="padding-bottom:5px"><%= category.name %> » </li>
The -ul- isn't included as it would be rendered multiple times. You'll need to wrap it around your render tag.
The result is the same as the suggestion #timbrandes outlined, (check out docs he linked to).
I've heard :collection gives you performance improvements.
http://rails-bestpractices.com/posts/38-use-render-collection-rails-3-when-possible
That's not the right way to do it, and your question is rather confusing.
I'd say that you still have to read a rails book. You seem to be still a bit too fresh
Anyway, controller methods usually represent http requests. And they are invoked accordingly to what is defined in the config/routes file. Views (*.erb) do not usually invoke controller methods. If they do so, they do it through an ajax request.
Data is passed from actions to the views through controllers instance variables.
If you want to invoke any methods within the views, they should be defined in helpers. Still, the only data they will manipulate is the one passed from controllers as instance variables.
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.