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 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