ruby2html 1.6.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,93 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base_component'
4
+
3
5
  module Ruby2html
4
6
  module RailsComponents
7
+ # Delegates to Rails' +form_with+ so the form tag, CSRF token, method
8
+ # spoofing (_method), multipart detection and URL resolution match Rails
9
+ # exactly. The yielded builder is wrapped in a FormBuilderProxy so that
10
+ # calls like +f.text_field :name+ write straight into the Ruby2html output.
5
11
  class FormWith < BaseComponent
6
- include ActionView::Helpers::FormHelper
7
- def render(&block)
8
- model = @options[:model]
9
- scope = @options[:scope]
10
- url = @options[:url]
11
- method = @options[:method]
12
- local = @options[:local]
12
+ private
13
+ def block_arg(arg)
14
+ FormBuilderProxy.wrap(@render, arg)
15
+ end
16
+ end
13
17
 
14
- form_options = @options.except(:model, :scope, :url, :method, :local)
15
- form_options[:action] = determine_url(model, url)
16
- form_options[:method] = determine_method(model, method)
17
- form_options['data-remote'] = 'true' unless local
18
- @model = model
19
- @scope = determine_scope(model, scope)
18
+ # Forwards every call to the wrapped ActionView FormBuilder. Markup results
19
+ # (html_safe strings) are appended to the renderer and +nil+ is returned so
20
+ # a block's return value can't re-emit them; anything else (f.object,
21
+ # f.field_id, ...) is returned unchanged. Builder methods that clash with
22
+ # private Kernel methods (select, format, ...) still reach method_missing.
23
+ class FormBuilderProxy
24
+ include OutputCapture
20
25
 
21
- @render.form(**form_options) do
22
- authenticity_token_tag
23
- utf8_enforcer_tag
24
- block.call(self)
25
- end
26
- end
26
+ def self.wrap(render, arg)
27
+ return arg unless defined?(ActionView::Helpers::FormBuilder) && arg.is_a?(ActionView::Helpers::FormBuilder)
27
28
 
28
- def label(method, text = nil, options = {})
29
- @render.label(**options.merge(for: field_id(method))) do
30
- text || method.to_s.humanize
31
- end
29
+ new(render, arg)
32
30
  end
33
31
 
34
- def text_field(method, options = {})
35
- @render.input(**options.merge(type: 'text', name: field_name(method), id: field_id(method), value: object_value_for(method)))
32
+ def initialize(render, builder)
33
+ @render = render
34
+ @builder = builder
36
35
  end
37
36
 
38
- def hidden_field(method, options = {})
39
- @render.input(**options.merge(type: 'hidden', name: field_name(method), id: field_id(method), value: object_value_for(method)))
37
+ # The underlying ActionView::Helpers::FormBuilder.
38
+ def __builder__
39
+ @builder
40
40
  end
41
41
 
42
- def password_field(method, options = {})
43
- @render.input(**options.merge(type: 'password', name: field_name(method), id: field_id(method)))
44
- end
42
+ def method_missing(name, ...)
43
+ return super unless @builder.respond_to?(name)
45
44
 
46
- def file_field(method, options = {})
47
- @render.input(**options.merge(type: 'file', name: field_name(method), id: field_id(method)))
45
+ forward(name, ...)
48
46
  end
49
47
 
50
- def submit(value = nil, options = {})
51
- @render.input(**options.merge(type: 'submit', value: value || submit_default_value))
48
+ def respond_to_missing?(name, include_private = false)
49
+ @builder.respond_to?(name, include_private)
52
50
  end
53
51
 
54
52
  private
55
- def determine_url(model, url)
56
- return url if url
57
- return polymorphic_path(model) if model && model.respond_to?(:persisted?)
58
- nil
59
- end
53
+ def forward(name, *args, **options, &block)
54
+ result = @builder.public_send(name, *args, **options, &capturing(block))
55
+ return result unless result.is_a?(String) && result.html_safe?
60
56
 
61
- def determine_method(model, method)
62
- return method if method
63
- return 'post' unless model
64
- model.respond_to?(:persisted?) && model.persisted? ? 'patch' : 'post'
65
- end
66
-
67
- def determine_scope(model, scope)
68
- return scope if scope
69
- model.model_name.param_key if model.respond_to?(:model_name)
70
- end
71
-
72
- def authenticity_token_tag
73
- @render.input(type: 'hidden', name: 'authenticity_token', value: @context.form_authenticity_token)
74
- end
75
-
76
- def utf8_enforcer_tag
77
- @render.input(type: 'hidden', name: 'utf8', value: '✓')
78
- end
79
-
80
- def field_name(method)
81
- @scope ? "#{@scope}[#{method}]" : method.to_s
82
- end
83
-
84
- def field_id(method)
85
- @scope ? "#{@scope}_#{method}" : method.to_s
57
+ @render.plain(result)
58
+ nil
86
59
  end
87
60
 
88
- def object_value_for(method)
89
- @model&.public_send(method) if @model
61
+ # Nested builders (fields_for / fields) are wrapped as well.
62
+ def block_arg(arg)
63
+ FormBuilderProxy.wrap(@render, arg)
90
64
  end
91
65
  end
92
66
  end
93
- end if defined?(ActionView::Helpers::FormHelper)
67
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base_component'
4
+
3
5
  module Ruby2html
4
6
  module RailsComponents
7
+ # Delegates to Rails' +image_tag+; see BaseComponent.
5
8
  class ImageTag < BaseComponent
6
- def render(&block)
7
- @render.plain(@context.image_tag(*@args, **@options, &block))
8
- end
9
9
  end
10
10
  end
11
11
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base_component'
4
+
3
5
  module Ruby2html
4
6
  module RailsComponents
7
+ # Delegates to Rails' +link_to+; see BaseComponent.
5
8
  class LinkTo < BaseComponent
6
- def render(&block)
7
- @render.plain(@context.button_to(*@args, **@options, &block))
8
- end
9
9
  end
10
10
  end
11
11
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby2html
4
+ module RailsComponents
5
+ # Bridges Ruby2html blocks and Rails helpers that take a block.
6
+ #
7
+ # Rails helpers (link_to, button_to, form_with, FormBuilder#label, ...) run
8
+ # their block through ActionView's +capture+, which only sees the view's
9
+ # output buffer. Ruby2html tag methods write into the renderer's buffer
10
+ # instead, so without this bridge nested tags would leak out *before* the
11
+ # helper's markup. Including classes must set +@render+.
12
+ module OutputCapture
13
+ private
14
+ # Wraps +block+ in a proc for a Rails helper. The proc redirects the
15
+ # renderer's output into a scratch buffer while the block runs and
16
+ # returns that markup as html_safe. +block_arg+ lets subclasses swap
17
+ # yielded objects (e.g. wrap a FormBuilder) before the user sees them.
18
+ def capturing(block)
19
+ return unless block
20
+
21
+ proc { |*yielded| capture_output(*yielded.map! { |arg| block_arg(arg) }, &block) }
22
+ end
23
+
24
+ def capture_output(*args)
25
+ previous = @render.current_output
26
+ buffer = String.new(capacity: 256)
27
+ @render.current_output = buffer
28
+ value = yield(*args)
29
+ # Same rule as ActionView's capture: markup written to the buffer wins,
30
+ # otherwise the block's return value is used (Rails escapes it if unsafe).
31
+ buffer.empty? ? value : buffer.html_safe
32
+ ensure
33
+ @render.current_output = previous
34
+ end
35
+
36
+ def block_arg(arg)
37
+ arg
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Loaded by TemplateCompiler.available? after Prism (Prism::Visitor must exist).
4
+ module Ruby2html
5
+ module RailsComponents
6
+ class TemplateCompiler
7
+ # Walks the AST collecting insertions. Method/class bodies are skipped:
8
+ # the renderer local isn't visible there.
9
+ class Collector < Prism::Visitor
10
+ attr_reader :edits, :uses_helpers
11
+
12
+ def initialize(renderer_methods)
13
+ super()
14
+ @renderer_methods = renderer_methods
15
+ @edits = []
16
+ @uses_helpers = false
17
+ end
18
+
19
+ def visit_call_node(node)
20
+ rewrite(node) if node.receiver.nil?
21
+ return super unless own_renderer_block?(node)
22
+
23
+ # The block belongs to a fresh renderer (e.g. Ruby2html::Render.new
24
+ # { p 'x' }) and is instance_exec'd on it, so its receiverless calls
25
+ # must stay receiverless. Visit everything except the block body.
26
+ node.receiver&.accept(self)
27
+ node.arguments&.accept(self)
28
+ end
29
+
30
+ def visit_def_node(_node); end
31
+ def visit_class_node(_node); end
32
+ def visit_module_node(_node); end
33
+ def visit_singleton_class_node(_node); end
34
+
35
+ private
36
+ # `Render.new { ... }` / `Ruby2html::Render.new { ... }`
37
+ def own_renderer_block?(node)
38
+ return false unless node.name == :new && node.block.is_a?(Prism::BlockNode)
39
+
40
+ receiver = node.receiver
41
+ case receiver
42
+ when Prism::ConstantReadNode then receiver.name == :Render
43
+ when Prism::ConstantPathNode then receiver.name == :Render
44
+ else false
45
+ end
46
+ end
47
+
48
+ def rewrite(node)
49
+ name = node.name
50
+ return unless node.message_loc
51
+
52
+ if @renderer_methods.include?(name)
53
+ @edits << [node.message_loc.start_offset, TemplateCompiler::RENDERER_PREFIX]
54
+ elsif TemplateCompiler.helper_methods.include?(name)
55
+ @edits << [node.message_loc.start_offset, TemplateCompiler::HELPERS_PREFIX]
56
+ @uses_helpers = true
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Ruby2html
6
+ module RailsComponents
7
+ # Compile-time rewrite of a .html.rb template.
8
+ #
9
+ # Instead of instance_exec'ing the template on a Render (which forces the
10
+ # renderer to copy every view instance variable on each render and to
11
+ # route every helper call through method_missing), the template runs
12
+ # directly in the view/component. Prism finds the receiverless calls that
13
+ # belong to Ruby2html and gives them an explicit receiver:
14
+ #
15
+ # div class: 'x' do => __r2h_renderer.div class: 'x' do
16
+ # link_to 'Home', '/' => __r2h_helpers.link_to 'Home', '/'
17
+ # render partial: 'nav' => __r2h_helpers.render partial: 'nav'
18
+ #
19
+ # Only text is inserted, never newlines, so line numbers are unchanged.
20
+ class TemplateCompiler
21
+ RENDERER = '__r2h_renderer'
22
+ HELPERS = '__r2h_helpers'
23
+ RENDERER_PREFIX = "#{RENDERER}."
24
+ HELPERS_PREFIX = "#{HELPERS}."
25
+
26
+ def self.helper_methods
27
+ @helper_methods ||= TemplateHelpers.public_instance_methods(false).to_set.freeze
28
+ end
29
+
30
+ Result = Struct.new(:source, :uses_helpers)
31
+
32
+ class << self
33
+ # Prism is only loaded when the first template is compiled. Without it
34
+ # (Ruby < 3.3 and no prism gem) TemplateHandler falls back to the
35
+ # closure strategy (template block instance_exec'd on the renderer).
36
+ def available?
37
+ return @available if defined?(@available)
38
+
39
+ @available = begin
40
+ require 'prism'
41
+ require_relative 'template_collector'
42
+ true
43
+ rescue LoadError
44
+ false
45
+ end
46
+ end
47
+
48
+ # Returns a Result, or nil when the source can't be parsed (the caller
49
+ # then compiles it unchanged so Ruby reports the syntax error).
50
+ # +locals+: the template's local names (partial locals, `locals:`).
51
+ # Prism must know them, or `h2 title` would parse `title` as a call
52
+ # and be rewritten into a <title> tag.
53
+ def compile(source, locals = [])
54
+ return unless available?
55
+
56
+ parsed = locals.empty? ? Prism.parse(source) : Prism.parse(source, scopes: [locals.map(&:to_sym)])
57
+ return unless parsed.success?
58
+
59
+ collector = Collector.new(renderer_methods)
60
+ parsed.value.accept(collector)
61
+ Result.new(apply(source, collector.edits), collector.uses_helpers)
62
+ end
63
+
64
+ # Ruby2html methods a template may call without a receiver: every
65
+ # tag method plus plain/component. Resolved lazily (and memoized) so
66
+ # it reflects Render after it has been fully loaded.
67
+ def renderer_methods
68
+ @renderer_methods ||= begin
69
+ tags = Ruby2html::Render::HTML5_TAGS.map { |tag| tag.tr('-', '_').to_sym }
70
+ (tags + %i[plain component]).select { |name| Ruby2html::Render.public_method_defined?(name) }.to_set.freeze
71
+ end
72
+ end
73
+
74
+ private
75
+ # Edits are [byte_offset, text] insertions; Prism offsets are bytes.
76
+ def apply(source, edits)
77
+ return source if edits.empty?
78
+
79
+ bytes = source.b
80
+ out = String.new(capacity: bytes.bytesize + (edits.size * 16), encoding: Encoding::BINARY)
81
+ cursor = 0
82
+ edits.sort_by!.with_index { |(offset, _), index| [offset, index] }.each do |offset, text|
83
+ out << bytes.byteslice(cursor, offset - cursor) << text
84
+ cursor = offset
85
+ end
86
+ out << bytes.byteslice(cursor, bytes.bytesize - cursor)
87
+ out.force_encoding(source.encoding)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'output_capture'
4
+ require_relative 'base_component'
5
+ require_relative 'link_to'
6
+ require_relative 'button_to'
7
+ require_relative 'image_tag'
8
+ require_relative 'form_with'
9
+
10
+ module Ruby2html
11
+ module RailsComponents
12
+ # Receiver for the Rails helper calls of a compiled .html.rb template
13
+ # (see TemplateCompiler). Routes each helper through its RailsComponents
14
+ # wrapper so the helper's markup lands in the Ruby2html buffer.
15
+ class TemplateHelpers
16
+ include OutputCapture
17
+
18
+ COMPONENTS = {
19
+ link_to: LinkTo,
20
+ button_to: ButtonTo,
21
+ image_tag: ImageTag,
22
+ form_with: FormWith
23
+ }.freeze
24
+
25
+ def initialize(render, context)
26
+ @render = render
27
+ @context = context
28
+ end
29
+
30
+ COMPONENTS.each do |name, component|
31
+ define_method(name) do |*args, **options, &block|
32
+ component.new(@render, @context, name, *args, **options).render(&block)
33
+ end
34
+ end
35
+
36
+ # Ruby2html::Component instances write straight into the template's
37
+ # buffer; anything else (partials, ViewComponents, ...) goes through the
38
+ # view's render and is appended. A block is captured from the Ruby2html
39
+ # buffer so its tags end up inside the rendered output.
40
+ def render(*args, **options, &block)
41
+ first = args.first
42
+ if args.size == 1 && options.empty? && first.is_a?(Ruby2html::Component)
43
+ first.render_into(@render.current_output, @context, block)
44
+ else
45
+ @render.plain(@context.render(*args, **options, &capturing(block)))
46
+ end
47
+ nil
48
+ end
49
+
50
+ # ActionView's capture only sees the view's output buffer, so a block of
51
+ # Ruby2html tags would be written to the page and capture nothing.
52
+ # Redirect the renderer's buffer instead (same return rule as Rails).
53
+ def capture(*args, &block)
54
+ return @context.capture(*args) unless block
55
+
56
+ value = capture_output(*args, &block)
57
+ value.is_a?(String) ? ERB::Util.html_escape(value) : nil
58
+ end
59
+
60
+ def content_for(name, content = nil, **options, &block)
61
+ content = capture(&block) if block
62
+ @context.content_for(name, content, **options)
63
+ end
64
+
65
+ def provide(name, content = nil, &block)
66
+ content = capture(&block) if block
67
+ @context.provide(name, content)
68
+ end
69
+
70
+ # Kept for templates written against the old instance_exec evaluation,
71
+ # where Render's private escape_html was callable. Returns a plain String.
72
+ def escape_html(text)
73
+ ERB::Util.unwrapped_html_escape(text.to_s)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruby2html
4
+ # Include in a controller to get +html(self) { ... }+ inside ERB/other views.
4
5
  module RailsHelper
5
- def html(context, &block)
6
- Ruby2html::Render.new(context, &block).render.html_safe
6
+ def self.included(base)
7
+ # Only controllers can expose helpers to views; plain modules just get +html+.
8
+ base.helper_method :html if base.respond_to?(:helper_method)
7
9
  end
8
10
 
9
- def self.included(base)
10
- base.helper_method :html
11
+ def html(context, &block)
12
+ output = Ruby2html::Render.new(context, &block).render
13
+ # Render#render already returns a SafeBuffer under Rails; avoid copying it.
14
+ output.html_safe? ? output : output.html_safe
11
15
  end
12
16
  end
13
17
  end
@@ -1,40 +1,119 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if defined?(Rails)
4
- module Ruby2html
5
- class TemplateHandler
6
- class_attribute :default_format
7
- self.default_format = :html
8
-
9
- def self.call(template, source)
10
- <<-RUBY
11
- begin
12
- previous_renderer = Thread.current[:__ruby2html_renderer__]
13
- renderer = Ruby2html::Render.new(self) do
14
- #{source}
15
- end
16
- Thread.current[:__ruby2html_renderer__] = renderer
17
- renderer.__render_from_rails(#{template.identifier.inspect})
18
- ensure
19
- Thread.current[:__ruby2html_renderer__] = previous_renderer
20
- end
21
- RUBY
3
+ # The template handler and Railtie only make sense with ActiveSupport/ActionView.
4
+ return unless defined?(ActiveSupport)
5
+
6
+ require 'active_support/core_ext/class/attribute'
7
+ require_relative '../ruby2html' unless defined?(Ruby2html::RailsComponents) # autoloads
8
+
9
+ module Ruby2html
10
+ # ActionView template handler for +*.html.rb+ views, partials and
11
+ # ViewComponent sidecar templates.
12
+ #
13
+ # Templates are compiled once by ActionView; all decisions that don't depend
14
+ # on the request (rewriting, annotations, whether helpers are needed) are
15
+ # made here so the compiled method does as little as possible per render.
16
+ #
17
+ # Two strategies:
18
+ # * compiled (default): TemplateCompiler rewrites the source so it runs in
19
+ # the view itself. No instance variable copying, no instance_exec, helper
20
+ # calls go straight to the view.
21
+ # * closure (fallback when Prism is unavailable or the source doesn't
22
+ # parse): the source becomes a block instance_exec'd on a Render that
23
+ # copies the view's instance variables, as in 1.x.
24
+ class TemplateHandler
25
+ # Render#render runs its root block; compiled templates have already
26
+ # written their output, so they pass this shared no-op.
27
+ NOOP = proc { }
28
+
29
+ # Every generated prologue ends on the template's first line so that
30
+ # backtrace line numbers point at the right line of the .html.rb file.
31
+ # Locals are prefixed because the template code shares their scope.
32
+ #
33
+ # The renderer is held in a local and published on the view/component as
34
+ # +@__r2h+ (the slot ComponentHelper#method_missing reads), restoring the
35
+ # previous value afterwards so nested partials/components on the same
36
+ # object stay correct. No thread/fiber-global state is involved.
37
+ SAVE_PREVIOUS = '__r2h_prev = @__r2h; begin; '
38
+ COMPILED_PROLOGUE = "#{SAVE_PREVIOUS}__r2h_renderer = Ruby2html::Render.new(nil, &Ruby2html::TemplateHandler::NOOP); "
39
+ HELPERS_PROLOGUE = '__r2h_helpers = Ruby2html::RailsComponents::TemplateHelpers.new(__r2h_renderer, self); '
40
+ PUSH_RENDERER = '@__r2h = __r2h_renderer'
41
+ CLOSURE_PROLOGUE = "#{SAVE_PREVIOUS}__r2h_renderer = Ruby2html::Render.new(self) do; "
42
+ EPILOGUE = "\nensure\n@__r2h = __r2h_prev\nend"
43
+
44
+ class_attribute :default_format
45
+ self.default_format = :html
46
+
47
+ class << self
48
+ def call(template, source)
49
+ compiled = RailsComponents::TemplateCompiler.compile(source, template_locals(template))
50
+ compiled ? compiled_code(template, compiled) : closure_code(template, source)
22
51
  end
23
52
 
24
- def self.handles_encoding?
25
- true
53
+ # This handler does no encoding work of its own, so let ActionView
54
+ # transcode the source to Encoding.default_external (honouring an
55
+ # `# encoding:` magic comment) before it reaches #call.
56
+ def handles_encoding?
57
+ false
26
58
  end
59
+
60
+ private
61
+ def template_locals(template)
62
+ template.respond_to?(:locals) ? Array(template.locals) : []
63
+ end
64
+
65
+ def compiled_code(template, compiled)
66
+ helpers = compiled.uses_helpers ? HELPERS_PROLOGUE : ''
67
+ "#{COMPILED_PROLOGUE}#{helpers}#{PUSH_RENDERER}; #{compiled.source}\n" \
68
+ "#{render_expression(template)}#{EPILOGUE}"
69
+ end
70
+
71
+ def closure_code(template, source)
72
+ "#{CLOSURE_PROLOGUE}#{source}\nend\n#{PUSH_RENDERER}\n#{render_expression(template)}#{EPILOGUE}"
73
+ end
74
+
75
+ # Mirrors the ERB handler: wrap HTML templates in BEGIN/END comments
76
+ # when config.action_view.annotate_rendered_view_with_filenames is on.
77
+ def render_expression(template)
78
+ return '__r2h_renderer.render' unless annotate?(template)
79
+
80
+ path = template.short_identifier
81
+ "ActiveSupport::SafeBuffer.new(#{"<!-- BEGIN #{path} -->".inspect})" \
82
+ '.safe_concat(__r2h_renderer.render)' \
83
+ ".safe_concat(#{"<!-- END #{path} -->".inspect})"
84
+ end
85
+
86
+ def annotate?(template)
87
+ defined?(ActionView::Base) &&
88
+ ActionView::Base.annotate_rendered_view_with_filenames &&
89
+ template.respond_to?(:format) && template.format == :html &&
90
+ template.respond_to?(:short_identifier)
91
+ end
27
92
  end
93
+ end
28
94
 
95
+ if defined?(Rails::Railtie)
29
96
  class Railtie < Rails::Railtie
97
+ # Lazy by default (dev, tests, plain `require`). When the app eager-loads
98
+ # (production), load everything up front instead: no first-request
99
+ # latency, and forking servers share these pages copy-on-write.
100
+ config.before_eager_load do
101
+ Ruby2html::RailsComponents::TemplateCompiler.available? # loads Prism
102
+ Ruby2html::RailsComponents.constants.each { |name| Ruby2html::RailsComponents.const_get(name) }
103
+ Ruby2html::Component
104
+ Ruby2html::ComponentHelper
105
+ end
106
+
30
107
  initializer 'ruby2html.initializer' do
31
- Rails.autoloaders.main.ignore(
32
- Rails.root.join('app/views/**/*.html.rb'),
33
- Rails.root.join('app/components/**/*.html.rb')
34
- )
108
+ # *.html.rb files are templates, not constants: keep Zeitwerk from
109
+ # trying to autoload/eager-load them anywhere under app/.
110
+ Rails.autoloaders.main.ignore(Rails.root.join('app/**/*.html.rb'))
35
111
  end
36
112
  end
37
113
  end
114
+ end
38
115
 
116
+ # Register lazily so requiring the gem doesn't force ActionView to load.
117
+ ActiveSupport.on_load(:action_view) do
39
118
  ActionView::Template.register_template_handler :rb, Ruby2html::TemplateHandler
40
119
  end