Skip to content

Commit

Permalink
Update hanami-view extension to work with the 2.1 beta
Browse files Browse the repository at this point in the history
`Hanami::View::Context` no longer supports `#with`, as it's encouraged
for a single instance to be used for the whole request [1].

We use the same approach as hanami [2], where users can configure a
default context class and it's expected to be initialized with keyword
arguments. In our case, the default context class is a noop and we
expect users to implement it. It can be configured in the
`view_context_class` setting, while the initialize options can be
created from the connection struct through a lambda configured in the
`view_context_options` setting.

[1] - hanami/view#223
[2] - hanami/hanami#1359
  • Loading branch information
waiting-for-dev committed Oct 25, 2023
1 parent f910ed7 commit ffecd9e
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ gemspec

group :development do
# TODO: Move to gemspec when hanami-view 2.0 is available
gem 'hanami-view', github: 'hanami/view', tag: 'v2.0.0.alpha2'
gem 'hanami-view', github: 'hanami/view', tag: 'v2.1.0.beta2'
end
33 changes: 10 additions & 23 deletions docs/extensions/hanami_view.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Hanami View

This extension currently works with `hanami-view` v2.0.0.alpha2, which is not
This extension currently works with `hanami-view` v2.1.0.beta, which is not
still released but available on the gem repository.

This extension integrates with [hanami-view](https://github.com/hanami/view)
Expand All @@ -20,7 +20,6 @@ WebPipe.load_extensions(:hanami_view)
class SayHelloView < Hanami::View
config.paths = [File.join(__dir__, '..', 'templates')]
config.template = 'say_hello'
config.default_context = MyContext

expose :name
end
Expand Down Expand Up @@ -62,34 +61,22 @@ class MyApp
end
```

As in a standard call to `Hanami::View#call`, you can override the context
(`Hanami::View::Context`) to use through the `context:` option. However, it is still possible to leverage the configured default context while injecting specific data to it.

To work, you have to specify required dependencies (in this case,
request specific data) to your hanami-view's context. A very convenient way to do that is with [`dry-auto_inject`](https://dry-rb.org/gems/dry-auto_inject):
You can configure the view context class to use through the `:view_context_class` configuration option. The only requirement for it is to implement an initialize method accepting keyword arguments:

```ruby
require 'hanami/view/context'
require 'hanami/view'
require 'my_import'

class MyContext < Hanami::View::Context
include MyImport::Import[:current_path]

# Without `dry-auto_inject` you have to manually specify dependencies and
# override the initializer:
#
# attr_reader :current_path
#
# def initialize(current_path:, **options)
# @current_path = current_path
# super
# end
def initialize(current_path:)
@current_path = current_path
end
end
```

Then, you have to configure a `:view_context` setting, which must be a lambda
accepting a `WebPipe::Conn` instance and returning a hash matching required
dependencies:
Then, you also need to configure a `:view_context_options` setting, which must be a lambda
accepting a `WebPipe::Conn` instance and returning a hash matching required arguments for
the view context class:

```ruby
require 'web_pipe'
Expand All @@ -101,11 +88,11 @@ class MyApp
include WebPipe

plug :config, WebPipe::Plugs::Config.(
view_context_class: MyContext,
view_context: ->(conn) { { current_path: conn.full_path} }
)
plug(:render) do |conn|
conn.view(SayHelloView.new, name: 'Joe')
# `:current_path` will be provided to the context
end
end
```
46 changes: 24 additions & 22 deletions lib/web_pipe/extensions/hanami_view/hanami_view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,43 @@

require 'web_pipe/types'
require 'web_pipe/conn'
require 'web_pipe'
require 'web_pipe/extensions/hanami_view/hanami_view/context'
require 'hanami/view'

# :nodoc:
module WebPipe
# See the docs for the extension linked from the README.
module DryView
# Where to find in {#config} request's view context generator.
VIEW_CONTEXT_KEY = :view_context
# See the docs for the extension linked in the README.
module HanamiView
VIEW_CONTEXT_CLASS_KEY = :view_context_class
private_constant :VIEW_CONTEXT_CLASS_KEY

# Default request's view context
DEFAULT_VIEW_CONTEXT = ->(_conn) { Types::EMPTY_HASH }
DEFAULT_VIEW_CONTEXT_CLASS = Class.new(WebPipe::HanamiView::Context)
private_constant :DEFAULT_VIEW_CONTEXT_CLASS

VIEW_CONTEXT_OPTIONS_KEY = :view_context_options
private_constant :VIEW_CONTEXT_OPTIONS_KEY

DEFAULT_VIEW_CONTEXT_OPTIONS = ->(_conn) { {} }

# Sets string output of a view as response body.
#
# If the view is not a {Hanami::View} instance, it is resolved from
# the configured container.
#
# `kwargs` is used as the input for the view (the arguments that
# {Hanami::View#call} receives). If they doesn't contain an explicit
# `context:` key, it can be added through the injection of the
# result of a lambda present in context's `:view_context`.(see
# {Hanami::View::Context#with}).
# {Hanami::View#call} receives).
#
# @param view_spec [Hanami::View, Any]
# @param kwargs [Hash] Arguments to pass along to `Hanami::View#call`
#
# @return WebPipe::Conn
def view(view_spec, **kwargs)
view_instance = view_instance(view_spec)
view_input = view_input(kwargs, view_instance)

set_response_body(
view_instance.call(
**view_input
**view_input(kwargs)
).to_str
)
end
Expand All @@ -48,20 +51,19 @@ def view_instance(view_spec)
fetch_config(:container)[view_spec]
end

def view_input(kwargs, view_instance)
def view_input(kwargs)
return kwargs if kwargs.key?(:context)

context = view_instance
.config
.default_context
.with(
**fetch_config(
VIEW_CONTEXT_KEY, DEFAULT_VIEW_CONTEXT
).call(self)
)
context = fetch_config(
VIEW_CONTEXT_CLASS_KEY, DEFAULT_VIEW_CONTEXT_CLASS
).new(
**fetch_config(
VIEW_CONTEXT_OPTIONS_KEY, DEFAULT_VIEW_CONTEXT_OPTIONS
).call(self)
)
kwargs.merge(context: context)
end
end

Conn.include(DryView)
Conn.include(HanamiView)
end
14 changes: 14 additions & 0 deletions lib/web_pipe/extensions/hanami_view/hanami_view/context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require 'hanami/view'

module WebPipe
module HanamiView
# Noop context class for Hanami::View used by default.
class Context < Hanami::View::Context
def initialize(**_kwargs)
super()
end
end
end
end
24 changes: 12 additions & 12 deletions spec/extensions/hanami_view/hanami_view_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,28 +55,29 @@
expect(new_conn.response_body).to eq(['Hello world'])
end

it 'injects configured view_context to the context' do
it 'initializes view_context class with the generated view_context_options hash' do
view = Class.new(view_class) do
config.template = 'template_with_input'
config.default_context = Class.new(Hanami::View::Context) do
attr_reader :name

def initialize(name: nil, **options)
@name = name
super
end
end.new
end
context_class = Class.new(Hanami::View::Context) do
attr_reader :name

def initialize(name:)
super
@name = name
end
end
conn = build_conn(default_env)
.add(:name, 'Joe')
.add_config(:view_context, ->(c) { { name: c.fetch(:name) } })
.add_config(:view_context_class, context_class)
.add_config(:view_context_options, ->(c) { { name: c.fetch(:name) } })

new_conn = conn.view(view.new)

expect(new_conn.response_body).to eq(['Hello Joe'])
end

it 'does not inject configured view_context when it is explicit' do
it 'respects context given at render time if given' do
view = Class.new(view_class) do
config.template = 'template_with_input'
end
Expand All @@ -86,7 +87,6 @@ def name
end
end.new
conn = build_conn(default_env)
.add_config(:view_context, ->(_conn) { { name: 'Joe' } })

new_conn = conn.view(view.new, context: context)

Expand Down

0 comments on commit ffecd9e

Please sign in to comment.