I have a loop that renders a partial
1000.times do |i|
render partial: 'test', locals: {i: i}
end
this is really slow, up to 0.1 ms for foreach render call, even if the partial only prints out i
my_partial = render_to_method(partial: 'test')
1000.times do |i|
my_partial(locals: {i: i})
end
Now this should be doing the same thing way faster right? But I don't know how to do this.
update:
I've tried the to do it this way:
Haml::Engine.new(File.read(File.expand_path File.dirname(FILE)+"/../table/#{column.macro}/_#{column.display_type}.haml"))
.render(OpenStruct.new({column_value: column_value, object: object}))
two major drawbacks:
- The path to the views will not watch for fallbacks like it does when you do it with render (parital: 'partial' will look for the parital in the current view dir, in some gems and also in the view/application.
- The Rails view helpers aren't available any more
update 2:
Many of the answers try to solve the problem by using other techniques. I my real application this techniques can't be applied. https://github.com/antpaw/bhf/blob/master/app/views/bhf/pages/_platform.haml#L54 mainly because this line is a dynamic string that sometimes links to partials that don't even exsist in the gem and are defined in the main_app. I wonder why it's so hard to do something that is so basic: 1. grab the view. 2. render it. (3. render it again.)
update 3:
@Flash Gordon suggested this https://gist.github.com/antpaw/d6670c23d6f08e35812e#file-gistfile1-haml-L54
template = lookup_context.find_template "#{column.macro}/#{column.display_type}", ['bhf/table'], true
template.render(self, {column_value: column_value, object: object})
it almost works just having some troubles with locals
. But it already feels like the part where you grab the template and the render part is well separated.
Here is an example from one of my previous answers. It's extracted from PartialRenderer
sources.
- local_names = [:i]
- partials = {}
- 1000.times do |i|
- name = 'name_%s' % (i % 10)
- partials[name] ||= lookup_context.find_template(name, lookup_context.prefixes, true, local_names)
= partials[name].render(self, i: i)
I'd recommend you to wrap it with a helper method. Keep in mind that locals' names appear here twice: first in local_names
as an array and second in hash's keys passed as the second argument of #render
method.
I love this question, but it may be a "wrong tool for the job" thing
Basically:
- if the partial is simple - consider not using a partial at all, the overhead isn't worth it
- if the partial is complex - then rendering 1000s of things will take TIME out of which partial overhead is just a fraction of, you'll need a plan B
Plan Bs:
- render 25-50-100.... thingies at first and fetch additional records from the server as the user scrolls - http://railscasts.com/episodes/114-endless-page
- send a JSON array from the server and let client side JS iterate through the collection and render HTML - this way the partial template can still be rendered in rails and client side JS can do
gsub
- cache everything you possibly can - this doesn't include forms unfortunately which are typically the slowest rendering components
- ...
PS have you tried out cells? https://github.com/apotonick/cells
This is an interesting proposition and it seems harder than I thought to find information about it. However from what I can tell from the implementation of the response
object I would think it should be possible to append (<<
) your output to the stream
object of the response
object. This will be rendered as a string into the response#body
as necessary.
The tricky part is to find a good place to define your my_partial
method. While it is not commonplace to do this in a View
it should still be possible and I think you should have access to the response
object directly. Otherwise you might define it in a helper, though you probably do not want to use this code in a controller.
Sorry if this is rather an idea than an answer, but I lack the time to test it properly.
You could add a helper method and use content_tag
. This accomplishes your goal of having a method work as a partial.
def my_partial(i)
content_tag(:div, i, class: 'iterator')
end
Using helper methods with content_tag
probably isn't a good solution if you have a lot of content in your partial, for readability's sake.
An alternative recommendation: Instead of rendering the same partial multiple times within a loop, you could render a single partial that includes a loop. If you needed to reuse the partial for only one object, you could just pass the object as an array.
Here's a more real-world example:
# index.html.erb
<%= render partial: 'dogs', locals: {dogs: @dogs} %>
# show.html.erb
<%= render partial: 'dogs', locals: {dogs: [@dog]} %>
# _dogs.html.erb
<% dogs.each do |dog| %>
<%= dog.name %>
<% end %>
Use :collection
option:
= render partial: 'test', collection: 10.times.to_a, as: :i
Docs: http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
If you need render many times and it is slow, I suggest you do not call render
many times. I know, I am captain obvious.
If locals
contains simple strings or numbers, try to render your view once, and then just replace parts with actual variables.
text = render(partial: 'test', locals: {i: '$'})
1000.times do |i|
output[i] = text.gsub('$', i)
end
来源:https://stackoverflow.com/questions/24238518/convert-a-partial-to-method-block-for-speed