Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Template Rendering in Rails
Created by Stan Lo ( @st0012 )
About me
Stan Lo
GitHub: @st0012
Twitter: @_st0012
Email: stan001212@gmail.com
Work at Polydice(iCook)
Love to contribute ...
Steps for Rails to render template
1. Create the rendering context
2. Prepare for rendering
3. Find template and convert i...
1. Rails creates an ActionView::Base instance as template
rendering's context.
2. Then collects and initializes the inform...
The beginning of template rendering process
Normally we render template in two ways
• Automatically rendering by controller action
class PostsController < Application...
Either way, they will both call
ActionView::Rendering#render_to_body
actionview/lib/action_view/rendering.rb
def render_to...
And I'll use this action as example to explain the rendering process
class PostsController < ActionController::Base
def in...
Here's the first phase of template rendering:
Create rendering context
Let's take a look a #_render_template
def _render_template(options)
variant = options.delete(:variant)
assigns = options.d...
And we can see the keyword: view_context
def _render_template(options)
variant = options.delete(:variant)
assigns = option...
view_context is an instance of view_context_class.
def view_context
view_context_class.new(view_renderer, view_assigns, se...
And this is where the view_context_class comes from.
module ClassMethods
def view_context_class
@view_context_class ||= be...
It turns out Rails creates an anonymous class (which inherites
from ActionView::Base) as view_context_class.
module ClassM...
The code here creates an anonymous class that inherits
ActionView::Base
module ClassMethods
def view_context_class
@view_c...
And it will also includes some helpers depends on the context
module ClassMethods
def view_context_class
@view_context_cla...
So view_context can be considered as an instance of
ActionView::Base, which will be created in every rendering.
And its ma...
The second phase: Prepare for rendering
Let's back to #_render_template. After creates the context, Rails
will call view_renderer's #render method.
def _render_te...
So what's ActionView::Renderer?
It stores an instance variable called @lookup_context, which
carries some important info for finding our template. Such as
...
This is the lookup context created in our example. We'll see it
again later.
#<ActionView::LookupContext:0x007f8763cee608
...
Renderer also determines whether we are rendering a partial or a
template, and passes the informations to coorresponding r...
It uses the lookup context it holds to initialize corresponding
renderer.
module ActionView
class Renderer
# Direct access...
I will explain the rest of processes using
TemplateRenderer#render as an example.
This is how TemplateRenderer#render looks like:
module ActionView
class TemplateRnderer
def render(context, options)
@view...
As you can see, our next step is to find the template
module ActionView
class TemplateRnderer
def render(context, options)
...
So here's the third phase: Finding Template
This is the most complicated part in the rendering process, so we'll spend a l...
After calling #determine_template, we then calls #find_template
module ActionView
class TemplateRenderer
def render(contex...
But #find_template is actually delegated to @lookup_context
delegate :find_template, :find_file, :template_exists?,
:any_t...
So let's take a look at @lookup_context again
#<ActionView::LookupContext:0x007f8763cee608
@cache=true,
@details=
{:locale...
It's an instance of ActionView::LookupContext.
And as I said before, it carries some important info for finding
templates l...
Details: Contains several informations for building a template
searching query later
#<ActionView::LookupContext:0x007f876...
Prefix: Target template's prefix, like posts or application
#<ActionView::LookupContext:0x007f8763cee608
@cache=true,
@detai...
View Paths: It tells Rails where to look for the template files. For
example: our app's app/views
#<ActionView::LookupConte...
After received #find_template from template renderer,
LookupContext will call #find on its @view_paths.
module ActionView
...
The @view_paths is an instance of ActionView::PathSet and
contains several resolver instances.
<ActionView::PathSet:0x007f...
Every resolver represents a place Rails should looking for
templates, such as app/views in your app or devise's app/views....
So a path set can looks like:
After we call #find on PathSet, it will iterate through every
resolvers it has to look for templates.
module ActionView
cl...
And every resolver uses #find_templates to search the template
we want from templates it holds.
module ActionView
class Re...
Here's the last step for finding a template, I'll split it into several
phases.
First, Rails will create a resolver path object.
def find_templates(name, prefix, partial, details, outside_app_allowed = ...
Then we can use this path object and lookup context's details to
build a query
def find_templates(name, prefix, partial, d...
This query is a kind of formal language, you can consider it a
regular expression for matching file's path and extension.
"...
And every resolver will use this query to find the target in its
templates.
def query(path, details, formats, outside_app_a...
If it found a matched template, it returns its absolute path.
["/path_to_your_app/app/views/layouts/application.html.erb"]
Finally, Rails can use the absolute path it got to read the file and
use its content to initialize an ActionView::Template'...
The above is the third phase of template rendering in Rails.
Then here comes the fourth phase: Compile
Template
After ActionView::TemplateRenderer found the template, it'll call
#render_template and pass the template object as a argum...
Then Rails will call #render on the template object with locals
(local variables) and the view context.
def render_templat...
And #render will compile the template into a method and call it
immediately.
def render(view, locals, buffer = nil, &block...
Every template will only be compiled once, Rails will check if it has
been compiled before compiles it.
def compile!(view)...
And this is how Template#compile looks like.
def compile(mod)
encode!
method_name = self.method_name
code = @handler.call(...
The source part is a string template that will become a method
definition in Ruby.
source = <<-end_src
def #{method_name}(l...
I'll explain this process with the example partial file: app/views/
users/_hello.html.erb:
Hello <%= user.name %>!
And we r...
The source generated with our example would look like this:
"def _app_views_users__hello_html_erb___4079420934646067298_70...
First, we need to take a look at locals_code
"def _app_views_users__hello_html_erb___4079420934646067298_70142066674000(lo...
Remember how we render our partial?
render partial: "users/hello", locals: { user: @user }
The local_assigns is actually t...
Then the code section will be generated differently according to the
template's format and handler. In our case we are ren...
Now we have a complete method definition in source variable.
Rails will then use module_eval to make mod
(ActionView::Compi...
The fifth(last) phase: Get final result
This is the simplist phase, Rails just calls the method it defined
and it'll return the result.
def render(view, locals, bu...
To summarize
• Template rendering is a long journey, so it consumes a lot of
computing resources and time.
• Everytime we ...
To summarize
• LookupContext plays a key role in the rendering process since it
holds view paths and details for searching...
Thank you for your listening
Join the Goby project
We're at early stage and looking for contributors, contact me it
you're insterested in it (or just s...
Template rendering in rails
Upcoming SlideShare
Loading in …5
×

Template rendering in rails

3,734 views

Published on

An introduction about how Rails renders your view templates

Published in: Software

Template rendering in rails

  1. 1. Template Rendering in Rails Created by Stan Lo ( @st0012 )
  2. 2. About me Stan Lo GitHub: @st0012 Twitter: @_st0012 Email: stan001212@gmail.com Work at Polydice(iCook) Love to contribute open source projects, currently working on Goby language
  3. 3. Steps for Rails to render template 1. Create the rendering context 2. Prepare for rendering 3. Find template and convert it into an object (most complicated part) 4. Compile template object into a method 5. Call compiled method and get final result
  4. 4. 1. Rails creates an ActionView::Base instance as template rendering's context. 2. Then collects and initializes the informations Rails needs for finding a template. 3. Find the template file and use it to create an ActionView::Template instance. 4. Compile the template object's content into ActionView::Base's method 5. Call the compiled method on ActionView::Base's instance and it'll return the final result.
  5. 5. The beginning of template rendering process
  6. 6. Normally we render template in two ways • Automatically rendering by controller action class PostsController < ApplicationController def index @posts = Post.all end end • Or call render manually class PostsController < ApplicationController def search @posts = Post.search(params[:q]) render :index end end
  7. 7. Either way, they will both call ActionView::Rendering#render_to_body actionview/lib/action_view/rendering.rb def render_to_body(options = {}) _process_options(options) _render_template(options) end Then call #_render_template def _render_template(options) variant = options.delete(:variant) assigns = options.delete(:assigns) context = view_context context.assign assigns if assigns lookup_context.rendered_format = nil if options[:formats] lookup_context.variants = variant if variant view_renderer.render(context, options) end
  8. 8. And I'll use this action as example to explain the rendering process class PostsController < ActionController::Base def index @posts = Post.all end end
  9. 9. Here's the first phase of template rendering: Create rendering context
  10. 10. Let's take a look a #_render_template def _render_template(options) variant = options.delete(:variant) assigns = options.delete(:assigns) context = view_context context.assign assigns if assigns lookup_context.rendered_format = nil if options[:formats] lookup_context.variants = variant if variant view_renderer.render(context, options) end
  11. 11. And we can see the keyword: view_context def _render_template(options) variant = options.delete(:variant) assigns = options.delete(:assigns) context = view_context context.assign assigns if assigns lookup_context.rendered_format = nil if options[:formats] lookup_context.variants = variant if variant view_renderer.render(context, options) end
  12. 12. view_context is an instance of view_context_class. def view_context view_context_class.new(view_renderer, view_assigns, self) end def view_context_class @_view_context_class ||= self.class.view_context_class end Rails initializes it with view_renderer and view_assigns, which represents ActionView::Renderer and instance variables created in controller. (In our case it's @posts)
  13. 13. And this is where the view_context_class comes from. module ClassMethods def view_context_class @view_context_class ||= begin supports_path = supports_path? routes = respond_to?(:_routes) && _routes helpers = respond_to?(:_helpers) && _helpers Class.new(ActionView::Base) do if routes include routes.url_helpers(supports_path) include routes.mounted_helpers end if helpers include helpers end end end end end
  14. 14. It turns out Rails creates an anonymous class (which inherites from ActionView::Base) as view_context_class. module ClassMethods def view_context_class @view_context_class ||= begin supports_path = supports_path? routes = respond_to?(:_routes) && _routes helpers = respond_to?(:_helpers) && _helpers Class.new(ActionView::Base) do if routes include routes.url_helpers(supports_path) include routes.mounted_helpers end if helpers include helpers end end end end end
  15. 15. The code here creates an anonymous class that inherits ActionView::Base module ClassMethods def view_context_class @view_context_class ||= begin supports_path = supports_path? routes = respond_to?(:_routes) && _routes helpers = respond_to?(:_helpers) && _helpers Class.new(ActionView::Base) do if routes include routes.url_helpers(supports_path) include routes.mounted_helpers end if helpers include helpers end end end end end
  16. 16. And it will also includes some helpers depends on the context module ClassMethods def view_context_class @view_context_class ||= begin supports_path = supports_path? routes = respond_to?(:_routes) && _routes helpers = respond_to?(:_helpers) && _helpers Class.new(ActionView::Base) do if routes include routes.url_helpers(supports_path) include routes.mounted_helpers end if helpers include helpers end end end end end
  17. 17. So view_context can be considered as an instance of ActionView::Base, which will be created in every rendering. And its main function is to provide isolated environment for every template rendering.
  18. 18. The second phase: Prepare for rendering
  19. 19. Let's back to #_render_template. After creates the context, Rails will call view_renderer's #render method. def _render_template(options) ...... context = view_context ...... view_renderer.render(context, options) end And the view_renderer is actually an ActionView::Renderer's instance.
  20. 20. So what's ActionView::Renderer?
  21. 21. It stores an instance variable called @lookup_context, which carries some important info for finding our template. Such as locale, format, handlers...etc. class Renderer attr_accessor :lookup_context def initialize(lookup_context) @lookup_context = lookup_context end end
  22. 22. This is the lookup context created in our example. We'll see it again later. #<ActionView::LookupContext:0x007f8763cee608 @cache=true, @details= {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}, @details_key=nil, @prefixes=["posts", "application"], @rendered_format=nil, @view_paths= #<ActionView::PathSet:0x007f8763cee428 @paths= [#<ActionView::OptimizedFileSystemResolver:0x007f8763a2f548 @cache=#<ActionView::Resolver::Cache:0x7f8763a2f228 keys=1 queries=0>, @path="/Users/stanlow/projects/sample/app/views", @pattern= ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}">]>>
  23. 23. Renderer also determines whether we are rendering a partial or a template, and passes the informations to coorresponding renderer. def render(context, options) if options.key?(:partial) render_partial(context, options) else render_template(context, options) end end
  24. 24. It uses the lookup context it holds to initialize corresponding renderer. module ActionView class Renderer # Direct access to template rendering. def render_template(context, options) #:nodoc: TemplateRenderer.new(@lookup_context).render(context, options) end # Direct access to partial rendering. def render_partial(context, options, &block) #:nodoc: PartialRenderer.new(@lookup_context).render(context, options, block) end end end
  25. 25. I will explain the rest of processes using TemplateRenderer#render as an example.
  26. 26. This is how TemplateRenderer#render looks like: module ActionView class TemplateRnderer def render(context, options) @view = context @details = extract_details(options) template = determine_template(options) prepend_formats(template.formats) @lookup_context.rendered_format ||= (template.formats.first || formats.first) render_template(template, options[:layout], options[:locals]) end end end
  27. 27. As you can see, our next step is to find the template module ActionView class TemplateRnderer def render(context, options) @view = context @details = extract_details(options) template = determine_template(options) prepend_formats(template.formats) @lookup_context.rendered_format ||= (template.formats.first || formats.first) render_template(template, options[:layout], options[:locals]) end end end
  28. 28. So here's the third phase: Finding Template This is the most complicated part in the rendering process, so we'll spend a lot of time here.
  29. 29. After calling #determine_template, we then calls #find_template module ActionView class TemplateRenderer def render(context, options) ...... template = determine_template(options) ...... end def determine_template(options) ...... if ...... elsif options.key?(:template) # Means Rails already found that template if options[:template].respond_to?(:render) options[:template] else find_template(options[:template], options[:prefixes], false, keys, @details) end ........ end end end end
  30. 30. But #find_template is actually delegated to @lookup_context delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context
  31. 31. So let's take a look at @lookup_context again #<ActionView::LookupContext:0x007f8763cee608 @cache=true, @details= {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}, @details_key=nil, @prefixes=["posts", "application"], @rendered_format=nil, @view_paths= #<ActionView::PathSet:0x007f8763cee428 @paths= [#<ActionView::OptimizedFileSystemResolver:0x007f8763a2f548 @cache=#<ActionView::Resolver::Cache:0x7f8763a2f228 keys=1 queries=0>, @path="/Users/stanlow/projects/sample/app/views", @pattern= ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}">]>>
  32. 32. It's an instance of ActionView::LookupContext. And as I said before, it carries some important info for finding templates like: • Details • Prefixes • View Paths
  33. 33. Details: Contains several informations for building a template searching query later #<ActionView::LookupContext:0x007f8763cee608 @cache=true, @details= {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}, @details_key=nil, @prefixes=["posts", "application"], @rendered_format=nil, @view_paths= #<ActionView::PathSet:0x007f8763cee428 @paths= [#<ActionView::OptimizedFileSystemResolver:0x007f8763a2f548 @cache=#<ActionView::Resolver::Cache:0x7f8763a2f228 keys=1 queries=0>, @path="/Users/stanlow/projects/sample/app/views", @pattern= ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}">]>>
  34. 34. Prefix: Target template's prefix, like posts or application #<ActionView::LookupContext:0x007f8763cee608 @cache=true, @details= {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}, @details_key=nil, @prefixes=["posts", "application"], @rendered_format=nil, @view_paths= #<ActionView::PathSet:0x007f8763cee428 @paths= [#<ActionView::OptimizedFileSystemResolver:0x007f8763a2f548 @cache=#<ActionView::Resolver::Cache:0x7f8763a2f228 keys=1 queries=0>, @path="/Users/stanlow/projects/sample/app/views", @pattern= ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}">]>>
  35. 35. View Paths: It tells Rails where to look for the template files. For example: our app's app/views #<ActionView::LookupContext:0x007f8763cee608 @cache=true, @details= {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}, @details_key=nil, @prefixes=["posts", "application"], @rendered_format=nil, @view_paths= #<ActionView::PathSet:0x007f8763cee428 @paths= [#<ActionView::OptimizedFileSystemResolver:0x007f8763a2f548 @cache=#<ActionView::Resolver::Cache:0x7f8763a2f228 keys=1 queries=0>, @path="/Users/stanlow/projects/sample/app/views", @pattern= ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}">]>>
  36. 36. After received #find_template from template renderer, LookupContext will call #find on its @view_paths. module ActionView class LookupContext module ViewPaths def find(name, prefixes = [], partial = false, keys = [], options = {}) @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options)) end alias :find_template :find end include ViewPaths end end
  37. 37. The @view_paths is an instance of ActionView::PathSet and contains several resolver instances. <ActionView::PathSet:0x007f87678f9648 @paths= [#<ActionView::OptimizedFileSystemResolver:0x007f8763a2f548 @cache= #<ActionView::Resolver::Cache:0x7f8763a2f228 keys=2 queries=0>, @path= "/Users/stanlow/projects/sample/app/views", @pattern= ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}">]>
  38. 38. Every resolver represents a place Rails should looking for templates, such as app/views in your app or devise's app/views. <ActionView::PathSet:0x007f87678f9648 @paths= [ #<ActionView::OptimizedFileSystemResolver:0x007f8763a2f548 @cache=#<ActionView::Resolver::Cache:0x7f8763a2f228 keys=2 queries=0>, @path="/Users/stanlow/projects/sample/app/views", @pattern=":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"> ]>
  39. 39. So a path set can looks like:
  40. 40. After we call #find on PathSet, it will iterate through every resolvers it has to look for templates. module ActionView class PathSet def _find_all(path, prefixes, args, outside_app) prefixes.each do |prefix| paths.each do |resolver| if outside_app templates = resolver.find_all_anywhere(path, prefix, *args) else templates = resolver.find_all(path, prefix, *args) end return templates unless templates.empty? end end [] end end end
  41. 41. And every resolver uses #find_templates to search the template we want from templates it holds. module ActionView class Resolver def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[]) cached(key, [name, prefix, partial], details, locals) do find_templates(name, prefix, partial, details) end end def find_all_anywhere(name, prefix, partial=false, details={}, key=nil, locals=[]) cached(key, [name, prefix, partial], details, locals) do find_templates(name, prefix, partial, details, true) end end end end
  42. 42. Here's the last step for finding a template, I'll split it into several phases.
  43. 43. First, Rails will create a resolver path object. def find_templates(name, prefix, partial, details, outside_app_allowed = false) path = Path.build(name, prefix, partial) ...... end Which looks like: #<ActionView::Resolver::Path:0x007ffa3a5523a0 @name="index", @partial=false, @prefix="posts", @virtual="posts/index">
  44. 44. Then we can use this path object and lookup context's details to build a query def find_templates(name, prefix, partial, details, outside_app_allowed = false) path = Path.build(name, prefix, partial) query(path, details, details[:formats], outside_app_allowed) end def query(path, details, formats, outside_app_allowed) query = build_query(path, details) ...... end
  45. 45. This query is a kind of formal language, you can consider it a regular expression for matching file's path and extension. "/Users/stanlow/projects/sample/app/views/posts/index{.en,}{.html,} {}{.raw,.erb,.html,.builder,.ruby,.coffee,.jbuilder,}"
  46. 46. And every resolver will use this query to find the target in its templates. def query(path, details, formats, outside_app_allowed) query = build_query(path, details) template_paths = find_template_paths(query) ...... template_paths.map do |template| handler, format, variant = extract_handler_and_format_and_variant(template, formats) contents = File.binread(template) Template.new(contents, File.expand_path(template), handler, virtual_path: path.virtual, format: format, variant: variant, updated_at: mtime(template) ) end end
  47. 47. If it found a matched template, it returns its absolute path. ["/path_to_your_app/app/views/layouts/application.html.erb"]
  48. 48. Finally, Rails can use the absolute path it got to read the file and use its content to initialize an ActionView::Template's instance. def query(path, details, formats, outside_app_allowed) query = build_query(path, details) template_paths = find_template_paths(query) ...... template_paths.map do |template| handler, format, variant = extract_handler_and_format_and_variant(template, formats) contents = File.binread(template) Template.new(contents, File.expand_path(template), handler, virtual_path: path.virtual, format: format, variant: variant, updated_at: mtime(template) ) end end
  49. 49. The above is the third phase of template rendering in Rails.
  50. 50. Then here comes the fourth phase: Compile Template
  51. 51. After ActionView::TemplateRenderer found the template, it'll call #render_template and pass the template object as a argument. module ActionView class TemplateRenderer < AbstractRenderer #:nodoc: def render(context, options) ...... # Found your template template = determine_template(options) ...... render_template(template, options[:layout], options[:locals]) end end end
  52. 52. Then Rails will call #render on the template object with locals (local variables) and the view context. def render_template(template, layout_name = nil, locals = nil) #:nodoc: view, locals = @view, locals || {} render_with_layout(layout_name, locals) do |layout| instrument(:template, .....) do # the block will only be execute if your template contains `yield` template.render(view, locals) { |*name| view._layout_for(*name) } end end end
  53. 53. And #render will compile the template into a method and call it immediately. def render(view, locals, buffer = nil, &block) instrument_render_template do compile!(view) view.send(method_name, locals, buffer, &block) end rescue => e handle_render_error(view, e) end
  54. 54. Every template will only be compiled once, Rails will check if it has been compiled before compiles it. def compile!(view) return if @compiled @compile_mutex.synchronize do return if @compiled ..... instrument("!compile_template") do compile(mod) end ...... @compiled = true end end
  55. 55. And this is how Template#compile looks like. def compile(mod) encode! method_name = self.method_name code = @handler.call(self) source = <<-end_src def #{method_name}(local_assigns, output_buffer) _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect}; _old_output_buffer = @output_buffer;#{locals_code};#{code} ensure @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer end end_src ...... mod.module_eval(source, identifier, 0) ...... end
  56. 56. The source part is a string template that will become a method definition in Ruby. source = <<-end_src def #{method_name}(local_assigns, output_buffer) _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect}; _old_output_buffer = @output_buffer;#{locals_code};#{code} ensure @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer end end_src
  57. 57. I'll explain this process with the example partial file: app/views/ users/_hello.html.erb: Hello <%= user.name %>! And we render it using render partial: "users/hello", locals: { user: @user }
  58. 58. The source generated with our example would look like this: "def _app_views_users__hello_html_erb___4079420934646067298_70142066674000(local_assigns, output_buffer)n _old_virtual_path, @virtual_path = @virtual_path, "users/hello"; _old_output_buffer = @output_buffer # locals_code user = user = local_assigns[:user] # code @output_buffer = output_buffer || ActionView::OutputBuffer.new; @output_buffer.safe_append='Hello '.freeze; @output_buffer.append=( user.name ); @output_buffer.safe_append='!n'.freeze; @output_buffer.to_sn ensuren @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffern endn"
  59. 59. First, we need to take a look at locals_code "def _app_views_users__hello_html_erb___4079420934646067298_70142066674000(local_assigns, output_buffer)n _old_virtual_path, @virtual_path = @virtual_path, "users/hello"; _old_output_buffer = @output_buffer # locals_code user = user = local_assigns[:user] # code @output_buffer = output_buffer || ActionView::OutputBuffer.new; @output_buffer.safe_append='Hello '.freeze; @output_buffer.append=( user.name ); @output_buffer.safe_append='!n'.freeze; @output_buffer.to_sn ensuren @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffern endn"
  60. 60. Remember how we render our partial? render partial: "users/hello", locals: { user: @user } The local_assigns is actually the locals { user: @user } So when we call local variable user in our view, it's actually searching the key :user's value from the locals hash. And this mapping is generated during compilation.
  61. 61. Then the code section will be generated differently according to the template's format and handler. In our case we are rendering an erb file so it's generated by ERB handler. "def _app_views_users__hello_html_erb___4079420934646067298_70142066674000(local_assigns, output_buffer)n ...... # locals_code user = user = local_assigns[:user] # code @output_buffer = output_buffer || ActionView::OutputBuffer.new; @output_buffer.safe_append='Hello '.freeze; @output_buffer.append=( user.name ); @output_buffer.safe_append='!n'.freeze; @output_buffer.to_sn ensuren @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffern endn" erb uses a buffer to store strings, it keeps appending string into the buffer and output the final result with to_s method.
  62. 62. Now we have a complete method definition in source variable. Rails will then use module_eval to make mod (ActionView::CompiledTemplates) evaluate the it, so the module will have that method. def compile(mod) .... mod.module_eval(source, identifier, 0) ... end
  63. 63. The fifth(last) phase: Get final result
  64. 64. This is the simplist phase, Rails just calls the method it defined and it'll return the result. def render(view, locals, buffer = nil, &block) instrument_render_template do compile!(view) view.send(method_name, locals, buffer, &block) end rescue => e handle_render_error(view, e) end
  65. 65. To summarize • Template rendering is a long journey, so it consumes a lot of computing resources and time. • Everytime we render a template, Rails creates an ActionView::Base's instance as an isolated rendering environment. • Rails renders templates and partials differently.
  66. 66. To summarize • LookupContext plays a key role in the rendering process since it holds view paths and details for searching a template. • A resolver represents a place to look for templates. • Every template would be compiled into a method once it gets rendered. So to some degree we can say rendering template is just calling methods on ActionView::Base's instance.
  67. 67. Thank you for your listening
  68. 68. Join the Goby project We're at early stage and looking for contributors, contact me it you're insterested in it (or just sending your first PR đ)

×