hanami 2.0.0.alpha6 → 2.0.0.alpha8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3fc76408b7d97db32194d24bf73739bde8405fbd13855b7e4ea744f634d0739e
4
- data.tar.gz: 30d954f8332ea7579898a87187a89a0ae2a1b34b5d9f564a86b6f61c0844f2af
3
+ metadata.gz: 36c07c10545a327f0eab55e1a23e3a6f1b258dd7c4db890d16155d98a132bb90
4
+ data.tar.gz: 02a8dbd6f028e198dead1d972aef4a78b04246b3201e2c90f94e2827f4582906
5
5
  SHA512:
6
- metadata.gz: f64124d027449d451f5be703f4c00850c826e4d55ebef1e6c396be63dea59a9019cb74438f7844f1e68a187299ea45c7f89ba1e98267380e7d8e1b1f8666e4b9
7
- data.tar.gz: 8ea32aae47f0fe9536726c977af03904fa23f7258ef747b2cdd1a3b324eb064466beccc9340b7dddfb46d3d6a49c8aba899b1b488c6016377581599efd594577
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