I'm trying to experiment with blocks and how to iterate over collections in ERB. I have a models in a one-to-many relatinship (Channel and their corresponding types).
controller
class HomePageController < ActionController
def index
#channels = Channel.all
end
end
Then in the view, I iterate over all the attributes belonging to a Channel. When I want to print all types, this code gives me the desired output:
view
<% #channels.each do |channel| %>
<% #types.each do |type| %>
<%= Type.find(type).name %>
<% end %>
<% end %>
At first I tried to achieve this by using the yield keyword in a neat one-liner but I couldn't manage to print anything to the browser, only to the console
<% #types.each {|type| yield Type.find(type).name } %>
Is there an equivalent one-liner?
First of all this method is so inefficient, you are doing n-queries, to find each record of type Type instead convert those into an array of types by using a single query in the controller, assume that that array is in type_ids
# controller
#channels = Channel.includes(:types) # avoiding n+1 queries
# view
<% #channels.each do |channel| %>
# some channel info output
<% channel.types.each do |type| %>
<%= type.name %>
<% end %> # types loop
<% end %> # channel loop
As #Almaron mentioned, you could render a partial for more simplification, if you have a partial called _type.html.erb you can call render directly
# view
<%= render channel.types %>
Rails will do all the iterating and rendering.
First of all, this kind of code does not belong to the view. Don't tackle the database from the view (in your case Type.find()). Move it to the controller where it belongs.
The second thing to note is the difference between <%= and <% tags. The first one outputs the returned result, while the second one doesn't. The problem with .each is that it returns the object it has been used on, so in your case if you just go <%= #types.each {|type| Type.find(type).name } %> you'll get the #types array printed out.
If you want to simplify that code, you can use a helper method for iterating and a partial for rendering each item. That way you get something like this
<% collection_iterate #items, 'item_partial' %>
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"%>
Let's say I have a comment model:
class Comment < ActiveRecord::Base
has_many :replies, class: "Comment", foreign_key: "reply_id"
end
I can show a comment instance`s replies in a view like so:
comment.replies do |reply|
reply.content
end
However, how do I loop through the replies of the reply? And its reply? And its reply ad infitum? I'm feeling we need to make a multidimensional array of the replies via class method and then loop through this array in the view.
I don't want to use a gem, I want to learn
It seems like what you have is one short step away from what you want. You just need to use recursion to call the same code for each reply as you're calling for the original comments. E.g.
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: #comments %>
</div>
<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: comment.replies %>
</div>
NB: this isn't the most efficient way of doing things. Each time you call comment.replies active record will run another database query. There's definitely room for improvement but that's the basic idea anyway.
Would using a nested set still count as 'from scratch'?
The short description of a nested set is a database-specific strategy of querying hierarchies by storing/querying pre- and post-order tree traversal counts.
A picture is worth a thousand words (see also, the wikipedia page on nested sets).
There are a bunch of nested set gems, and I can personally speak for the quality of Awesome Nested Set and Ancestry
Then, Awesome Nested Set (I know from experience, presumably Ancestry too) provide helpers to do a single query to pull up all records under a tree, and iterate through the tree in sorted depth-first order, passing in the level while you go.
The view code for Awesome Nested Set would be something like:
<% Comment.each_with_level(#post.comments.self_and_descendants) do |comment, level| %>
<div style="margin-left: <%= level * 50 %>px">
<%= comment.body %>
<%# etc %>
</div>
<% end %>
I just made that up from vague memories, and it's been a while, so this is where it can be "an exercise for the reader"
My approach is to make this done as efficient as possible.
First lets address how to do that:
DRY solution.
Least Number of queries to retrieve the comments.
Thinking about that, I have found that most of the people address the first but not the second.So lets start with the easy one.
we have to have partial for the comments so referencing the answer of jeanaux
we can use his approach to display the comments and will update it later in the answer
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: #comments %>
</div>
<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: comment.replies %>
</div>
We must now retrieve those comments in one query if possible so we can just do this in the controller. to be able to do this all comments and replies should have a commentable_id (and type if polymorphic) so that when we query we can get all comments then group them the way we want.
So if we have a post for example and we want to get all its comments we will say in the controller.
#comments = #post.comments.group_by {|c| c.reply_id}
by this we have comments in one query processed to be displayed directly
Now we can do this to display them instead of what we previously did
All the comments that are not replies are now in the #comments[nil] as they had no reply_id
(NB: I don like the #comments[nil] if anyone has any other suggestion please comment or edit)
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: #comments[nil] %>
</div>
All the replies for each comment will be in the has under the parent comment id
<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: #comments[comment.id] %>
</div>
To wrap up:
We added an object_id in the comment model to be able to retrieve
them( if not already there)
We added grouping by reply_id to
retrieve the comments with one query and process them for the view.
We added a partial that recursively displays the comments (as
proposed by jeanaux).
It seems like you need a self-referential association. Check out the following railscast: http://railscasts.com/episodes/163-self-referential-association
We've done this:
We used the ancestry gem to create a hierarchy-centric dataset, and then outputted with a partial outputting an ordered list:
#app/views/categories/index.html.erb
<% # collection = ancestry object %>
<%= render partial: "category", locals: { collection: collection } %>
#app/views/categories/_category.html.erb
<ol class="categories">
<% collection.arrange.each do |category, sub_item| %>
<li>
<!-- Category -->
<div class="category">
<%= link_to category.title, edit_admin_category_path(category) %>
<%= link_to "+", admin_category_new_path(category), title: "New Categorgy", data: {placement: "bottom"} %>
<% if category.prime? %>
<%= link_to "", admin_category_path(category), title: "Delete", data: {placement: "bottom", confirm: "Really?"}, method: :delete, class: "icon ion-ios7-close-outline" %>
<% end %>
<!-- Page -->
<%= link_to "", new_admin_category_page_path(category), title: "New Page", data: {placement: "bottom"}, class: "icon ion-compose" %>
</div>
<!-- Pages -->
<%= render partial: "pages", locals: { id: category.name } %>
<!-- Children -->
<% if category.has_children? %>
<%= render partial: "category", locals: { collection: category.children } %>
<% end %>
</li>
<% end %>
</ol>
We also made a nested dropdown:
#app/helpers/application_helper.rb
def nested_dropdown(items)
result = []
items.map do |item, sub_items|
result << [('- ' * item.depth) + item.name, item.id]
result += nested_dropdown(sub_items) unless sub_items.blank?
end
result
end
That can be solved with resursion or with a special data structure. Recursion is simpler to implement, whereas a datastructure like the one used by the nested_set gem is more performant.
Recursion
First an example how it works in pure Ruby.
class Comment < Struct.new(:content, :replies);
def print_nested(level = 0)
puts "#{' ' * level}#{content}" # handle current comment
if replies
replies.each do |reply|
# here is the list of all nested replies generated, do not care
# about how deep the subtree is, cause recursion...
reply.print_nested(level + 1)
end
end
end
end
Example
comments = [ Comment.new(:c_1, [ Comment.new(:c_1a) ]),
Comment.new(:c_2, [ Comment.new(:c_2a),
Comment.new(:c_2b, [ Comment.new(:c_2bi),
Comment.new(:c_2bii) ]),
Comment.new(:c_2c) ]),
Comment.new(:c_3),
Comment.new(:c_4) ]
comments.each(&:print_nested)
# Output
#
# c_1
# c_1a
# c_2
# c_2a
# c_2b
# c_2bi
# c_2bii
# c_2c
# c_3
# c_4
And now with recursive calls of Rails view partials:
# in your comment show view
<%= render :partial => 'nested_comment', :collection => #comment.replies %>
# recursion in a comments/_nested_comment.html.erb partial
<%= nested_comment.content %>
<%= render :partial => 'nested_comment', :collection => nested_comment.replies %>
Nested Set
Setup your database structure, see the docs: http://rubydoc.info/gems/nested_set/1.7.1/frames That add the something like following (untested) to your app.
# in model
acts_as_nested_set
# in controller
def index
#comment = Comment.root # `root` is provided by the gem
end
# in helper
module NestedSetHelper
def root_node(node, &block)
content_tag(:li, :id => "node_#{node.id}") do
node_tag(node) +
with_output_buffer(&block)
end
end
def render_tree(hash, options = {}, &block)
if hash.present?
content_tag :ul, options do
hash.each do |node, child|
block.call node, render_tree(child, &block)
end
end
end
end
def node_tag(node)
content_tag(:div, node.content)
end
end
# in index view
<ul>
<%= render 'tree', :root => #comment %>
</ul>
# in _tree view
<%= root_node(root) do %>
<%= render_tree root.descendants.arrange do |node, child| %>
<%= content_tag :li, :id => "node_#{node.id}" do %>
<%= node_tag(node) %>
<%= child %>
<% end %>
<% end %>
<% end %>
This code is from an old Rails 3.0 app, slightly change and untested. Therefore it will probably not work out of the box, but should illustrate the idea.
This will be my approach:
I have a Comment Model and a Reply model.
Comment has_many association with Reply
Reply has belongs_to association with Comment
Reply has self referential HABTM
class Reply < ActiveRecord::Base
belongs_to :comment
has_and_belongs_to_many :sub_replies,
class_name: 'Reply',
join_table: :replies_sub_replies,
foreign_key: :reply_id,
association_foreign_key: :sub_reply_id
def all_replies(reply = self,all_replies = [])
sub_replies = reply.sub_replies
all_replies << sub_replies
return if sub_replies.count == 0
sub_replies.each do |sr|
if sr.sub_replies.count > 0
all_replies(sr,all_replies)
end
end
return all_replies
end
end
Now to get a reply from a comment etc:
Getting all replies from a comment: #comment.replies
Getting the Comment from any reply: #reply.comment
Getting the intermediate level of replies from a reply: #reply.sub_replies
Getting all levels of replies from a reply: #reply.all_replies
I've had various generally bad experience with the different hierarchy gems available for ActiveRecord. Typically you do not want to do this yourself as your queries will end up being very inefficient.
The Ancestry gem was ok, but I had to move away from it because 'children' is a scope and NOT an association. This means you CANNOT use nested attributes with it because nested attributes only work with associations, not scopes. That may or may not be a problem depending on what you are doing, such as ordering or updating siblings through the parent or updating entire subtrees/graphs in a single operation.
The most efficient ActiveRecord gem for this is the Closure Tree gem and I had good results with it, with the caveat that splatting/ mutating entire sub-trees was diabolical because of the way ActiveRecord works. If you don't need to compute things over a tree when doing updates then it is the way to go.
I've since moved away from ActiveRecord to Sequel and it has recursive common table expression (RCTE) support which is used by its built-in tree plugin. An RCTE tree is as fast as is theoretically possible to update (just modify a single parent_id as in a naive implementation) and querying is also typically orders of magnitude faster than other approaches because of the SQL RCTE feature it uses. It is also the most space efficient approach since there is just parent_id to maintain. I am not aware of any ActiveRecord solutions that support RCTE trees because ActiveRecord doesn't cover nearly as much of the SQL spectrum that Sequel does.
If you're not wedded to ActiveRecord then Sequel and Postgres is a formidable combination IMO. You will find out the deficiencies in AR when your queries become ever so slightly complex. There is always pain moving to another ORM as its not the out of the box stock rails approach but I have been able to express queries that I couldn't do with ActiveRecord or ARel (even though they were pretty simple), and generally improved query performance across the board 10-20 times over what I was getting with ActiveRecord. In my use case with maintaining trees of data its hundreds of times faster. That means tens to hundreds times less server infrastructure I need for the same load. Think about it.
You'd collect the reply's replies within each Reply iteration.
<% comment.replies do |reply| %>
<%= reply.content %>
<% reply_replies = Post.where("reply_id = #{reply.id}").all %>
<% reply_replies .each do |p| %>
<%= p.post %>
<% end
<% end %>
Though im not sure if it'd be the most conventional way cost-wise.
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.
I have 3 models: parent-companies, companies and contacts that I'd like to list then all on one index page with 3 different partials depending on the model.
Is there a clean way to do this??
Perhaps something like this?
<% #objects.each do |obj| %>
<% klass = obj.class_name.to_s.tableize.singularize %>
<%= render :partial => "#{klass}", :locals => {"#{klass}".to_sym => obj} %>
<% end %>
Still four lines of code, and you lose clarity. Why not just list the three calls to partials?
You could define a view model class that contains the lists you need to fetch to the UI.