hanami 2.0.0.beta1.1 → 2.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -0
- data/hanami.gemspec +1 -2
- data/lib/hanami/app.rb +76 -16
- data/lib/hanami/assets/{application_configuration.rb → app_configuration.rb} +1 -1
- data/lib/hanami/configuration.rb +20 -20
- data/lib/hanami/extensions/action/slice_configured_action.rb +44 -1
- data/lib/hanami/extensions/view/slice_configured_view.rb +47 -7
- data/lib/hanami/providers/rack.rb +2 -0
- data/lib/hanami/providers/settings.rb +81 -6
- data/lib/hanami/settings/env_store.rb +32 -0
- data/lib/hanami/settings.rb +8 -12
- data/lib/hanami/setup.rb +1 -6
- data/lib/hanami/slice/routing/middleware/stack.rb +26 -5
- data/lib/hanami/slice.rb +38 -45
- data/lib/hanami/slice_configurable.rb +14 -1
- data/lib/hanami/slice_registrar.rb +65 -5
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami.rb +53 -2
- data/spec/new_integration/action/slice_configuration_spec.rb +287 -0
- data/spec/new_integration/code_loading/loading_from_lib_spec.rb +208 -0
- data/spec/new_integration/dotenv_loading_spec.rb +137 -0
- data/spec/new_integration/settings/access_to_constants_spec.rb +169 -0
- data/spec/new_integration/settings/loading_from_env_spec.rb +187 -0
- data/spec/new_integration/settings/settings_component_loading_spec.rb +113 -0
- data/spec/new_integration/settings/using_types_spec.rb +87 -0
- data/spec/new_integration/setup_spec.rb +145 -0
- data/spec/new_integration/slices/slice_loading_spec.rb +171 -0
- data/spec/new_integration/view/context/settings_spec.rb +5 -1
- data/spec/new_integration/view/slice_configuration_spec.rb +289 -0
- data/spec/support/app_integration.rb +4 -5
- data/spec/unit/hanami/configuration/slices_spec.rb +34 -0
- data/spec/unit/hanami/settings/env_store_spec.rb +52 -0
- data/spec/unit/hanami/slice_configurable_spec.rb +2 -2
- data/spec/unit/hanami/version_spec.rb +1 -1
- metadata +30 -28
- data/lib/hanami/settings/dotenv_store.rb +0 -58
- data/spec/new_integration/action/configuration_spec.rb +0 -26
- data/spec/new_integration/settings_spec.rb +0 -115
- data/spec/new_integration/view/configuration_spec.rb +0 -49
- data/spec/unit/hanami/settings/dotenv_store_spec.rb +0 -119
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e9e5c35a72383ba29d2523a9b30da2a3170605bba5cee20562c897025a58524
|
4
|
+
data.tar.gz: 328e2e6aa30db94ce7dbae5f58d3ac0ce8435ec928ed71fa058345ca3a22553f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 769a484cea4c2594afaece8ae368e16003d02f988b6c015370b0b8236de477042611d71432da8d89d885ebabd3e0e013e7dd9299d0622897f44ac3d511980daf
|
7
|
+
data.tar.gz: e5808f8d9195cb242cbc02d136c993167ab124dabda9b239469841c981c621410d80ace966378157f22cdfd4aaf683f885257d96713561e2e12ce54cdadcab0f
|
data/CHANGELOG.md
CHANGED
@@ -2,8 +2,79 @@
|
|
2
2
|
|
3
3
|
The web, with simplicity.
|
4
4
|
|
5
|
+
## v2.0.0.beta2 - 2022-08-16
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- [Tim Riley] Added `config.slices` setting to specify a list of slices to load. This may also be set via a comma-separated string in the `HANAMI_SLICES` env var. [#1189]
|
10
|
+
|
11
|
+
This is useful when only certain parts of your application are required for particular workloads. Specifying those slices directly improves boot time and minimizes memory usage, as well as ensuring clean boundaries between your slices.
|
12
|
+
|
13
|
+
For example, given an app with both `blog`, `shop` and `admin` slices, you can specify only the first two to load:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
# config/app.rb
|
17
|
+
|
18
|
+
module AppName
|
19
|
+
class App < Hanami::App
|
20
|
+
config.slices = %w[blog shop]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
Or by setting `HANAMI_SLICES=blog,shop` in the env.
|
26
|
+
|
27
|
+
You can also specify arbitrarily nested slices using dot delimiters. For example, if you have an `admin` slice with nested `shop` and `blog` slices, you can specify `config.slices = %w[admin.shop]` to only load the admin slice and its nested shop slice.
|
28
|
+
- [Tim Riley] Added `App.prepare_load_path`, which need only be called if you explicitly change your app's `config.root`. The `$LOAD_PATH` is otherwise still prepared automatically for the default `root` of `Dir.pwd`. [#1188]
|
29
|
+
- [Marc Busqué, Tim Riley] Added `Hanami.setup` to find and load your Hanami app file (expected at `config/app.rb`). This will search in the current directory, and will search upwards through parent directories until the app file is found. [#1197]
|
30
|
+
|
31
|
+
This behavior allows `Hanami.setup` (and its counterpart convenience require, `require "hanami/setup"`) to work even when called
|
32
|
+
from a nested directory within your Hanami app.
|
33
|
+
|
34
|
+
Also added `Hanami.app_path`, returning the absolute path of the app file, if found.
|
35
|
+
|
36
|
+
### Changed
|
37
|
+
|
38
|
+
- [Tim Riley] Allow access to autoloaded constants from within `config/settings.rb` [#1186]
|
39
|
+
|
40
|
+
New Hanami apps generate a types module at `lib/[app_name]/types.rb`. Now you can access it within your settings class to type check your settings, e.g.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# config/settings.rb
|
44
|
+
|
45
|
+
module AppName
|
46
|
+
class Settings < Hanami::Settings
|
47
|
+
# By default, AppName::Types is defined at `lib/[app_name]/types.rb`, and can be autoloaded here
|
48
|
+
setting :some_flag, constructor: Types::Params::Bool
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
For settings defined within a slice, you can access types (or any constant) defined within the slice:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# slices/slice_name/config/settings.rb
|
57
|
+
|
58
|
+
module SliceName
|
59
|
+
class Settings < Hanami::Settings
|
60
|
+
# SliceName::Types is expected to be defined at `slices/[slice_name]/types.rb`
|
61
|
+
setting :some_slice_flag, constructor: Types::Params::bool
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
- [Tim Riley] Every slice has a distinct `Zeitwerk::Loader` instance at `Slice.autoloader`. This change enabled the autoloading within settings described above. [#1186]
|
66
|
+
- [Tim Riley] `Slice.settings` reader method has been removed. Access settings via `App["settings"]` or `Slice["settings"]` (after the slice has been prepared) instead. [#1186]
|
67
|
+
- [Tim Riley] dry-types is no longer a dependency specified in the gemspec. This is made available to generated app via a line in their `Gemfile` instead. [#1186]
|
68
|
+
|
69
|
+
### Fixed
|
70
|
+
|
71
|
+
- [Tim Riley] `config` explicitly set in `Hanami::Action` or `Hanami::View` base classes (either app- or slice-level) is no longer overridden by slice-level configuration [#1193]
|
72
|
+
- [Nikita Shilnikov] Load dry-monitor rack extension explicitly to avoid autoloading errors [#1198] [#1199]
|
73
|
+
|
5
74
|
## v2.0.0.beta1.1 - 2022-07-22
|
6
75
|
|
76
|
+
### Changed
|
77
|
+
|
7
78
|
- [Andrew Croome] Specified 2.0.0.beta dependencies for hanami-cli and hanami-utils (to prefer these over previously installed alpha versions) [#1187]
|
8
79
|
|
9
80
|
## v2.0.0.beta1 - 2022-07-20
|
@@ -57,10 +128,12 @@ The web, with simplicity.
|
|
57
128
|
- [Luca Guidi] `Hanami.application` -> `Hanami.app`
|
58
129
|
- [Luca Guidi] `config/application.rb` -> `config/app.rb`
|
59
130
|
- [Luca Guidi] Remove `::Application::` namespace in public API class names (e.g. `Hanami::Application::Routes` -> `Hanami::Routes`) [#1172]
|
131
|
+
- [Tim Riley] Removed `Hanami::Configuration#source_dirs` due to the new autoloading and auto-registration features [#1174]
|
60
132
|
- [Tim Riley] Removed `Hanami::Configuration#settings_path`, `#settings_class_name` [#1175]
|
61
133
|
- [Tim Riley] Removed `Hanami::Configuration::Router#routes_path`, and `#routes_class_name` [#1175]
|
62
134
|
- [Tim Riley] Make `Hanami::App` to inherit from `Hanami::Slice` [#1162]
|
63
135
|
|
136
|
+
|
64
137
|
## v2.0.0.alpha8 - 2022-05-19
|
65
138
|
|
66
139
|
### Added
|
data/hanami.gemspec
CHANGED
@@ -34,10 +34,9 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency "bundler", ">= 1.16", "< 3"
|
35
35
|
spec.add_dependency "dry-configurable", "~> 0.15"
|
36
36
|
spec.add_dependency "dry-core", "~> 0.7"
|
37
|
-
spec.add_dependency "dry-types", "~> 1.5"
|
38
37
|
spec.add_dependency "dry-inflector", "~> 0.2", ">= 0.2.1"
|
39
38
|
spec.add_dependency "dry-system", "~> 0.25", ">= 0.25.0"
|
40
|
-
spec.add_dependency "dry-monitor", "~> 0.6", ">= 0.6.
|
39
|
+
spec.add_dependency "dry-monitor", "~> 0.6", ">= 0.6.3"
|
41
40
|
spec.add_dependency "hanami-cli", "~> 2.0.beta"
|
42
41
|
spec.add_dependency "hanami-utils", "~> 2.0.beta"
|
43
42
|
spec.add_dependency "zeitwerk", "~> 2.4"
|
data/lib/hanami/app.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "zeitwerk"
|
4
3
|
require_relative "configuration"
|
5
4
|
require_relative "constants"
|
6
5
|
require_relative "slice"
|
@@ -31,29 +30,97 @@ module Hanami
|
|
31
30
|
@_mutex.synchronize do
|
32
31
|
subclass.class_eval do
|
33
32
|
@configuration = Hanami::Configuration.new(app_name: slice_name, env: Hanami.env)
|
34
|
-
@autoloader = Zeitwerk::Loader.new
|
35
33
|
|
36
|
-
|
34
|
+
# Prepare the load path (based on the default root of `Dir.pwd`) as early as
|
35
|
+
# possible, so you can make a `require` inside the body of an `App` subclass,
|
36
|
+
# which may be useful for certain kinds of app configuration.
|
37
|
+
prepare_load_path
|
38
|
+
|
39
|
+
load_dotenv
|
37
40
|
end
|
38
41
|
end
|
39
42
|
end
|
40
43
|
|
41
44
|
# App class interface
|
42
45
|
module ClassMethods
|
43
|
-
attr_reader :
|
46
|
+
attr_reader :configuration
|
44
47
|
|
45
48
|
def app_name
|
46
49
|
slice_name
|
47
50
|
end
|
48
51
|
|
52
|
+
# Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/`
|
53
|
+
# directory if it exists. If the lib directory is already added, this will do
|
54
|
+
# nothing.
|
55
|
+
#
|
56
|
+
# In ordinary circumstances, you should never have to call this method: this method
|
57
|
+
# is called immediately upon subclassing {Hanami::App}, as a convenicence to put
|
58
|
+
# lib/ (under the default root of `Dir.pwd`) on the load path automatically. This is
|
59
|
+
# helpful if you need to require files inside the subclass body for performing
|
60
|
+
# certain app configuration steps.
|
61
|
+
#
|
62
|
+
# If you change your app's `config.root` and you need to require files from its
|
63
|
+
# `lib/` directory within your {App} subclass body, you should call
|
64
|
+
# {.prepare_load_path} explicitly after setting the new root.
|
65
|
+
#
|
66
|
+
# Otherwise, this method is called again as part of the app {.prepare} step, so if
|
67
|
+
# you've changed your app's root and do _not_ need to require files within your {App}
|
68
|
+
# subclass body, then you don't need to call this method.
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# module MyApp
|
72
|
+
# class App < Hanami::App
|
73
|
+
# config.root = Pathname(__dir__).join("../src")
|
74
|
+
# prepare_load_path
|
75
|
+
#
|
76
|
+
# # You can make requires for your files here
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# @return [self]
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
# @since 2.0.0
|
84
|
+
def prepare_load_path
|
85
|
+
if (lib_path = root.join(LIB_DIR)).directory?
|
86
|
+
path = lib_path.realpath.to_s
|
87
|
+
$LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
|
88
|
+
end
|
89
|
+
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
49
93
|
private
|
50
94
|
|
51
|
-
|
52
|
-
|
53
|
-
|
95
|
+
# Uses [dotenv](https://github.com/bkeepers/dotenv) (if available) to populate `ENV` from
|
96
|
+
# various `.env` files.
|
97
|
+
#
|
98
|
+
# For a given `HANAMI_ENV` environment, the `.env` files are looked up in the following order:
|
99
|
+
#
|
100
|
+
# - .env.{environment}.local
|
101
|
+
# - .env.local (unless the environment is `test`)
|
102
|
+
# - .env.{environment}
|
103
|
+
# - .env
|
104
|
+
#
|
105
|
+
# If dotenv is unavailable, the method exits and does nothing.
|
106
|
+
def load_dotenv
|
107
|
+
return unless Hanami.bundled?("dotenv")
|
108
|
+
|
109
|
+
hanami_env = Hanami.env
|
110
|
+
dotenv_files = [
|
111
|
+
".env.#{hanami_env}.local",
|
112
|
+
(".env.local" unless hanami_env == :test),
|
113
|
+
".env.#{hanami_env}",
|
114
|
+
".env"
|
115
|
+
].compact
|
116
|
+
|
117
|
+
require "dotenv"
|
118
|
+
Dotenv.load(*dotenv_files)
|
54
119
|
end
|
55
120
|
|
56
121
|
def prepare_all
|
122
|
+
prepare_load_path
|
123
|
+
|
57
124
|
# Make app-wide notifications available as early as possible
|
58
125
|
container.use(:notifications)
|
59
126
|
|
@@ -65,16 +132,9 @@ module Hanami
|
|
65
132
|
# standard steps have been skipped via the empty method overrides below.
|
66
133
|
prepare_app_component_dirs
|
67
134
|
prepare_app_providers
|
68
|
-
|
69
|
-
# The autoloader must be setup after the container is configured, which is the
|
70
|
-
# point at which any component dirs from other slices are added to the autoloader
|
71
|
-
app = self
|
72
|
-
container.after(:configure) do
|
73
|
-
app.send(:prepare_app_autoloader)
|
74
|
-
end
|
75
135
|
end
|
76
136
|
|
77
|
-
# Skip standard slice prepare steps that do not apply to the app
|
137
|
+
# Skip standard slice prepare steps that do not apply to the app
|
78
138
|
def prepare_container_component_dirs; end
|
79
139
|
def prepare_container_imports; end
|
80
140
|
|
@@ -122,7 +182,7 @@ module Hanami
|
|
122
182
|
register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
|
123
183
|
end
|
124
184
|
|
125
|
-
def
|
185
|
+
def prepare_autoloader
|
126
186
|
# Component dirs are automatically pushed to the autoloader by dry-system's
|
127
187
|
# zeitwerk plugin. This method adds other dirs that are not otherwise configured
|
128
188
|
# as component dirs.
|
data/lib/hanami/configuration.rb
CHANGED
@@ -11,7 +11,7 @@ require_relative "constants"
|
|
11
11
|
require_relative "configuration/logger"
|
12
12
|
require_relative "configuration/router"
|
13
13
|
require_relative "configuration/sessions"
|
14
|
-
require_relative "settings/
|
14
|
+
require_relative "settings/env_store"
|
15
15
|
require_relative "slice/routing/middleware/stack"
|
16
16
|
|
17
17
|
module Hanami
|
@@ -27,18 +27,18 @@ module Hanami
|
|
27
27
|
|
28
28
|
setting :inflector, default: Dry::Inflector.new
|
29
29
|
|
30
|
-
setting :settings_store, default: Hanami::Settings::
|
31
|
-
|
32
|
-
setting :
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
30
|
+
setting :settings_store, default: Hanami::Settings::EnvStore.new
|
31
|
+
|
32
|
+
setting :shared_app_component_keys, default: %w[
|
33
|
+
inflector
|
34
|
+
logger
|
35
|
+
notifications
|
36
|
+
rack.monitor
|
37
|
+
routes
|
38
|
+
settings
|
39
|
+
]
|
40
|
+
|
41
|
+
setting :slices
|
42
42
|
|
43
43
|
setting :base_url, default: "http://0.0.0.0:2300", constructor: ->(url) { URI(url) }
|
44
44
|
|
@@ -100,10 +100,9 @@ module Hanami
|
|
100
100
|
@environments = DEFAULT_ENVIRONMENTS.clone
|
101
101
|
@env = env
|
102
102
|
|
103
|
-
#
|
104
|
-
# have appropriate values for the current app
|
103
|
+
# Apply default values that are only knowable at initialize-time (vs require-time)
|
105
104
|
self.root = Dir.pwd
|
106
|
-
|
105
|
+
load_from_env
|
107
106
|
|
108
107
|
config.logger = Configuration::Logger.new(env: env, app_name: app_name)
|
109
108
|
|
@@ -199,7 +198,10 @@ module Hanami
|
|
199
198
|
|
200
199
|
private
|
201
200
|
|
202
|
-
|
201
|
+
def load_from_env
|
202
|
+
self.slices = ENV["HANAMI_SLICES"]&.split(",")&.map(&:strip)
|
203
|
+
end
|
204
|
+
|
203
205
|
def apply_env_config(env = self.env)
|
204
206
|
environments[env].each do |block|
|
205
207
|
instance_eval(&block)
|
@@ -207,7 +209,7 @@ module Hanami
|
|
207
209
|
end
|
208
210
|
|
209
211
|
# @api private
|
210
|
-
def load_dependent_config(require_path
|
212
|
+
def load_dependent_config(require_path)
|
211
213
|
require require_path
|
212
214
|
yield
|
213
215
|
rescue LoadError => e
|
@@ -217,7 +219,6 @@ module Hanami
|
|
217
219
|
NullConfiguration.new
|
218
220
|
end
|
219
221
|
|
220
|
-
# @api private
|
221
222
|
def method_missing(name, *args, &block)
|
222
223
|
if config.respond_to?(name)
|
223
224
|
config.public_send(name, *args, &block)
|
@@ -226,7 +227,6 @@ module Hanami
|
|
226
227
|
end
|
227
228
|
end
|
228
229
|
|
229
|
-
# @api private
|
230
230
|
def respond_to_missing?(name, _incude_all = false)
|
231
231
|
config.respond_to?(name) || super
|
232
232
|
end
|
@@ -48,7 +48,50 @@ module Hanami
|
|
48
48
|
|
49
49
|
def configure_action(action_class)
|
50
50
|
action_class.config.settings.each do |setting|
|
51
|
-
|
51
|
+
# Configure the action from config on the slice, _unless it has already been configured
|
52
|
+
# by a parent slice_, and re-configuring it for this slice would make no change.
|
53
|
+
#
|
54
|
+
# In the case of most slices, its actions config is likely to be the same as its parent
|
55
|
+
# (since each slice copies its `config` from its parent), and if we re-apply the config
|
56
|
+
# here, then it may possibly overwrite config customisations explicitly made in parent
|
57
|
+
# action classes.
|
58
|
+
#
|
59
|
+
# For example, given an app-level base action class, with custom config:
|
60
|
+
#
|
61
|
+
# module MyApp
|
62
|
+
# class Action < Hanami::Action
|
63
|
+
# config.default_response_format = :json
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# And then an action in a slice inheriting from it:
|
68
|
+
#
|
69
|
+
# module MySlice
|
70
|
+
# module Actions
|
71
|
+
# class SomeAction < MyApp::Action
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# In this case, `SliceConfiguredAction` will be extended two times:
|
77
|
+
#
|
78
|
+
# 1. When `MyApp::Action` is defined
|
79
|
+
# 2. Again when `MySlice::Actions::SomeAction` is defined
|
80
|
+
#
|
81
|
+
# If we blindly re-configure all action settings each time `SliceConfiguredAction` is
|
82
|
+
# extended, then at the point of (2) above, we'd end up overwriting the custom
|
83
|
+
# `config.default_response_format` explicitly configured in the `MyApp::Action` base
|
84
|
+
# class, leaving `MySlice::Actions::SomeAction` with `config.default_response_format` of
|
85
|
+
# `:html` (the default at `Hanami.app.config.actions.default_response_format`), and not
|
86
|
+
# the `:json` value configured in its immediate superclass.
|
87
|
+
#
|
88
|
+
# This would be surprising behavior, and we want to avoid it.
|
89
|
+
slice_value = slice.config.actions.public_send(:"#{setting}")
|
90
|
+
parent_value = slice.parent.config.actions.public_send(:"#{setting}") if slice.parent
|
91
|
+
|
92
|
+
next if slice.parent && slice_value == parent_value
|
93
|
+
|
94
|
+
action_class.config.public_send(:"#{setting}=", slice_value)
|
52
95
|
end
|
53
96
|
end
|
54
97
|
|
@@ -32,12 +32,52 @@ module Hanami
|
|
32
32
|
# rubocop:disable Metrics/AbcSize
|
33
33
|
def configure_view(view_class)
|
34
34
|
view_class.settings.each do |setting|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
next unless slice.config.views.respond_to?(:"#{setting}")
|
36
|
+
|
37
|
+
# Configure the view from config on the slice, _unless it has already been configured by
|
38
|
+
# a parent slice_, and re-configuring it for this slice would make no change.
|
39
|
+
#
|
40
|
+
# In the case of most slices, its views config is likely to be the same as its parent
|
41
|
+
# (since each slice copies its `config` from its parent), and if we re-apply the config
|
42
|
+
# here, then it may possibly overwrite config customisations explicitly made in parent
|
43
|
+
# view classes.
|
44
|
+
#
|
45
|
+
# For example, given an app-level base view class, with custom config:
|
46
|
+
#
|
47
|
+
# module MyApp
|
48
|
+
# class View < Hanami::View
|
49
|
+
# config.layout = "custom_layout"
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# And then a view in a slice inheriting from it:
|
54
|
+
#
|
55
|
+
# module MySlice
|
56
|
+
# module Views
|
57
|
+
# class SomeView < MyApp::View
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# In this case, `SliceConfiguredView` will be extended two times:
|
63
|
+
#
|
64
|
+
# 1. When `MyApp::View` is defined
|
65
|
+
# 2. Again when `MySlice::Views::SomeView` is defined
|
66
|
+
#
|
67
|
+
# If we blindly re-configure all view settings each time `SliceConfiguredView` is
|
68
|
+
# extended, then at the point of (2) above, we'd end up overwriting the custom
|
69
|
+
# `config.layout` explicitly configured in the `MyApp::View` base class, leaving
|
70
|
+
# `MySlice::Views::SomeView` with `config.layout` of `"app"` (the default as specified
|
71
|
+
# at `Hanami.app.config.views.layout`), and not the `"custom_layout"` value configured
|
72
|
+
# in its immediate superclass.
|
73
|
+
#
|
74
|
+
# This would be surprising behavior, and we want to avoid it.
|
75
|
+
slice_value = slice.config.views.public_send(:"#{setting}")
|
76
|
+
parent_value = slice.parent.config.views.public_send(:"#{setting}") if slice.parent
|
77
|
+
|
78
|
+
next if slice.parent && slice_value == parent_value
|
79
|
+
|
80
|
+
view_class.config.public_send(:"#{setting}=", slice_value)
|
41
81
|
end
|
42
82
|
|
43
83
|
view_class.config.inflector = inflector
|
@@ -86,7 +126,7 @@ module Hanami
|
|
86
126
|
|
87
127
|
begin
|
88
128
|
inflector.constantize(inflector.camelize(path))
|
89
|
-
rescue NameError
|
129
|
+
rescue NameError # rubocop: disable Lint/SuppressedException
|
90
130
|
end
|
91
131
|
end
|
92
132
|
|
@@ -1,22 +1,97 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "dry/system/provider/source"
|
4
|
+
require_relative "../constants"
|
5
|
+
require_relative "../errors"
|
4
6
|
|
5
7
|
module Hanami
|
6
8
|
module Providers
|
9
|
+
# The settings provider loads and registers the "settings" component in app and slice
|
10
|
+
# containers.
|
11
|
+
#
|
12
|
+
# To register this provider with a slice container, you should use
|
13
|
+
# {.register_with_slice}, which will register the provider only if settings are
|
14
|
+
# defined for the slice.
|
15
|
+
#
|
16
|
+
# @see Slice::ClassMethods.prepare_container_providers
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
# @since 2.0.0
|
7
20
|
class Settings < Dry::System::Provider::Source
|
8
|
-
|
9
|
-
|
10
|
-
|
21
|
+
class << self
|
22
|
+
# Registers the provider with the slice's container, but only if settings are
|
23
|
+
# defined for the slice.
|
24
|
+
def register_with_slice(slice)
|
25
|
+
return unless settings_defined?(slice)
|
26
|
+
|
27
|
+
slice.register_provider(:settings, source: with_slice(slice))
|
28
|
+
end
|
29
|
+
|
30
|
+
# Creates a new subclass of the provider for the given slice.
|
31
|
+
#
|
32
|
+
# You must do this before registering the provider with a container. The provider
|
33
|
+
# uses the slice to locate the settings definition based on the slice's config.
|
34
|
+
def with_slice(slice)
|
35
|
+
Class.new(self) do |klass|
|
36
|
+
klass.instance_variable_set(:@slice, slice)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the slice for the provider
|
41
|
+
def slice
|
42
|
+
unless @slice
|
43
|
+
raise SliceLoadError, "a slice must be given to #{self} via `.with_slice(slice)`"
|
44
|
+
end
|
45
|
+
|
46
|
+
@slice
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Returns true if settings are defined for the slice.
|
52
|
+
#
|
53
|
+
# Settings are considered defined if a `Settings` class is already defined in the
|
54
|
+
# slice namespace, or a `config/settings.rb` exists under the slice root.
|
55
|
+
def settings_defined?(slice)
|
56
|
+
slice.namespace.const_defined?(SETTINGS_CLASS_NAME) ||
|
57
|
+
slice.root.join("#{SETTINGS_PATH}#{RB_EXT}").file?
|
11
58
|
end
|
12
59
|
end
|
13
60
|
|
14
|
-
def
|
15
|
-
|
61
|
+
def prepare
|
62
|
+
require_slice_settings unless slice_settings_class?
|
16
63
|
end
|
17
64
|
|
18
65
|
def start
|
19
|
-
|
66
|
+
settings = slice_settings_class.new(slice.config.settings_store)
|
67
|
+
|
68
|
+
register :settings, settings
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def slice
|
74
|
+
self.class.slice
|
75
|
+
end
|
76
|
+
|
77
|
+
def slice_settings_class?
|
78
|
+
slice.namespace.const_defined?(SETTINGS_CLASS_NAME)
|
79
|
+
end
|
80
|
+
|
81
|
+
def slice_settings_class
|
82
|
+
slice.namespace.const_get(SETTINGS_CLASS_NAME)
|
83
|
+
end
|
84
|
+
|
85
|
+
def require_slice_settings
|
86
|
+
require "hanami/settings"
|
87
|
+
|
88
|
+
slice_settings_require_path = File.join(slice.root, SETTINGS_PATH)
|
89
|
+
|
90
|
+
begin
|
91
|
+
require slice_settings_require_path
|
92
|
+
rescue LoadError => e
|
93
|
+
raise e unless e.path == slice_settings_require_path
|
94
|
+
end
|
20
95
|
end
|
21
96
|
end
|
22
97
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/constants"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class Settings
|
7
|
+
# The default store for {Hanami:Settings}, loading setting values from `ENV`.
|
8
|
+
#
|
9
|
+
# If your app loads the dotenv gem, then `ENV` will also be populated from various `.env` files when
|
10
|
+
# you subclass `Hanami::App`.
|
11
|
+
#
|
12
|
+
# @since 2.0.0
|
13
|
+
# @api private
|
14
|
+
class EnvStore
|
15
|
+
Undefined = Dry::Core::Constants::Undefined
|
16
|
+
|
17
|
+
attr_reader :store, :hanami_env
|
18
|
+
|
19
|
+
def initialize(store: ENV, hanami_env: Hanami.env)
|
20
|
+
@store = store
|
21
|
+
@hanami_env = hanami_env
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(name, default_value = Undefined, &block)
|
25
|
+
name = name.to_s.upcase
|
26
|
+
args = default_value == Undefined ? [name] : [name, default_value]
|
27
|
+
|
28
|
+
store.fetch(*args, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/hanami/settings.rb
CHANGED
@@ -22,26 +22,22 @@ module Hanami
|
|
22
22
|
# end
|
23
23
|
# end
|
24
24
|
#
|
25
|
-
# Settings are defined with
|
26
|
-
#
|
27
|
-
# take a look there to see the supported syntax.
|
25
|
+
# Settings are defined with [dry-configurable](https://dry-rb.org/gems/dry-configurable/), so you
|
26
|
+
# can take a look there to see the supported syntax.
|
28
27
|
#
|
29
|
-
# Users work with an instance of this class made available within the
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# {Hanami::Settings::DotenvStore}.
|
28
|
+
# Users work with an instance of this class made available within the `settings` key in the
|
29
|
+
# container. The instance gets its settings populated from a configurable store, which defaults to
|
30
|
+
# {Hanami::Settings::EnvStore}.
|
33
31
|
#
|
34
|
-
# A different store can be set through the `settings_store` Hanami
|
35
|
-
#
|
36
|
-
# with the same signature as `Hash#fetch`.
|
32
|
+
# A different store can be set through the `settings_store` Hanami configuration option. All it
|
33
|
+
# needs to do is implementing a `#fetch` method with the same signature as `Hash#fetch`.
|
37
34
|
#
|
38
35
|
# @see Hanami::Settings::DotenvStore
|
39
36
|
# @since 2.0.0
|
40
37
|
class Settings
|
41
38
|
# Exception for errors in the definition of settings.
|
42
39
|
#
|
43
|
-
# Its message collects all the individual errors that can be raised for
|
44
|
-
# each setting.
|
40
|
+
# Its message collects all the individual errors that can be raised for each setting.
|
45
41
|
InvalidSettingsError = Class.new(StandardError) do
|
46
42
|
def initialize(errors)
|
47
43
|
@errors = errors
|