In this part we will continue our exploration of how Rails finds your templates. If you need a refresh, here is Part1, Part 2 and Part 3.
In last part we saw that ActionView::LookupContext#find_template is just a delegation to ActionView::PathSet#find. So let’s check what ActionView::PathSet#find is doing.
ActionView::PathSet
From the class name of PathSet we can get an idea that this class is to manage a set of pathes, where the templates are stored. Let’s have a look at the definition of this class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
- We can see that the initialize method accepts a paths param, which is normally an array of strings. And in initialize method, it will call the typecast the paths and then store the typecasted value in @paths instance variable
- for each element in the paths, the typecast method will convert it to an OptimizedFileSystemResolver instance.
- The find_all method will interate each resolver instance in paths and return the first value which is not empty
- The find method just delegates to find_all method, if it can’t find a template, it will raise MissingTemplate exception.
In LookupContext class, we can see how the ActionView::PathSet instance is created.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
The view_paths= method is defind in module ActionView::LookupContext::ViewPaths and ActionView::LookupContext includes this module. We can see that the when calling view_paths= method, an instance of ActionView::PathSet will be created and set to the @view_paths instance variable of ActionView::LookupContext.
So how this view_paths= method is called? It’s in ActionView::LookupContext#initialize method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
So the view_paths is passed to ActionView::LookupContext instances in initialize. And in ActionView::ViewPaths it initializes ActionView::LookupContext.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
|
This ActionView::ViewPaths will be included in a controller class. We can see that by default the view_paths is an empty array. But it provides append_view_path and prepend_view_path to add a view path at the end or the front of view paths. Notice that the prepend_view_path and append_view_path are defined both as class methods and instance methods.
So how the view path is initialized? Actually it’s in Rails::Engine class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
a initializer defines a code block which will be called during application startup. So when load :action_controller it will call prepend_view_path and the view_paths contains only one element, which is the app/views folder in your rails application.
OptimizedFileSystemResolver
Now understand how ActionView::PathSet is initialized, and we also know that in find method it actually delegates to the resolver. So let’s see how the resolver implements the method.
The Resolver is to resolve a template when passing the details. The class relationship is as the above diagram,
We can see that by default the resolver used in ActionView::PathSet, which is ActionView::OptimizedFileSystemResolver, extends from ActionView::FileSystemResolver, which in terms extends from ActionView::PathResolver, which extends from ActionView::Resolver, which is the base class for all resolvers.
The base class ActionView::Resolver provides some common functionalities such as caching for all sub-class resolvers. However here I will focus on ActionView::PathResolver, which implements the logic how to find a template in a path.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
|
The most important methods are build_query and find_template_paths.
- The PathResolver will use a pattern to search templates in path, and by default the pattern is DEFAULT_PATTERN
- build_query: This method is to build a query from the path and details. Remember in last part, ActionView::AbstractRenderer#extract_details will extract details from options. For example, for an index action in Articles controller, the query will be
articles/index{.en,}{.html,}{+:variants,}{.haml,}
For this query, in find_template_paths it will call Dir[query], which is actually an alias to glob which is to expand the query. For example, for the above query, {p, q} matchs either p, or q. So for example for {.en,} it could either match .en or an empty string. So if we define a template articles/index.html.haml, it will match the query. For more information, consult Ruby doc.
So for each matched template, in query method it will create a Template to wrap the template file.
Summary
So in summary here we can see that the flow for Rails to find a template is as following,
- Rails initializes a set of Paths for resolve templates, by default it contains only one path app/views
- When an action of a controller is accessed, the render will be called either explicitly or implicitly.
- Rails will extract the prefixes, action and some details from the options passed to render,
- Rails find the template by search in the path set using a query composed from prefixes, action and details.