Rails render partial in a loop - ruby-on-rails

I am new to rails and I am trying to render a partial within a loop as such.
Here , books is an array eager loaded in controller.
books.each do |book|
<%= render 'books/book', :book => book %>
end
This works fine. But when the books array is really huge, it takes time for the view to load completely. Is there any other efficient way using which the same can be achieved?. I tried partial with collection(like here) as well, but even that did not make much difference in the load time of books. Any suggestions would be highly appreciated. Thanks.

Rendering partial in a loop consume too much time because of open/close partial file every iteration. Instead of partial try to use your own helper for this purpose.

If you have list of values to display in the same view then you can iterate the values in the same view instead of rendering a new partial each time. If it's not the case then it is better to pass values from controller to your view. Hope it helps.

How about using "proc" on your view top, and then call it in your loop.
<% book_proc = proc do |book| %>
#your html call
<% nil %><%# return nil to prevent print out in last string %>
<% end %>
<% books.each do |book| %>
<%= book_proc.call(book) %>
<% end %>

You write that you're new to ruby/rails, so have you ever tried using
pagination
to solve your performance-problem?
A pagination will split your list into parts of e.g. 20 books and sets a paginator mostly at the bottom of your table.

I recently worked on a project in which I needed to render a table with about 1000 rows and I obviously experienced performance issues.
When pagination cannot be applied (due to requirements) and speed is required, then helpers is the solution (as already answered by Dima Melnik).
To prove what i said, i give a link to a performance test produced by Ben Scofield:
http://viget.com/extend/helpers-vs-partials-a-performance-question

Related

Active Records - List the item's index number (the actual order number in which the item is displayed)

I'm a newb to Ruby on Rails and I have a issue trying to display the index number of a record set. I've had a good search, but can't find the specific answer.
The issue I'm having is that I cannot find a way to output the index number from a recordset which i'm displaying.
[Title], [Description]
[Title], [Description]
[Title], [Description]
etc.
So to clarify, I'm looking to output the order (index) number (1, 2, 3) in a list where i'm able to output the Title & Description, also i'll point out the obvious - this is different to the unique ID which may be stored in the database, thus if i was to filter or sort the results I would still want to show the order (index) number (1, 2, 3 etc).
I have founds example of where they loop through the results and incrementally add to a pre-defined index value. The problem is my app doesn't use a loop statement to output the records, instead it's using an Active Record to display (and essentially loop through) the results. From what I understand, Active records will automatically loop through and output the records by rendering the code snippet ie. <%= render #links %> This works great for my example - For the full code for the app, please refer to the tutorial I deprived the app from:
https://www.codementor.io/danielchinedu/building-a-basic-hacker-news-clone-with-rails-5-4gr4hrbis
So in retrospect, I'm looking to clone the app in the tutorial but add an order number to the link lists.
From the documentation:
Rails also makes a counter variable available within a partial called
by the collection, named after the member of the collection followed
by _counter.if you're rendering #products, within the partial you can
refer to product_counter to tell you how many times the partial has
been rendered.
guides.rubyonrails.org
So you can use something like <%= link_counter %> in your _link.html.erb partial. Hope this will help.
The tutorial actually explains it quiet well, i think.
Underneath the <%= render #links %>:
We are using a rails feature here, when you call render on an instance variable holding an active record relation object, it will iteratively loop through the object and then render the appropriate partial.
So! It's a Rails feature making it easier to write partial loops!
<%= render #links %> is the same as: <%= render partial: 'link', collection: #links %>
If you want an index in your partial, you can just append a local variable. For example:
<%= render #links, locals: {num: 1} %>
And then in your view after you have written the variable, remember to add 1 so it's ascending.
<div class="link">
<div class="title">
<%= num %>
<%= link_to link.title, (link.url? ? link.url : link) %>
...
<% num += 1 %>
Good luck!

Ruby on Rails: alternative to nested partials (which are too slow)

I need my Rails application to generate huge table (about 5-7 columns, and about 2000-3000 rows), it is for internal company usage so we don't care about traffic and so on.
The structure of objects being displayed should be quite flexible: when some column gets added/changed/removed, it is changed in just one place (apart from modifying db schema), and all the views (index, show, edit form) get updated automatically.
I had totally no experience in Rails before. I took partials as a way to reuse code (which is good thing), and so, I've seen nothing bad in using nested sub-sub-sub-partials. Say, I have several models, index view of each of them renders generic partial gen_index, and for each cell of the table there is special partial gen_field_view.
The same partial gen_field_view is rendered from the show views, so, I'd really like to have it as a partial.
If I have even just 600 rows and 5 columns, this partial gets rendered 3000 times. This was a huge surprise for me that it is so hard for Rails: it currently takes about 7 seconds to generate the page. If I remove gen_field_view partial and include it directly into gen_index partial, it takes about 3 seconds: more than twice faster (which is quite slow anyway, but this is different story).
It seems, we should avoid nested partials for such cases, but then, what else should we use to reuse views code?
We probably can return html code from some method in the model, but it actually kills the idea of Rails, and, after all, it's not convenient.
What about using a caching strategy ? You can use rail's fragment cashing feature:
<% Product.all.each do |p| %>
<% cache(p) do %>
<%= render partial: 'some_partial', locals: {product: p} %>
<% end %>
<% end %>
Some of the things you can try are
Caching the main partial result which will then be reused while the record isn't updated
Render records as json on the page and render a template of the table row and then use javascript to render the actual table
<div id="data" data-records="<%= #products.to_json %>"></div>
<script>
jQuery.each(jQuery("#data").data("records"), function(record) {
jQuery("<li>").text(record.title).appendTo("#my-table")
})
</script>
Use pagination with a gem like will_paginate and then reduce the amount of records showing on the page at the same time
Use a presentation object that renders the html as string
class ModelPresentation
def initialize(model)
#model = model
end
def render
[title, rate].join
end
def title
"<h3>#model.title</h3>"
end
def rates
"<span>Rate #{#model.score}</span>"
end
end

Efficiently query comments of all posts at a controller level?

I have an index action in posts controller, and the view displays all posts with their comments following each post. My current implementation is, in the embedded html file, in the block of #posts.each do |p| I'm using p.comments to get all comments of each post. However, it seems not good to do this just in the view, but I can't find a way of doing that in controller. Can anyone give some suggestions? Or is this just the correct way to do?
In you controller, when you query posts, use includes(:comments) to reduce the number of SQL query from 1+n to 2:
#posts = Post.includes(:comments).where(......)
You can leave the code in your view as it is, p.comments won't fire more SQL queries.
Check the tutorial Eager loading associations.
Arie is spot on with solving the n + 1 problem. In terms of the view, you can still use the same approach, but it would be better to use collection partials:
# posts/index.html.erb
<%= render #posts %>
This will look for a partial located at /views/posts/_post.html.erb as send it a post variable. It will do this for every Post model in the #posts array.
For the comments, in _post.html.erb you could write:
# /views/posts/_post.html.erb
<% # Post content here %>
<%= render post.comments %>
Which like the posts collection, will render each comment using the partial located at /views/comments/_comment.html.erb
For more details, check out the rails guide

rails3 displays all records at the end of the view

I have a index action in my rails 3.2.6(ruby 1.9.3) app. In the view i am simply looping over all the records in the database.When I hit the index action in the browser it displays all the records present in the database as an array at the end of the page any pointers what the problem might be?
Seeing the code for your view would help a lot. My guess, you are likely printing out the value of the each call. each returns the array after it loops through all of the elements. If you are using erb, below should work:
Change
<%= #records.each do |record| %>
to
<% #records.each do |record| %>

How to make the view simpler, the controller more useful?

This question relates to cleaning up the view and giving the controller more of the work.
I have many cases in my project where I have nested variables being displayed in my view. For example:
# controller
#customers = Customer.find_all_by_active(true)
render :layout => 'forms'
# view
<% #customers.each do |c| %>
<%= c.name %>
<% #orders = c.orders %> # I often end up defining nested variables inside the view
<% #orders.each do |o| %>
...
<% end %>
<% end %>
I am fairly new to RoR but it seems that what I'm doing here is at odds with the 'intelligent controller, dumb view' mentality. Where I have many customers, each with many orders, how can I define these variables properly inside my controller and then access them inside the view?
If you could provide an example of how the controller would look and then how I would relate to that in the view it would be incredibly helpful. Thank you very much!
I don't think there is anything drastically wrong with what you're doing. Looping through the customers and outputting some of their attributes and for each customer, looping through their orders and outputting some attributes is very much a view-oriented operation.
In the MVC architecture, the controller has responsibility for interacting with the model, selecting the view and (certainly in the case of Rails) providing the view with the information it needs to render the model.
You might consider extracting the code into a view helper though, if you have that exact code repeated more than once. You could even genericize it, passing in the name of a model and association. I haven't tested it, but you should be able to do something like this:
def display_attributes(models, association, attribute, association_attribute)
content = ''
models.each do |m|
content << "<p>#{m.attribute}</p>"
associated_models = m.association
associated_models.each do |am|
content << "<p>#{am.association_attribute}</p>"
end
end
content
end
Then in the view, you could use the helper like this:
<%= display_attributes(#customers, orders, name, name) %>
Obviously you would change the HTML markup within the helper method to suit your requirements. Note that if you're not using Rails 3 then you'll want to escape the output of the attribute names in the helper method.
I don't think there's anything wrong with your code. I'd just suggest for you to use a :include in your find
#customers = Customer.find_all_by_active(true, :include => :orders)
to reduce the number of queries.
I see nothing wrong with the code as you showed.
You are mixed up about the "intelligent controller, dumb view" approach though, i tend to prefer the "skinny controller, fat model", so indeed the view should be dumb, but you put the intelligence inside your model, and your helpers (or use a presenter), but definitely not in the controller.

Resources