Skip to content

Instantly share code, notes, and snippets.

@wangjohn
Last active September 14, 2021 08:53
Show Gist options
  • Save wangjohn/5418350 to your computer and use it in GitHub Desktop.
Save wangjohn/5418350 to your computer and use it in GitHub Desktop.
Rendering Partials in Rails

Let's look at what happens when you do something like this inside of a view:

<%= render partial: 'some_partial' -%>

To render a partial, Rails has a helper method inside of Action View. The render method serves as a delegator and takes its input arguments and provides them to the view_renderer object.

The view_renderer object is initialized when ActionView::Base is initialized and is of the ActionView::Renderer class. This class performs all of the rendering that is necessary inside of the views. The design pattern of delegating to an object is used. When you call view_renderer.render, you are asking the view_renderer object to delegate the arguments of your call to another more specific object.

Inside of the view_renderer, you can get sent to different classes depending on the options that the user originally passed. For example, if you were attempting to render a partial, the view_renderer.render method would send your arguments to the PartialRenderer class. If you were rendering a template, then the arguments would be sent to the TemplateRenderer class.

AbstractRenderer Class

All of these classes are subclasses of the AbstractRenderer which provides the interface for which these renderers operate. The skeleton of these templates are very simple - they are first instantiated, then render is called on the new object.

For example, if one is attempting to render a partial, the view_renderer would eventually send you to this method:

def render_partial(context, options, &block) #:nodoc:
  PartialRenderer.new(@lookup_context).render(context, options, block)
end

Each render passes in a @lookup_context and the context and options from the original call to render by the user. From there, the PartialRenderer object does all the heavy lifting of setting up, finding, and rendering the partial that it was given.

The Lookup Context

However, in order for the PartialRenderer to do its job correctly, it needs to have some context as to how to look up partials. The LookupContext class holds all the information required to lookup templates. Its main job is to store view paths and details.

The view paths hold the location of the partial. If you had called <%= render partial: 'my_partial' -%> inside of the fake_stuff view, then the path to the partial would look something like app/views/fake_stuff/_my_partial.html.erb. The paths are found by either calling the to_partial_path on an object or by using the string passed to the partial.

If you used the syntax <%= render partial: @some_object -%>, then the path would be found by calling @some_object.to_partial_path.

Templates

Once the partial path has been found, a Template object is created and rendered based on the partial's path. This is the final phase of rendering a partial. A Template object contains a template handler, a source, an identifier, and a number of other attributes.

To render a template, the Template object takes the source that it was initialized with to encode and compile it. The compile method makes sure that the source has the correct encoding, runs the source through the appropriate template handler, and then evals the source to compile it.

Template Handler

The last thing that I should talk about is how different templates are handled. Rails provides a canonical way to create a new type of template (although ERB is the default). Each template handler is a different class under the ActionView::Template::Handlers module.

The only thing that the template handler has to do is implement the method call(template). Once this method is implemented, it can be plugged into ActionView and be used to modify templates when they are getting compiled.

The ERB template handler will parse template.source. The handler looks for ruby code and injects it into the template when appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment