SlideShare une entreprise Scribd logo
1  sur  69
Télécharger pour lire hors ligne
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 open source projects, currently working on Goby
language
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
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.
The beginning of template rendering process
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
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
And I'll use this action as example to explain the rendering process
class PostsController < ActionController::Base
def index
@posts = Post.all
end
end
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.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
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
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)
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
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
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
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
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.
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_template(options)
......
context = view_context
......
view_renderer.render(context, options)
end
And the view_renderer is actually an ActionView::Renderer's
instance.
So what's ActionView::Renderer?
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
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,}">]>>
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
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
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 = 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
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
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.
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
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
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,}">]>>
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
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,}">]>>
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,}">]>>
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,}">]>>
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
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,}">]>
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,}">
]>
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
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
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
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 = false)
path = Path.build(name, prefix, partial)
......
end
Which looks like:
#<ActionView::Resolver::Path:0x007ffa3a5523a0
@name="index",
@partial=false,
@prefix="posts",
@virtual="posts/index">
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
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,}"
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
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'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
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 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
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
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
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
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
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
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 }
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"
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"
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.
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.
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
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, 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
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.
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.
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 sending your first PR đ)

Contenu connexe

Tendances

model.search: customize your own search logic
model.search: customize your own search logicmodel.search: customize your own search logic
model.search: customize your own search logicTse-Ching Ho
 
Design patterns in Magento
Design patterns in MagentoDesign patterns in Magento
Design patterns in MagentoDivante
 
Variables, expressions, standard types
 Variables, expressions, standard types  Variables, expressions, standard types
Variables, expressions, standard types Rubizza
 
Using of TDD practices for Magento
Using of TDD practices for MagentoUsing of TDD practices for Magento
Using of TDD practices for MagentoIvan Chepurnyi
 
Ruby on Rails ステップアップ講座 - 大場寧子
Ruby on Rails ステップアップ講座 - 大場寧子Ruby on Rails ステップアップ講座 - 大場寧子
Ruby on Rails ステップアップ講座 - 大場寧子Yasuko Ohba
 
Angular Directives from Scratch
Angular Directives from ScratchAngular Directives from Scratch
Angular Directives from ScratchChristian Lilley
 
WordPress Capabilities Magic
WordPress Capabilities MagicWordPress Capabilities Magic
WordPress Capabilities Magicmannieschumpert
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelpauldix
 
The Django Book, Chapter 16: django.contrib
The Django Book, Chapter 16: django.contribThe Django Book, Chapter 16: django.contrib
The Django Book, Chapter 16: django.contribTzu-ping Chung
 
Optimizing Magento by Preloading Data
Optimizing Magento by Preloading DataOptimizing Magento by Preloading Data
Optimizing Magento by Preloading DataIvan Chepurnyi
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologyDaniel Knell
 
Implementation of EAV pattern for ActiveRecord models
Implementation of EAV pattern for ActiveRecord modelsImplementation of EAV pattern for ActiveRecord models
Implementation of EAV pattern for ActiveRecord modelsKostyantyn Stepanyuk
 
Meet Magento Belarus debug Pavel Novitsky (eng)
Meet Magento Belarus debug Pavel Novitsky (eng)Meet Magento Belarus debug Pavel Novitsky (eng)
Meet Magento Belarus debug Pavel Novitsky (eng)Pavel Novitsky
 
ALPHA Script - XML Model
ALPHA Script - XML ModelALPHA Script - XML Model
ALPHA Script - XML ModelPROBOTEK
 
Art of Javascript
Art of JavascriptArt of Javascript
Art of JavascriptTarek Yehia
 

Tendances (20)

model.search: customize your own search logic
model.search: customize your own search logicmodel.search: customize your own search logic
model.search: customize your own search logic
 
Design patterns in Magento
Design patterns in MagentoDesign patterns in Magento
Design patterns in Magento
 
RicoLiveGrid
RicoLiveGridRicoLiveGrid
RicoLiveGrid
 
Using java beans(ii)
Using java beans(ii)Using java beans(ii)
Using java beans(ii)
 
Patterns In-Javascript
Patterns In-JavascriptPatterns In-Javascript
Patterns In-Javascript
 
Variables, expressions, standard types
 Variables, expressions, standard types  Variables, expressions, standard types
Variables, expressions, standard types
 
Using of TDD practices for Magento
Using of TDD practices for MagentoUsing of TDD practices for Magento
Using of TDD practices for Magento
 
Ruby on Rails ステップアップ講座 - 大場寧子
Ruby on Rails ステップアップ講座 - 大場寧子Ruby on Rails ステップアップ講座 - 大場寧子
Ruby on Rails ステップアップ講座 - 大場寧子
 
Angular Directives from Scratch
Angular Directives from ScratchAngular Directives from Scratch
Angular Directives from Scratch
 
WordPress Capabilities Magic
WordPress Capabilities MagicWordPress Capabilities Magic
WordPress Capabilities Magic
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
 
The Django Book, Chapter 16: django.contrib
The Django Book, Chapter 16: django.contribThe Django Book, Chapter 16: django.contrib
The Django Book, Chapter 16: django.contrib
 
Optimizing Magento by Preloading Data
Optimizing Magento by Preloading DataOptimizing Magento by Preloading Data
Optimizing Magento by Preloading Data
 
PHP MVC
PHP MVCPHP MVC
PHP MVC
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
jQuery PPT
jQuery PPTjQuery PPT
jQuery PPT
 
Implementation of EAV pattern for ActiveRecord models
Implementation of EAV pattern for ActiveRecord modelsImplementation of EAV pattern for ActiveRecord models
Implementation of EAV pattern for ActiveRecord models
 
Meet Magento Belarus debug Pavel Novitsky (eng)
Meet Magento Belarus debug Pavel Novitsky (eng)Meet Magento Belarus debug Pavel Novitsky (eng)
Meet Magento Belarus debug Pavel Novitsky (eng)
 
ALPHA Script - XML Model
ALPHA Script - XML ModelALPHA Script - XML Model
ALPHA Script - XML Model
 
Art of Javascript
Art of JavascriptArt of Javascript
Art of Javascript
 

Similaire à Template rendering in rails

Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Railsrstankov
 
Templates, partials and layouts
Templates, partials and layoutsTemplates, partials and layouts
Templates, partials and layoutsKadiv Vech
 
RoR 101: Session 2
RoR 101: Session 2RoR 101: Session 2
RoR 101: Session 2Rory Gianni
 
A tour on ruby and friends
A tour on ruby and friendsA tour on ruby and friends
A tour on ruby and friends旻琦 潘
 
Ruby on Rails - Introduction
Ruby on Rails - IntroductionRuby on Rails - Introduction
Ruby on Rails - IntroductionVagmi Mudumbai
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to DjangoJoaquim Rocha
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overviewYehuda Katz
 
How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30fiyuer
 
Building a dashboard using AngularJS
Building a dashboard using AngularJSBuilding a dashboard using AngularJS
Building a dashboard using AngularJSRajthilakMCA
 
MVC & SQL_In_1_Hour
MVC & SQL_In_1_HourMVC & SQL_In_1_Hour
MVC & SQL_In_1_HourDilip Patel
 
Writing HTML5 Web Apps using Backbone.js and GAE
Writing HTML5 Web Apps using Backbone.js and GAEWriting HTML5 Web Apps using Backbone.js and GAE
Writing HTML5 Web Apps using Backbone.js and GAERon Reiter
 
Laravel 8 export data as excel file with example
Laravel 8 export data as excel file with exampleLaravel 8 export data as excel file with example
Laravel 8 export data as excel file with exampleKaty Slemon
 
Create an application with ember
Create an application with ember Create an application with ember
Create an application with ember Chandra Sekar
 
Getting started with Rails (2), Season 2
Getting started with Rails (2), Season 2Getting started with Rails (2), Season 2
Getting started with Rails (2), Season 2RORLAB
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the FinishYehuda Katz
 
[2015/2016] Require JS and Handlebars JS
[2015/2016] Require JS and Handlebars JS[2015/2016] Require JS and Handlebars JS
[2015/2016] Require JS and Handlebars JSIvano Malavolta
 

Similaire à Template rendering in rails (20)

Intro to Rails 4
Intro to Rails 4Intro to Rails 4
Intro to Rails 4
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Templates, partials and layouts
Templates, partials and layoutsTemplates, partials and layouts
Templates, partials and layouts
 
RoR 101: Session 2
RoR 101: Session 2RoR 101: Session 2
RoR 101: Session 2
 
A tour on ruby and friends
A tour on ruby and friendsA tour on ruby and friends
A tour on ruby and friends
 
Ruby on Rails - Introduction
Ruby on Rails - IntroductionRuby on Rails - Introduction
Ruby on Rails - Introduction
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to Django
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30
 
Building a dashboard using AngularJS
Building a dashboard using AngularJSBuilding a dashboard using AngularJS
Building a dashboard using AngularJS
 
MVC & SQL_In_1_Hour
MVC & SQL_In_1_HourMVC & SQL_In_1_Hour
MVC & SQL_In_1_Hour
 
Actionview
ActionviewActionview
Actionview
 
Writing HTML5 Web Apps using Backbone.js and GAE
Writing HTML5 Web Apps using Backbone.js and GAEWriting HTML5 Web Apps using Backbone.js and GAE
Writing HTML5 Web Apps using Backbone.js and GAE
 
Laravel 8 export data as excel file with example
Laravel 8 export data as excel file with exampleLaravel 8 export data as excel file with example
Laravel 8 export data as excel file with example
 
Create an application with ember
Create an application with ember Create an application with ember
Create an application with ember
 
Getting started with Rails (2), Season 2
Getting started with Rails (2), Season 2Getting started with Rails (2), Season 2
Getting started with Rails (2), Season 2
 
Django Vs Rails
Django Vs RailsDjango Vs Rails
Django Vs Rails
 
The Rails Way
The Rails WayThe Rails Way
The Rails Way
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
 
[2015/2016] Require JS and Handlebars JS
[2015/2016] Require JS and Handlebars JS[2015/2016] Require JS and Handlebars JS
[2015/2016] Require JS and Handlebars JS
 

Plus de Hung Wu Lo

What would your own version of Ruby look like? (RubyKaigi)
What would your own version of Ruby look like? (RubyKaigi)What would your own version of Ruby look like? (RubyKaigi)
What would your own version of Ruby look like? (RubyKaigi)Hung Wu Lo
 
What would your own version of Ruby look like?
What would your own version of Ruby look like?What would your own version of Ruby look like?
What would your own version of Ruby look like?Hung Wu Lo
 
Goby and its compiler
Goby and its compilerGoby and its compiler
Goby and its compilerHung Wu Lo
 
Method call in Ruby
Method call in RubyMethod call in Ruby
Method call in RubyHung Wu Lo
 
貢獻開源專案 (Contribute to open source project)
貢獻開源專案 (Contribute to open source project)貢獻開源專案 (Contribute to open source project)
貢獻開源專案 (Contribute to open source project)Hung Wu Lo
 

Plus de Hung Wu Lo (6)

What would your own version of Ruby look like? (RubyKaigi)
What would your own version of Ruby look like? (RubyKaigi)What would your own version of Ruby look like? (RubyKaigi)
What would your own version of Ruby look like? (RubyKaigi)
 
What would your own version of Ruby look like?
What would your own version of Ruby look like?What would your own version of Ruby look like?
What would your own version of Ruby look like?
 
Goby and its compiler
Goby and its compilerGoby and its compiler
Goby and its compiler
 
Method call in Ruby
Method call in RubyMethod call in Ruby
Method call in Ruby
 
貢獻開源專案 (Contribute to open source project)
貢獻開源專案 (Contribute to open source project)貢獻開源專案 (Contribute to open source project)
貢獻開源專案 (Contribute to open source project)
 
RubyConf
RubyConfRubyConf
RubyConf
 

Dernier

KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....kzayra69
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based projectAnoyGreter
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...Technogeeks
 
PREDICTING RIVER WATER QUALITY ppt presentation
PREDICTING  RIVER  WATER QUALITY  ppt presentationPREDICTING  RIVER  WATER QUALITY  ppt presentation
PREDICTING RIVER WATER QUALITY ppt presentationvaddepallysandeep122
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024StefanoLambiase
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Andreas Granig
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odishasmiwainfosol
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commercemanigoyal112
 
How to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfHow to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfLivetecs LLC
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaHanief Utama
 

Dernier (20)

KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
2.pdf Ejercicios de programación competitiva
2.pdf Ejercicios de programación competitiva2.pdf Ejercicios de programación competitiva
2.pdf Ejercicios de programación competitiva
 
What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based project
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...
 
PREDICTING RIVER WATER QUALITY ppt presentation
PREDICTING  RIVER  WATER QUALITY  ppt presentationPREDICTING  RIVER  WATER QUALITY  ppt presentation
PREDICTING RIVER WATER QUALITY ppt presentation
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commerce
 
How to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfHow to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdf
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief Utama
 

Template rendering in rails

  • 1. Template Rendering in Rails Created by Stan Lo ( @st0012 )
  • 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. 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. 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. The beginning of template rendering process
  • 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. 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. 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. Here's the first phase of template rendering: Create rendering context
  • 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. 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. 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. 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. 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. 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. 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. 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. The second phase: Prepare for rendering
  • 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.
  • 21.
  • 22. 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
  • 23. 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,}">]>>
  • 24. 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
  • 25. 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
  • 26. I will explain the rest of processes using TemplateRenderer#render as an example.
  • 27. 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
  • 28. 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
  • 29. 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.
  • 30. 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
  • 31. 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
  • 32. 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,}">]>>
  • 33. 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
  • 34. 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,}">]>>
  • 35. 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,}">]>>
  • 36. 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,}">]>>
  • 37. 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
  • 38. 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,}">]>
  • 39. 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,}"> ]>
  • 40. So a path set can looks like:
  • 41. 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
  • 42. 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
  • 43. Here's the last step for finding a template, I'll split it into several phases.
  • 44. 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">
  • 45. 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
  • 46. 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,}"
  • 47. 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
  • 48. If it found a matched template, it returns its absolute path. ["/path_to_your_app/app/views/layouts/application.html.erb"]
  • 49. 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
  • 50. The above is the third phase of template rendering in Rails.
  • 51. Then here comes the fourth phase: Compile Template
  • 52. 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
  • 53. 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
  • 54. 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
  • 55. 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
  • 56. 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
  • 57. 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
  • 58. 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 }
  • 59. 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"
  • 60. 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"
  • 61. 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.
  • 62. 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.
  • 63. 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
  • 64. The fifth(last) phase: Get final result
  • 65. 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
  • 66. 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.
  • 67. 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.
  • 68. Thank you for your listening
  • 69. 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 đ)