hanami 2.0.0.alpha6 → 2.0.0.alpha8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +79 -0
- data/lib/hanami/application/action/slice_configured_action.rb +103 -0
- data/lib/hanami/application/action.rb +72 -0
- data/lib/hanami/application/routing/router.rb +36 -0
- data/lib/hanami/application/slice_registrar.rb +106 -0
- data/lib/hanami/application/view/context.rb +95 -0
- data/lib/hanami/application/view/slice_configured_context.rb +71 -0
- data/lib/hanami/application/view/slice_configured_view.rb +101 -0
- data/lib/hanami/application/view.rb +24 -0
- data/lib/hanami/application/view_name_inferrer.rb +63 -0
- data/lib/hanami/application.rb +64 -147
- data/lib/hanami/configuration/actions/content_security_policy.rb +118 -0
- data/lib/hanami/configuration/actions/cookies.rb +29 -0
- data/lib/hanami/configuration/actions/sessions.rb +46 -0
- data/lib/hanami/configuration/actions.rb +95 -0
- data/lib/hanami/configuration/logger.rb +11 -8
- data/lib/hanami/configuration/views.rb +81 -0
- data/lib/hanami/configuration.rb +28 -62
- data/lib/hanami/constants.rb +29 -0
- data/lib/hanami/errors.rb +15 -0
- data/lib/hanami/slice.rb +157 -99
- data/lib/hanami/slice_configurable.rb +75 -0
- data/lib/hanami/slice_name.rb +111 -0
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami.rb +13 -5
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36c07c10545a327f0eab55e1a23e3a6f1b258dd7c4db890d16155d98a132bb90
|
4
|
+
data.tar.gz: 02a8dbd6f028e198dead1d972aef4a78b04246b3201e2c90f94e2827f4582906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 938be86da6f4c1d5e797db8f0cc185feb2ac070c4d8ba10f48b6bbe922f8b1fd8b618c9871306a7f3d2d75c34a8fab857949d7f18dd04bb833be092fbb8ced27
|
7
|
+
data.tar.gz: 459f220765e02c024fe4b1a5e17ec7a13bba10e2cb3f1b64dfa059cd351bea4cffcd47be1ca5a1cd8b2c89757f20dbcaaa7e77fbfdacb05c9fc87345974e7ec0
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,85 @@
|
|
1
1
|
# Hanami
|
2
2
|
The web, with simplicity.
|
3
3
|
|
4
|
+
## v2.0.0.alpha8 - 2020-05-19
|
5
|
+
|
6
|
+
## Added
|
7
|
+
- [Tim Riley] Introduced `Hanami::Application::Action` as base class for actions that integrate with Hanami applications. Base action classes in Hanami applications should now inherit from this.
|
8
|
+
- [Tim Riley] Introduced `Hanami::Application::View` and `Hanami::Application::View::Context` as base classes for views and view contexts that integrate with Hanami applications. Base view classes in Hanami applications should now inherit from these.
|
9
|
+
- [Tim Riley] Introduced `Hanami::Application.application_name`, which returns an `Hanami::SliceName` instance, with methods for representing the application name in various formats.
|
10
|
+
|
11
|
+
## Fixed
|
12
|
+
- [Andrew Croome] When a request is halted, do not attempt to automatically render any view paired with an `Hanami::Application::Action`
|
13
|
+
|
14
|
+
## Changed
|
15
|
+
- [Tim Riley] `Hanami::Application.namespace_name`, `.namespace_path` have been removed. These can now be accessed from the `.application_name`.
|
16
|
+
- [Tim Riley] `Hanami::Slice.slice_name` now returns an `Hanami::SliceName` instance instead of a Symbol
|
17
|
+
- [Tim Riley] `Hanami::Slice.namespace_path` has been removed. This can now be accessed from the `.slice_name`.
|
18
|
+
|
19
|
+
## v2.0.0.alpha7.1 - 2020-03-09
|
20
|
+
|
21
|
+
## Fixed
|
22
|
+
- [Tim Riley] Fixed error creating slice classes when the enclosing module did not already exist
|
23
|
+
|
24
|
+
## v2.0.0.alpha7 - 2020-03-08
|
25
|
+
|
26
|
+
## Added
|
27
|
+
- [Tim Riley] Introduced `Hanami::ApplicationLoadError` and `Hanami::SliceLoadError` exceptions to represent errors encountered during application and slice loading.
|
28
|
+
- [Tim Riley] `Hanami::Slice.shutdown` can be used to stop all the providers in a slice
|
29
|
+
|
30
|
+
## Changed
|
31
|
+
- [Tim Riley] Slices are now represented as concrete classes (such as `Main::Slice`) inheriting from `Hanami::Slice`, as opposed to _instances_ of `Hanami::Slice`. You may create your own definitions for these slices in `config/slices/[slice_name].rb`, which you can then use for customising per-slice config and behavior, e.g.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# config/slices/main.rb:
|
35
|
+
|
36
|
+
module Main
|
37
|
+
class Slice < Hanami::Slice
|
38
|
+
# slice config here
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
- [Tim Riley] Application-level `config.slice(slice_name, &block)` setting has been removed in favour of slice configuration within concrete slice class definitions
|
43
|
+
- [Tim Riley] You can configure your slice imports inside your slice classes, e.g.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
# config/slices/main.rb:
|
47
|
+
|
48
|
+
module Main
|
49
|
+
class Slice < Hanami::Slice
|
50
|
+
# Import all exported components from "search" slice
|
51
|
+
import from: :search
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
- [Tim Riley] You can configure your slice exports inside your slice classes, e.g.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# config/slices/search.rb:
|
59
|
+
|
60
|
+
module Search
|
61
|
+
class Slice < Hanami::Slice
|
62
|
+
# Export the "index_entity" component only
|
63
|
+
export ["index_entity"]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
- [Tim Riley] For advanced cases, you can configure your slice's container via a `prepare_container` block:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# config/slices/search.rb:
|
71
|
+
|
72
|
+
module Search
|
73
|
+
class Slice < Hanami::Slice
|
74
|
+
prepare_container do |container|
|
75
|
+
# `container` object is available here, with
|
76
|
+
# slice-specific configuration already applied
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
- [Tim Riley] `Hanami::Application.shutdown` will now also shutdown all registered slices
|
82
|
+
|
4
83
|
## v2.0.0.alpha6 - 2022-02-10
|
5
84
|
### Added
|
6
85
|
- [Luca Guidi] Official support for Ruby: MRI 3.1
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/action"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class Application
|
7
|
+
class Action < Hanami::Action
|
8
|
+
# Provides slice-specific configuration and behavior for any action class defined
|
9
|
+
# within a slice's module namespace.
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
# @since 2.0.0
|
13
|
+
class SliceConfiguredAction < Module
|
14
|
+
attr_reader :slice
|
15
|
+
|
16
|
+
def initialize(slice)
|
17
|
+
super()
|
18
|
+
@slice = slice
|
19
|
+
end
|
20
|
+
|
21
|
+
def extended(action_class)
|
22
|
+
configure_action(action_class)
|
23
|
+
extend_behavior(action_class)
|
24
|
+
define_new
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"#<#{self.class.name}[#{slice.name}]>"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @see Hanami::Application::Action#initialize
|
34
|
+
def define_new
|
35
|
+
resolve_view = method(:resolve_paired_view)
|
36
|
+
resolve_view_context = method(:resolve_view_context)
|
37
|
+
resolve_routes = method(:resolve_routes)
|
38
|
+
|
39
|
+
define_method(:new) do |**kwargs|
|
40
|
+
super(
|
41
|
+
view: kwargs.fetch(:view) { resolve_view.(self) },
|
42
|
+
view_context: kwargs.fetch(:view_context) { resolve_view_context.(self) },
|
43
|
+
routes: kwargs.fetch(:routes) { resolve_routes.() },
|
44
|
+
**kwargs,
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def configure_action(action_class)
|
50
|
+
action_class.config.settings.each do |setting|
|
51
|
+
action_class.config.public_send :"#{setting}=", actions_config.public_send(:"#{setting}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def extend_behavior(action_class)
|
56
|
+
if actions_config.sessions.enabled?
|
57
|
+
require "hanami/action/session"
|
58
|
+
action_class.include Hanami::Action::Session
|
59
|
+
end
|
60
|
+
|
61
|
+
if actions_config.csrf_protection
|
62
|
+
require "hanami/action/csrf_protection"
|
63
|
+
action_class.include Hanami::Action::CSRFProtection
|
64
|
+
end
|
65
|
+
|
66
|
+
if actions_config.cookies.enabled?
|
67
|
+
require "hanami/action/cookies"
|
68
|
+
action_class.include Hanami::Action::Cookies
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def resolve_paired_view(action_class)
|
73
|
+
view_identifiers = actions_config.view_name_inferrer.call(
|
74
|
+
action_class_name: action_class.name,
|
75
|
+
slice: slice,
|
76
|
+
)
|
77
|
+
|
78
|
+
view_identifiers.detect do |identifier|
|
79
|
+
break slice[identifier] if slice.key?(identifier)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def resolve_view_context(_action_class)
|
84
|
+
identifier = actions_config.view_context_identifier
|
85
|
+
|
86
|
+
if slice.key?(identifier)
|
87
|
+
slice[identifier]
|
88
|
+
elsif slice.application.key?(identifier)
|
89
|
+
slice.application[identifier]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def resolve_routes
|
94
|
+
slice.application[:routes_helper] if slice.application.key?(:routes_helper)
|
95
|
+
end
|
96
|
+
|
97
|
+
def actions_config
|
98
|
+
slice.application.config.actions
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/action"
|
4
|
+
require "hanami/slice_configurable"
|
5
|
+
require_relative "action/slice_configured_action"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
class Application
|
9
|
+
# Superclass for actions intended for use within an Hanami application.
|
10
|
+
#
|
11
|
+
# @see Hanami::Action
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
# @since 2.0.0
|
15
|
+
class Action < Hanami::Action
|
16
|
+
extend Hanami::SliceConfigurable
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# @api private
|
20
|
+
def configure_for_slice(slice)
|
21
|
+
extend SliceConfiguredAction.new(slice)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :view, :view_context, :routes
|
26
|
+
|
27
|
+
# @see SliceConfiguredAction#define_new
|
28
|
+
# @api public
|
29
|
+
def initialize(view: nil, view_context: nil, routes: nil, **kwargs)
|
30
|
+
@view = view
|
31
|
+
@view_context = view_context
|
32
|
+
@routes = routes
|
33
|
+
|
34
|
+
super(**kwargs)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def build_response(**options)
|
40
|
+
options = options.merge(view_options: method(:view_options))
|
41
|
+
super(**options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def view_options(req, res)
|
45
|
+
{context: view_context&.with(**view_context_options(req, res))}.compact
|
46
|
+
end
|
47
|
+
|
48
|
+
def view_context_options(req, res)
|
49
|
+
{request: req, response: res}
|
50
|
+
end
|
51
|
+
|
52
|
+
def finish(req, res, halted)
|
53
|
+
res.render(view, **req.params) if !halted && auto_render?(res)
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns true if a view should automatically be rendered onto the response body.
|
58
|
+
#
|
59
|
+
# This may be overridden to enable/disable automatic rendering.
|
60
|
+
#
|
61
|
+
# @param res [Hanami::Action::Response]
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
#
|
65
|
+
# @since 2.0.0
|
66
|
+
# @api public
|
67
|
+
def auto_render?(res)
|
68
|
+
view && res.body.empty?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
|
3
|
+
# require "hanami/application/router"
|
4
|
+
|
5
|
+
# Hanami.application.register_provider :router do
|
6
|
+
# start do
|
7
|
+
# configuration = Hanami.application.configuration
|
8
|
+
|
9
|
+
# routes = begin
|
10
|
+
# require File.join(configuration.root, configuration.router.routes_path)
|
11
|
+
# routes_class = Hanami.application.send(:autodiscover_application_constant, configuration.router.routes_class_name) # WIP private
|
12
|
+
# routes_class.routes
|
13
|
+
# rescue LoadError
|
14
|
+
# proc {}
|
15
|
+
# end
|
16
|
+
|
17
|
+
# resolver = configuration.router.resolver.new(
|
18
|
+
# slices: Hanami.application.slices,
|
19
|
+
# inflector: Hanami.application.inflector # TODO: use container[:inflector]?
|
20
|
+
# )
|
21
|
+
|
22
|
+
# router = Hanami::Application::Router.new(
|
23
|
+
# routes: routes,
|
24
|
+
# resolver: resolver,
|
25
|
+
# **configuration.router.options,
|
26
|
+
# ) do
|
27
|
+
# use Hanami.application[:rack_monitor]
|
28
|
+
|
29
|
+
# Hanami.application.config.for_each_middleware do |m, *args, &block|
|
30
|
+
# use(m, *args, &block)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
|
34
|
+
# register :router, router
|
35
|
+
# end
|
36
|
+
# end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../constants"
|
4
|
+
require_relative "../slice"
|
5
|
+
|
6
|
+
module Hanami
|
7
|
+
class Application
|
8
|
+
# @api private
|
9
|
+
class SliceRegistrar
|
10
|
+
attr_reader :application, :slices
|
11
|
+
private :application, :slices
|
12
|
+
|
13
|
+
def initialize(application)
|
14
|
+
@application = application
|
15
|
+
@slices = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def register(name, slice_class = nil, &block)
|
19
|
+
if slices.key?(name.to_sym)
|
20
|
+
raise SliceLoadError, "Slice '#{name}' is already registered"
|
21
|
+
end
|
22
|
+
|
23
|
+
# TODO: raise error unless name meets format (i.e. single level depth only)
|
24
|
+
|
25
|
+
slices[name.to_sym] = slice_class || build_slice(name, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](name)
|
29
|
+
slices.fetch(name) do
|
30
|
+
raise SliceLoadError, "Slice '#{name}' not found"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def freeze
|
35
|
+
slices.freeze
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_slices
|
40
|
+
slice_configs = Dir[root.join(CONFIG_DIR, "slices", "*#{RB_EXT}")]
|
41
|
+
.map { |file| File.basename(file, RB_EXT) }
|
42
|
+
|
43
|
+
slice_dirs = Dir[File.join(root, SLICES_DIR, "*")]
|
44
|
+
.select { |path| File.directory?(path) }
|
45
|
+
.map { |path| File.basename(path) }
|
46
|
+
|
47
|
+
(slice_dirs + slice_configs).uniq.sort.each do |slice_name|
|
48
|
+
load_slice(slice_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def each(&block)
|
55
|
+
slices.each_value(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_a
|
59
|
+
slices.values
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Attempts to load a slice class defined in `config/slices/[slice_name].rb`, then
|
65
|
+
# registers the slice with the matching class, if found.
|
66
|
+
def load_slice(slice_name)
|
67
|
+
slice_const_name = inflector.camelize(slice_name)
|
68
|
+
slice_require_path = root.join("config", "slices", slice_name).to_s
|
69
|
+
|
70
|
+
begin
|
71
|
+
require(slice_require_path)
|
72
|
+
rescue LoadError => e
|
73
|
+
raise e unless e.path == slice_require_path
|
74
|
+
end
|
75
|
+
|
76
|
+
slice_class =
|
77
|
+
begin
|
78
|
+
inflector.constantize("#{slice_const_name}::Slice")
|
79
|
+
rescue NameError # rubocop:disable Lint/SuppressedException
|
80
|
+
end
|
81
|
+
|
82
|
+
register(slice_name, slice_class)
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_slice(slice_name, &block)
|
86
|
+
slice_module =
|
87
|
+
begin
|
88
|
+
slice_module_name = inflector.camelize(slice_name.to_s)
|
89
|
+
inflector.constantize(slice_module_name)
|
90
|
+
rescue NameError
|
91
|
+
Object.const_set(inflector.camelize(slice_module_name), Module.new)
|
92
|
+
end
|
93
|
+
|
94
|
+
slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block))
|
95
|
+
end
|
96
|
+
|
97
|
+
def root
|
98
|
+
application.root
|
99
|
+
end
|
100
|
+
|
101
|
+
def inflector
|
102
|
+
application.inflector
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/view"
|
4
|
+
require "hanami/view/context"
|
5
|
+
require_relative "../../errors"
|
6
|
+
require_relative "../../slice_configurable"
|
7
|
+
require_relative "slice_configured_context"
|
8
|
+
|
9
|
+
module Hanami
|
10
|
+
class Application
|
11
|
+
class View < Hanami::View
|
12
|
+
# View context for views in Hanami applications.
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
# @since 2.0.0
|
16
|
+
class Context < Hanami::View::Context
|
17
|
+
extend Hanami::SliceConfigurable
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
def self.configure_for_slice(slice)
|
21
|
+
extend SliceConfiguredContext.new(slice)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @see SliceConfiguredContext#define_new
|
25
|
+
def initialize(**kwargs)
|
26
|
+
defaults = {content: {}}
|
27
|
+
|
28
|
+
super(**kwargs, **defaults)
|
29
|
+
end
|
30
|
+
|
31
|
+
def inflector
|
32
|
+
_options.fetch(:inflector)
|
33
|
+
end
|
34
|
+
|
35
|
+
def routes
|
36
|
+
_options.fetch(:routes)
|
37
|
+
end
|
38
|
+
|
39
|
+
def settings
|
40
|
+
_options.fetch(:settings)
|
41
|
+
end
|
42
|
+
|
43
|
+
def assets
|
44
|
+
unless _options[:assets]
|
45
|
+
raise Hanami::ComponentLoadError, "hanami-assets gem is required to access assets"
|
46
|
+
end
|
47
|
+
|
48
|
+
_options[:assets]
|
49
|
+
end
|
50
|
+
|
51
|
+
def content_for(key, value = nil, &block)
|
52
|
+
content = _options[:content]
|
53
|
+
output = nil
|
54
|
+
|
55
|
+
if block
|
56
|
+
content[key] = yield
|
57
|
+
elsif value
|
58
|
+
content[key] = value
|
59
|
+
else
|
60
|
+
output = content[key]
|
61
|
+
end
|
62
|
+
|
63
|
+
output
|
64
|
+
end
|
65
|
+
|
66
|
+
def current_path
|
67
|
+
request.fullpath
|
68
|
+
end
|
69
|
+
|
70
|
+
def csrf_token
|
71
|
+
request.session[Hanami::Action::CSRFProtection::CSRF_TOKEN]
|
72
|
+
end
|
73
|
+
|
74
|
+
def request
|
75
|
+
_options.fetch(:request)
|
76
|
+
end
|
77
|
+
|
78
|
+
def session
|
79
|
+
request.session
|
80
|
+
end
|
81
|
+
|
82
|
+
def flash
|
83
|
+
response.flash
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# TODO: create `Request#flash` so we no longer need the `response`
|
89
|
+
def response
|
90
|
+
_options.fetch(:response)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/view"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class Application
|
7
|
+
class View < Hanami::View
|
8
|
+
# Provides slice-specific configuration and behavior for any view context class
|
9
|
+
# defined within a slice's module namespace.
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
# @since 2.0.0
|
13
|
+
class SliceConfiguredContext < Module
|
14
|
+
attr_reader :slice
|
15
|
+
|
16
|
+
def initialize(slice)
|
17
|
+
super()
|
18
|
+
@slice = slice
|
19
|
+
end
|
20
|
+
|
21
|
+
def extended(_context_class)
|
22
|
+
define_new
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#<#{self.class.name}[#{slice.name}]>"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Defines a {.new} method on the context class that resolves key components from
|
32
|
+
# the application container and provides them to {#initialize} as injected
|
33
|
+
# dependencies.
|
34
|
+
#
|
35
|
+
# This includes the following application components:
|
36
|
+
#
|
37
|
+
# - the configured inflector as `inflector`
|
38
|
+
# - "settings" from the application container as `settings`
|
39
|
+
# - "routes" from the application container as `routes`
|
40
|
+
# - "assets" from the application container as `assets`
|
41
|
+
def define_new
|
42
|
+
inflector = slice.inflector
|
43
|
+
resolve_settings = method(:resolve_settings)
|
44
|
+
resolve_routes = method(:resolve_routes)
|
45
|
+
resolve_assets = method(:resolve_assets)
|
46
|
+
|
47
|
+
define_method :new do |**kwargs|
|
48
|
+
kwargs[:inflector] ||= inflector
|
49
|
+
kwargs[:settings] ||= resolve_settings.()
|
50
|
+
kwargs[:routes] ||= resolve_routes.()
|
51
|
+
kwargs[:assets] ||= resolve_assets.()
|
52
|
+
|
53
|
+
super(**kwargs)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve_settings
|
58
|
+
slice.application[:settings] if slice.application.key?(:settings)
|
59
|
+
end
|
60
|
+
|
61
|
+
def resolve_routes
|
62
|
+
slice.application[:routes_helper] if slice.application.key?(:routes_helper)
|
63
|
+
end
|
64
|
+
|
65
|
+
def resolve_assets
|
66
|
+
slice.application[:assets] if slice.application.key?(:assets)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/view"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class Application
|
7
|
+
class View < Hanami::View
|
8
|
+
# Provides slice-specific configuration and behavior for any view class defined
|
9
|
+
# within a slice's module namespace.
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
# @since 2.0.0
|
13
|
+
class SliceConfiguredView < Module
|
14
|
+
attr_reader :slice
|
15
|
+
|
16
|
+
def initialize(slice)
|
17
|
+
super()
|
18
|
+
@slice = slice
|
19
|
+
end
|
20
|
+
|
21
|
+
def extended(view_class)
|
22
|
+
configure_view(view_class)
|
23
|
+
define_inherited
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"#<#{self.class.name}[#{slice.name}]>"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# rubocop:disable Metrics/AbcSize
|
33
|
+
def configure_view(view_class)
|
34
|
+
view_class.settings.each do |setting|
|
35
|
+
if slice.application.config.views.respond_to?(:"#{setting}")
|
36
|
+
view_class.config.public_send(
|
37
|
+
:"#{setting}=",
|
38
|
+
slice.application.config.views.public_send(:"#{setting}")
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
view_class.config.inflector = inflector
|
44
|
+
view_class.config.paths = prepare_paths(slice, view_class.config.paths)
|
45
|
+
view_class.config.template = template_name(view_class)
|
46
|
+
|
47
|
+
if (part_namespace = namespace_from_path(slice.application.config.views.parts_path))
|
48
|
+
view_class.config.part_namespace = part_namespace
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# rubocop:enable Metrics/AbcSize
|
52
|
+
|
53
|
+
def define_inherited
|
54
|
+
template_name = method(:template_name)
|
55
|
+
|
56
|
+
define_method(:inherited) do |subclass|
|
57
|
+
super(subclass)
|
58
|
+
subclass.config.template = template_name.(subclass)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def prepare_paths(slice, configured_paths)
|
63
|
+
configured_paths.map { |path|
|
64
|
+
if path.dir.relative?
|
65
|
+
slice.root.join(path.dir)
|
66
|
+
else
|
67
|
+
path
|
68
|
+
end
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def namespace_from_path(path)
|
73
|
+
path = "#{slice.slice_name.path}/#{path}"
|
74
|
+
|
75
|
+
begin
|
76
|
+
require path
|
77
|
+
rescue LoadError => exception
|
78
|
+
raise exception unless exception.path == path
|
79
|
+
end
|
80
|
+
|
81
|
+
begin
|
82
|
+
inflector.constantize(inflector.camelize(path))
|
83
|
+
rescue NameError => exception
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def template_name(view_class)
|
88
|
+
slice
|
89
|
+
.inflector
|
90
|
+
.underscore(view_class.name)
|
91
|
+
.sub(/^#{slice.slice_name.path}\//, "")
|
92
|
+
.sub(/^#{view_class.config.template_inference_base}\//, "")
|
93
|
+
end
|
94
|
+
|
95
|
+
def inflector
|
96
|
+
slice.inflector
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|