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.
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.
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
.
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.
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.