Skip to content

Commit

Permalink
Document view components support (#38656)
Browse files Browse the repository at this point in the history
  • Loading branch information
joelhawksley committed Mar 13, 2020
1 parent 99b607e commit c82a919
Show file tree
Hide file tree
Showing 7 changed files with 32 additions and 78 deletions.
8 changes: 6 additions & 2 deletions actionpack/lib/action_controller/renderer.rb
Expand Up @@ -82,8 +82,12 @@ def initialize(controller, env, defaults)
# need to call <tt>.to_json</tt> on the object you want to render.
# * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>.
#
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, the default is
# to render a partial and use the second parameter as the locals hash.
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
#
# If an object responding to `render_in` is passed, `render_in` is called on the object,
# passing in the current view context.
#
# Otherwise, a partial is rendered using the second parameter as the locals hash.
def render(*args)
raise "missing controller" unless controller

Expand Down
4 changes: 2 additions & 2 deletions actionpack/test/controller/renderer_test.rb
Expand Up @@ -70,8 +70,8 @@ def test_render_component
renderer = ApplicationController.renderer

assert_equal(
%(<span title="my title">(Inline render)</span>),
renderer.render(TestComponent.new(title: "my title")).strip
%(Hello, World!),
renderer.render(TestComponent.new)
)
end

Expand Down
36 changes: 3 additions & 33 deletions actionpack/test/lib/test_component.rb
@@ -1,41 +1,11 @@
# frozen_string_literal: true

class TestComponent < ActionView::Base
delegate :render, to: :view_context

def initialize(title:)
@title = title
end

def render_in(view_context)
self.class.compile
@view_context = view_context
rendered_template
class TestComponent
def render_in(_view_context)
"Hello, World!"
end

def format
:html
end

def self.template
<<~'erb'
<span title="<%= title %>">(<%= render(plain: "Inline render") %>)</span>
erb
end

def self.compile
@compiled ||= nil
return if @compiled

class_eval(
"def rendered_template; @output_buffer = ActionView::OutputBuffer.new; " +
ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src +
"; end"
)

@compiled = true
end

private
attr_reader :title, :view_context
end
8 changes: 6 additions & 2 deletions actionview/lib/action_view/helpers/rendering_helper.rb
Expand Up @@ -22,8 +22,12 @@ module RenderingHelper
# type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
# object.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
#
# If an object responding to `render_in` is passed, `render_in` is called on the object,
# passing in the current view context.
#
# Otherwise, a partial is rendered using the second parameter as the locals hash.
def render(options = {}, locals = {}, &block)
case options
when Hash
Expand Down
40 changes: 3 additions & 37 deletions actionview/test/lib/test_component.rb
@@ -1,41 +1,7 @@
# frozen_string_literal: true

class TestComponent < ActionView::Base
delegate :render, to: :view_context

def initialize(title:)
@title = title
end

# Entrypoint for rendering. Called by ActionView::RenderingHelper#render.
#
# Returns ActionView::OutputBuffer.
def render_in(view_context, &block)
self.class.compile
@view_context = view_context
@content = view_context.capture(&block) if block_given?
rendered_template
end

def self.template
<<~'erb'
<span title="<%= title %>"><%= content %> (<%= render(plain: "Inline render") %>)</span>
erb
class TestComponent
def render_in(_view_context)
"Hello, World!"
end

def self.compile
@compiled ||= nil
return if @compiled

class_eval(
"def rendered_template; @output_buffer = ActionView::OutputBuffer.new; " +
ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src +
"; end"
)

@compiled = true
end

private
attr_reader :content, :title, :view_context
end
4 changes: 2 additions & 2 deletions actionview/test/template/render_test.rb
Expand Up @@ -681,8 +681,8 @@ def test_render_throws_exception_when_no_extensions_passed_to_register_template_

def test_render_component
assert_equal(
%(<span title="my title">Hello, World! (Inline render)</span>),
@view.render(TestComponent.new(title: "my title")) { "Hello, World!" }.strip
%(Hello, World!),
@view.render(TestComponent.new)
)
end
end
Expand Down
10 changes: 10 additions & 0 deletions guides/source/layouts_and_rendering.md
Expand Up @@ -277,6 +277,16 @@ since an attacker could use this action to access security sensitive files in yo

TIP: `send_file` is often a faster and better option if a layout isn't required.

#### Rendering objects

Rails can render objects responding to `:render_in`.

```ruby
render MyComponent.new
```

This calls `render_in` on the provided object with the current view context.

#### Options for `render`

Calls to the `render` method generally accept six options:
Expand Down

0 comments on commit c82a919

Please sign in to comment.