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

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

Related

Rails render partial in a loop

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

Design pattern for side bar with dynamic content in Rails

I would like to have a right side bar with content changes for each page.
For example, when I am in Friends page, the side bar should display New Friends.
When I am in Account page, the side bar should display Recent Activities.
How should I go about this to respect Rails design patterns? I heard about Cells gem, but I am not sure if I use it.
here is one way, in your layout add a named yield section
<div id="main-content">
<%= yield %>
</div>
<div id="side-content">
<%= yield(:side_bar) %>
</div>
Then in your views put content into the named yield using content_for
# friends view ....
<% content_for(:side_bar) do %>
<%= render :partial => "shared/new_friends" %>
<% end %>
# account view ....
<% content_for(:side_bar) do %>
<%= render :partial => "shared/recent_activity" %>
<% end %>
this requires you to be explicit about what content appears in the side bar for every view,
maybe having it do it dynamically is better? probably depends on the specific situation and your preference
see also - http://guides.rubyonrails.org/layouts_and_rendering.html#understanding-yield
I came by this question in a moment of a big design change in our views. After thinking about the sidebar problem a bit, I realized that there's no best solution (as always). There are better solutions for each case.
I'll compare 3 solutions here:
using content_for(:sidebar) and yield(:sidebar)
using the partials approach
using the Cells gem
1. Using content_for(:sidebar) and yield(:sidebar)
This is good for cases when each link (each controller action) you access renders a different sidebar. In this case, each view you access will have the content_for(:sidebar) part.
If your sidebar view depends only on the state of some variable in the session, for example, the sidebar should not be rendered for every link you access.
Then you should use a good caching system like turbolinks, to avoid rendering many times the same thing, or use something like the Cells gem with a javascript to render only the main part of the layout.
2. Using partials
Using partials is always good to eliminate duplication. If your sidebar is very simple and is changed for every controller, you can render it as a partial. But if you're rendering different partials in the same controller, according to some state, it may be an indication that you have business logic in your views, which should be avoided.
3. Using the Cells gem
Very good design pattern when you have to render your sidebar from a different controller than the rest of the view each time.
It takes a lot of business logic out of the view, which sure is a good practice.
Here you have an action calling a view. Inside that view, there is a statement render_cell(:sidebar, params). This statement will do some business logic and render the view of the sidebar. It's as if the first action called other controller actions to render specific parts of your view (called cells)
If you make changes to the sidebar only, you may have to create other simple action, so that a javascript will request it. This action will call the render_cell(:sidebar) method again to respond with the view.
It's a very interesting approach.
Other ideas:
Your sidebar could be rendered only with javascript from the same
action.
Your sidebar could be rendered by an angular controller, and rails sends jsons with the sidebar objects. (look for "One page apps")
try something like this
<div class="sidebar">
<% if current_page?(controller => "friends", :action => "show") %>
<h4>New Friends</h4>
<% elseif current_page?(controller => "accounts", :action => "show") %>
<h4>Recent Activities</h4>
<% end %>
</div>
If the above code fits what you are trying to do(looks like this is what you want to achieve), then stick with it, else it may be beneficial to go with some gems. Also checkout helper page on how to use current_page? method. Hope it helps

Best practice for caching expensive lists of non-rendered data in Rails

This is a general question, but to get the point across, let's discuss a specific example. Suppose you have an application that frequently uses forms that include a list of all the countries in the world. The countries are stored in the countries table in your DB, but this table is updated very very rarely, and only through your seeds.rb file.
In order to be able to shave off a little time in each request, I typically manage this sort of thing as follows:
module ApplicationHelper
def self.get_countries
countries = Country.order("name asc").all.collect{|country| [country.name, country.name]}
countries.unshift(["",""])
countries
end
# In production mode (cache_classes = true) this is executed only once, and
# will remain cached until you restart your application server
##COUNTRIES = ApplicationHelper.get_countries
# This would be called from various views
def countries_for_select(country)
selected = country.name unless country.nil?
options_for_select(##COUNTRIES, selected)
end
end
Is this a reasonable approach?
Please don't suggest not storing the list of countries in the database. I'm aware there are other ways to manage lists of countries. I'm not really concerned with countries - it's just the easiest to understand example I could think of to illustrate this general question.
I'm not sure what you mean by "non-rendered data". With your method, the form view still needs to loop through all of the countries and render an option for the select for each one.
<select id="user_country" name="user[country]">
<option value="US">United States</option>
<option value="CA">Canada</option>
<option value="ET">Etcetera</option>
If, however, you wrapped the select in a fragment cache, then the first time the form was rendered after the last deploy, the select options would be rendered and then cached into a separate file in /tmp/cache. Then on every subsequent time the form is loaded, that cached file would be used by rails as if it were a partial and inserted into the form.
<% form_for #user do |form| %>
<% cache('user_countries_selector') do %>
<% form.select :country, Country.order("name asc").all.collect{|country [country.name, country.name]}
<% end %>
<% etc, etc, etc %>
<% end %>
although, in practice, I would probably move the Country.order... code out to a helper method. Caching the list of countries to ##COUNTRIES is completely unnecessary with this method.
If you need to render a country select in other forms, you should pass a different key to cache() since the id and name of the rendered select tag will be form specific.

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.

Rails: Refactoring, views, helpers: how does it all go together?

Warning: Noob here.
I know this is a trivial subject but I'm having a lot of difficulty in figuring out how exactly I can simplify my views by moving parts of them into helpers. For example, I've always read that conditionals in your views are prime candidates for extraction into helpers, but I couldn't really find examples of this, and my attempts to achieve this failed.
For example, suppose I have:
#index.html.erb
<% for beast in #beasts do -%>
<% if beast.dead? -%>
<%= beast.body %>
<%= link_to "bury", bury_beast_path( :id => beast.id ) %>
<% else -%>
<%= beast.body %>
<%= link_to "kill!", kill_beast_path( :id => beast.id ) %>
<% end -%>
<% end -%>
It annoys me a little to have this in my view, but how exactly could I move this to a helper instead? And further simplify it, if possible. (I've read somewhere that conditionals are bad but it's just beyond me how you could program anything without them.)
Another example: I need to id my body tags with the format controller_action. The best I've got so far is this:
#index.html.erb
<body id="<%= controller_action %>">
…and…
#application_helper.rb
def controller_action
#id = #controller.controller_name + "_" + #controller.action_name
end
I'm no expert, but that's still ugly even to me.
To make things more complicated, Ryan Singer said something I liked: to treat ERB like an image tag, using helpers to "reveal intention". Then in the next breath saying that you should have no HTML in helpers for that is the way to hell. WTF? How are both things compatible? If it's come to the point where you can just declare behaviors in the view, surely there should be a lot of HTML to be rendered behind the scenes? I can't grasp it.
So, that's basically it. I'd appreciate if anyone could share some thoughts on this, or point me to some good in depth reading on the subject – which I've found to have a really weak coverage on the web. I've already googled it to exhaustion but who knows.
Refactoring makes your views easier to maintain. The problem is choosing where the refactored code goes.
Your two choices are partials and helpers. There's no stone-set rules dictating which should be used where. There are a couple of guidelines floating around like the one stating that helpers should not contain HTML.
Generally partials are better suited for refactoring sections that are more HTML/ERB/HAML than ruby. Helpers on the other hand are used for chunks of ruby code with minimal HTML or generating simple HTML from parameters.
However, I don't agree with the sentiment that helpers should contain no HTML at all. A little is ok, just don't over do it. The way helpers are processed hinder their use for producing large amounts of HTML. Which is why it's suggested that your helpers contain minimal amounts of HTML. If you look at the source the helpers that ship with rails you will notice that most of them generate html. The few that don't, are mainly used to generate parameters and evaluate common conditions.
For example, any of the form helpers or link_to variants fit the first form of helpers. While things like url_for and logged_in? as supplied by various authentication models are of the second kind.
This is the decision chain I use to determine whether to factor code from a view into a partial or helper.
Repeating or nearly identical statements producing a single shallow html tag? => helper.
Common expression used as an argument for another helper? => helper.
Long expression (more than 4 terms) used as an argument for another helper? => helper.
4 or more lines of ruby (that is not evaluated into HTML)? => helper.
Pretty much everything else => partial.
I'm going to use the code you're looking to refactor as an example:
I would refactor the view in the question this way:
app/helpers/beast_helper.rb:
def beast_action(beast)
if beast.dead?
link_to "bury", bury_beast_path(beast)
else
link_to "kill!", kill_beast_path(beast)
end
end
app/views/beasts/_beast.html.erb:
<%= beast.body %>
<%= beast_action(beast) %>
app/views/beasts/index.html.erb:
<%= render :partial => "beast", :collection => #beasts %>
It's technically more complicated, because it's 3 files, and 10 lines total as opposed to 1 file and 10 lines. The views are now only 3 lines combined spread over 2 files. The end result is your code is much more DRY. Allowing you to reuse parts or all of it in other controllers/actions/views with minimal added complexity.
As for your body tag id. You should really be using content_for/yield. For that kind of thing.
app/views/layouts/application.html.erb
...
<body id="<%= yield(:body_id) %>">
...
app/views/beasts/index.html.erb
<% content_for :body_id, controller_action %>
...
This will allow you to override the id of the body in any view that requires it. Eg:
app/views/users/preferences.html.erb
<% content_for :body_id, "my_preferences" %>
The first thing I'd do would be this:
#index.html.erb
<%= render #beasts %>
#_beast.html.erb
<%= beast.body %>
<%= link_to_next_beast_action(beast) %>
#beast_helper.rb
def link_to_next_beast_action(beast)
if beast.dead?
link_to "bury", bury_beast_path( :id => beast.id )
else
link_to "kill!", kill_beast_path( :id => beast.id )
end
end
What I've done is separate out the rendering of the beast into a partial which uses collection semantics.
Then I've moved the logic for showing the kill/bury links into a beast helper. This way if you decide to add another action (for example, 'bring back from dead'), you'll only have to change your helper.
Does this help?
A third choice is to use a view model from the Cells gem. This is a very popular framework that brings object-orientation to the view layer in Rails.
# app/cells/beast/cell.rb
class Beast::Cell < Cell::Concept
def show
return dead if model.dead?
kill
end
private
def dead
link_to "bury", bury_beast_path( :id => model.id )
# you could render a view here, too!
end
def kill
link_to "kill!", kill_beast_path( :id => model.id )
end
end
You then render a view model using a helper (in the view or controller).
# app/views/beasts/index.erb
<%= concept(:beast, #beast).call %>
<%-# this returns the link content %>
That's all! You can test this cell isolated in a separate test. Cells also give you view rendering, view inheritance and many more things.
As an example, you could use a view for the kill link.
# app/cells/beast/cell.rb
class Beast::Cell < Cell::Concept
# ..
def kill
render :kill
end
end
This renders the cell's killer view.
# app/cells/beast/views/index.erb
<%= link_to "kill!", kill_beast_path( :id => model.id ) %>
Note the location of the view, it's nicely packaged into the cell directory.
And, yes, cells can do HAML and any other template engine supported by AbstractController.
Another startegy would be to not use templates and helpers at all.
For rendering you could :
render your views directly from your controllers using render(:inline => ). If you still want to keep Views and Controllers formally separated you can create modules / mixins that you include into the controllers.
or create your own view classes and use them to render your response.
The idea behind this is that helpers and rails erb templating system don't take advantage of OOP, so that at the end of the day you can't define general behaviours that you'll specialize according to each controller's/request's needs; more often than not one ends up rewriting very similar looking chunks of code, which is not very nice from a maintenance standpoint.
Then if you still need some helper methods (eg. form_tag, h, raw, ...) you only have to include them in your controller / dedicated view class.
See this : rails-misapprehensions-helpers-are-shit for a fun but useful article.
EDIT: to not sound like a complete douche, I'd say implementing this depends on how big your application is supposed to be, and how often you're going to have to update your code. Plus, if you're delegating the design to a non-programmer, he/she may well be in for some programming courses before digging into your code, which admittedly would be less directly understandable than with templates syntax.

Resources