hanami 2.0.0.beta3 → 2.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/hanami.gemspec +5 -5
  4. data/lib/hanami/app.rb +4 -4
  5. data/lib/hanami/assets/{app_configuration.rb → app_config.rb} +10 -10
  6. data/lib/hanami/assets/{configuration.rb → config.rb} +4 -4
  7. data/lib/hanami/{configuration → config}/actions/content_security_policy.rb +2 -2
  8. data/lib/hanami/{configuration → config}/actions/cookies.rb +6 -5
  9. data/lib/hanami/{configuration → config}/actions/sessions.rb +2 -2
  10. data/lib/hanami/{configuration → config}/actions.rb +11 -12
  11. data/lib/hanami/{configuration → config}/logger.rb +2 -2
  12. data/lib/hanami/config/null_config.rb +14 -0
  13. data/lib/hanami/{configuration → config}/router.rb +8 -8
  14. data/lib/hanami/{configuration → config}/sessions.rb +2 -2
  15. data/lib/hanami/{configuration → config}/views.rb +10 -10
  16. data/lib/hanami/{configuration.rb → config.rb} +19 -19
  17. data/lib/hanami/errors.rb +3 -0
  18. data/lib/hanami/extensions/action/slice_configured_action.rb +1 -1
  19. data/lib/hanami/providers/inflector.rb +0 -2
  20. data/lib/hanami/providers/logger.rb +1 -3
  21. data/lib/hanami/providers/rack.rb +0 -2
  22. data/lib/hanami/providers/routes.rb +0 -2
  23. data/lib/hanami/settings/env_store.rb +3 -3
  24. data/lib/hanami/settings.rb +87 -5
  25. data/lib/hanami/slice/routing/middleware/stack.rb +45 -1
  26. data/lib/hanami/slice.rb +52 -37
  27. data/lib/hanami/slice_configurable.rb +1 -1
  28. data/lib/hanami/slice_registrar.rb +6 -0
  29. data/lib/hanami/version.rb +1 -1
  30. data/lib/hanami.rb +0 -1
  31. data/spec/integration/container/prepare_container_spec.rb +2 -0
  32. data/spec/integration/rack_app/body_parser_spec.rb +108 -0
  33. data/spec/integration/rack_app/middleware_spec.rb +28 -0
  34. data/spec/integration/settings/access_in_slice_class_body_spec.rb +82 -0
  35. data/spec/integration/settings/access_to_constants_spec.rb +23 -146
  36. data/spec/integration/{slices/slice_settings_spec.rb → settings/slice_registration_spec.rb} +5 -1
  37. data/spec/integration/settings/using_types_spec.rb +4 -11
  38. data/spec/support/app_integration.rb +18 -15
  39. data/spec/unit/hanami/{configuration → config}/actions/content_security_policy_spec.rb +10 -10
  40. data/spec/unit/hanami/{configuration → config}/actions/cookies_spec.rb +6 -6
  41. data/spec/unit/hanami/{configuration → config}/actions/csrf_protection_spec.rb +12 -12
  42. data/spec/unit/hanami/config/actions/default_values_spec.rb +54 -0
  43. data/spec/unit/hanami/{configuration → config}/actions/sessions_spec.rb +5 -5
  44. data/spec/unit/hanami/{configuration → config}/actions_spec.rb +9 -10
  45. data/spec/unit/hanami/{configuration → config}/base_url_spec.rb +2 -2
  46. data/spec/unit/hanami/{configuration → config}/inflector_spec.rb +2 -2
  47. data/spec/unit/hanami/{configuration → config}/logger_spec.rb +4 -4
  48. data/spec/unit/hanami/{configuration → config}/router_spec.rb +7 -8
  49. data/spec/unit/hanami/{configuration → config}/slices_spec.rb +2 -2
  50. data/spec/unit/hanami/{configuration → config}/views_spec.rb +11 -12
  51. data/spec/unit/hanami/{configuration_spec.rb → config_spec.rb} +3 -3
  52. data/spec/unit/hanami/settings_spec.rb +65 -10
  53. data/spec/unit/hanami/slice_configurable_spec.rb +21 -2
  54. data/spec/unit/hanami/version_spec.rb +1 -1
  55. metadata +73 -58
  56. data/lib/hanami/configuration/null_configuration.rb +0 -14
  57. data/lib/hanami/providers/settings.rb +0 -98
  58. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +0 -52
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dry/core"
3
4
  require "dry/configurable"
4
- require "dry/core/constants"
5
5
 
6
6
  module Hanami
7
7
  # App settings
@@ -33,11 +33,72 @@ module Hanami
33
33
  # needs to do is implementing a `#fetch` method with the same signature as `Hash#fetch`.
34
34
  #
35
35
  # @see Hanami::Settings::DotenvStore
36
+ #
37
+ # @api public
36
38
  # @since 2.0.0
37
39
  class Settings
40
+ class << self
41
+ def inherited(subclass)
42
+ super
43
+
44
+ if Hanami.bundled?("dry-types")
45
+ require "dry/types"
46
+ subclass.const_set(:Types, Dry.Types())
47
+ end
48
+ end
49
+
50
+ # Loads the settings for a slice.
51
+ #
52
+ # Returns nil if no settings class is defined.
53
+ #
54
+ # @return [Settings, nil]
55
+ #
56
+ # @api private
57
+ def load_for_slice(slice)
58
+ return unless settings_defined?(slice)
59
+
60
+ require_slice_settings(slice) unless slice_settings_class?(slice)
61
+
62
+ slice_settings_class(slice).new(slice.config.settings_store)
63
+ end
64
+
65
+ private
66
+
67
+ # Returns true if settings are defined for the slice.
68
+ #
69
+ # Settings are considered defined if a `Settings` class is already defined in the slice
70
+ # namespace, or a `config/settings.rb` exists under the slice root.
71
+ def settings_defined?(slice)
72
+ slice.namespace.const_defined?(SETTINGS_CLASS_NAME) ||
73
+ slice.root.join("#{SETTINGS_PATH}#{RB_EXT}").file?
74
+ end
75
+
76
+ def slice_settings_class?(slice)
77
+ slice.namespace.const_defined?(SETTINGS_CLASS_NAME)
78
+ end
79
+
80
+ def slice_settings_class(slice)
81
+ slice.namespace.const_get(SETTINGS_CLASS_NAME)
82
+ end
83
+
84
+ def require_slice_settings(slice)
85
+ require "hanami/settings"
86
+
87
+ slice_settings_require_path = File.join(slice.root, SETTINGS_PATH)
88
+
89
+ begin
90
+ require slice_settings_require_path
91
+ rescue LoadError => e
92
+ raise e unless e.path == slice_settings_require_path
93
+ end
94
+ end
95
+ end
96
+
38
97
  # Exception for errors in the definition of settings.
39
98
  #
40
99
  # Its message collects all the individual errors that can be raised for each setting.
100
+ #
101
+ # @api public
41
102
  InvalidSettingsError = Class.new(StandardError) do
42
103
  def initialize(errors)
43
104
  @errors = errors
@@ -52,6 +113,9 @@ module Hanami
52
113
  end
53
114
  end
54
115
 
116
+ # @api private
117
+ Undefined = Dry::Core::Constants::Undefined
118
+
55
119
  # @api private
56
120
  EMPTY_STORE = Dry::Core::Constants::EMPTY_HASH
57
121
 
@@ -59,14 +123,32 @@ module Hanami
59
123
 
60
124
  # @api private
61
125
  def initialize(store = EMPTY_STORE)
62
- errors = config._settings.map(&:name).reduce({}) do |errs, name|
63
- public_send("#{name}=", store.fetch(name) { Dry::Core::Constants::Undefined })
64
- errs
126
+ errors = config._settings.map(&:name).each_with_object({}) do |name, errs|
127
+ value = store.fetch(name, Undefined)
128
+
129
+ if value.eql?(Undefined)
130
+ # When a key is missing entirely from the store, _read_ its value from the config instead,
131
+ # which ensures its setting constructor runs (with a `nil` argument given) and raises any
132
+ # necessary errors.
133
+ public_send(name)
134
+ else
135
+ public_send("#{name}=", value)
136
+ end
65
137
  rescue => e # rubocop:disable Style/RescueStandardError
66
- errs.merge(name => e)
138
+ errs[name] = e
67
139
  end
68
140
 
69
141
  raise InvalidSettingsError, errors if errors.any?
142
+
143
+ config.finalize!
144
+ end
145
+
146
+ def inspect
147
+ "#<#{self.class.to_s} [#{config._settings.map(&:name).join(", ")}]>"
148
+ end
149
+
150
+ def inspect_values
151
+ "#<#{self.class.to_s} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
70
152
  end
71
153
 
72
154
  private
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hanami/middleware"
4
+ require "hanami/errors"
5
+
3
6
  module Hanami
4
7
  class Slice
5
8
  module Routing
@@ -39,11 +42,16 @@ module Hanami
39
42
  # @api private
40
43
  attr_reader :stack
41
44
 
45
+ # @since 2.0.0
46
+ # @api public
47
+ attr_reader :namespaces
48
+
42
49
  # @since 2.0.0
43
50
  # @api private
44
51
  def initialize
45
52
  @prefix = ROOT_PREFIX
46
53
  @stack = Hash.new { |hash, key| hash[key] = [] }
54
+ @namespaces = [Hanami::Middleware]
47
55
  end
48
56
 
49
57
  # @since 2.0.0
@@ -52,11 +60,13 @@ module Hanami
52
60
  super
53
61
  @prefix = source.instance_variable_get(:@prefix).dup
54
62
  @stack = stack.dup
63
+ @namespaces = namespaces.dup
55
64
  end
56
65
 
57
66
  # @since 2.0.0
58
67
  # @api private
59
- def use(middleware, *args, before: nil, after: nil, &blk)
68
+ def use(spec, *args, before: nil, after: nil, &blk)
69
+ middleware = resolve_middleware_class(spec)
60
70
  item = [middleware, args, blk]
61
71
 
62
72
  if before
@@ -141,6 +151,40 @@ module Hanami
141
151
  def index_of(middleware)
142
152
  @stack[@prefix].index { |(m, *)| m.equal?(middleware) }
143
153
  end
154
+
155
+ # @since 2.0.0
156
+ def resolve_middleware_class(spec)
157
+ case spec
158
+ when Symbol then load_middleware_class(spec)
159
+ when Class, Module then spec
160
+ else
161
+ if spec.respond_to?(:call)
162
+ spec
163
+ else
164
+ raise UnsupportedMiddlewareSpecError, spec
165
+ end
166
+ end
167
+ end
168
+
169
+ # @since 2.0.0
170
+ def load_middleware_class(spec)
171
+ begin
172
+ require "hanami/middleware/#{spec}"
173
+ rescue LoadError # rubocop:disable Lint/SuppressedException
174
+ end
175
+
176
+ class_name = Hanami::Utils::String.classify(spec.to_s)
177
+ namespace = namespaces.detect { |ns| ns.const_defined?(class_name) }
178
+
179
+ if namespace
180
+ namespace.const_get(class_name)
181
+ else
182
+ raise(
183
+ UnsupportedMiddlewareSpecError,
184
+ "Failed to find corresponding middleware class for `#{spec}` in #{namespaces.join(', ')}"
185
+ )
186
+ end
187
+ end
144
188
  end
145
189
  end
146
190
  end
data/lib/hanami/slice.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/system/container"
4
3
  require "zeitwerk"
4
+ require "dry/system"
5
+
6
+ require_relative "../hanami"
5
7
  require_relative "constants"
6
8
  require_relative "errors"
9
+ require_relative "settings"
7
10
  require_relative "slice_name"
8
11
  require_relative "slice_registrar"
9
- require_relative "providers/settings"
10
12
 
11
13
  module Hanami
12
14
  # A slice represents any distinct area of concern within an Hanami app.
@@ -17,12 +19,11 @@ module Hanami
17
19
  # Each slice corresponds a single module namespace and a single root directory of source
18
20
  # files for loading as components into its container.
19
21
  #
20
- # Each slice has its own configuration, and may optionally have its own settings,
21
- # routes, as well as other nested slices.
22
+ # Each slice has its own config, and may optionally have its own settings, routes, as well as
23
+ # other nested slices.
22
24
  #
23
- # Slices expect an Hanami app to be defined (which itself is a slice). They will
24
- # initialize their configuration as a copy of the app's, and will also configure
25
- # certain components
25
+ # Slices expect an Hanami app to be defined (which itself is a slice). They will initialize their
26
+ # config as a copy of the app's, and will also configure certain components
26
27
  #
27
28
  # Slices must be _prepared_ and optionally _booted_ before they can be used (see
28
29
  # {ClassMethods.prepare} and {ClassMethods.boot}). A prepared slice will lazily load its
@@ -56,15 +57,14 @@ module Hanami
56
57
  Hanami.app
57
58
  end
58
59
 
59
- # A slice's configuration is copied from the app configuration at time of first access. The
60
- # app should have its configuration completed before slices are loaded.
61
- def configuration
62
- @configuration ||= app.configuration.dup.tap do |config|
60
+ # A slice's config is copied from the app config at time of first access. The app should have
61
+ # its config completed before slices are loaded.
62
+ def config
63
+ @config ||= app.config.dup.tap do |slice_config|
63
64
  # Remove specific values from app that will not apply to this slice
64
- config.root = nil
65
+ slice_config.root = nil
65
66
  end
66
67
  end
67
- alias_method :config, :configuration
68
68
 
69
69
  def slice_name
70
70
  @slice_name ||= SliceName.new(self, inflector: method(:inflector))
@@ -75,11 +75,22 @@ module Hanami
75
75
  end
76
76
 
77
77
  def root
78
- configuration.root
78
+ # Provide a best guess for a root when it is not yet configured.
79
+ #
80
+ # This is particularly useful for user-defined slice classes that access `settings` inside
81
+ # the class body (since the root needed to find the settings file). In this case,
82
+ # `configuration.root` may be nil when `settings` is called, since the root is configured by
83
+ # `SliceRegistrar#configure_slice` _after_ the class is loaded.
84
+ #
85
+ # In common cases, this best guess will be correct since most Hanami slices will be expected
86
+ # to live in the app SLICES_DIR. For advanced cases, the correct slice root should be
87
+ # explicitly configured at the beginning of the slice class body, before any calls to
88
+ # `settings`.
89
+ config.root || app.root.join(SLICES_DIR, slice_name.to_s)
79
90
  end
80
91
 
81
92
  def inflector
82
- configuration.inflector
93
+ config.inflector
83
94
  end
84
95
 
85
96
  def prepare(provider_name = nil)
@@ -180,6 +191,12 @@ module Hanami
180
191
  end
181
192
  end
182
193
 
194
+ def settings
195
+ return @settings if instance_variable_defined?(:@settings)
196
+
197
+ @settings = Settings.load_for_slice(self)
198
+ end
199
+
183
200
  def routes
184
201
  @routes ||= load_routes
185
202
  end
@@ -209,7 +226,7 @@ module Hanami
209
226
  def prepare_slice
210
227
  return self if prepared?
211
228
 
212
- configuration.finalize!
229
+ config.finalize!
213
230
 
214
231
  ensure_slice_name
215
232
  ensure_slice_consts
@@ -222,8 +239,6 @@ module Hanami
222
239
 
223
240
  prepare_autoloader
224
241
 
225
- ensure_prepared
226
-
227
242
  # Load child slices last, ensuring their parent is fully prepared beforehand
228
243
  # (useful e.g. for slices that may wish to access constants defined in the
229
244
  # parent's autoloaded directories)
@@ -250,17 +265,13 @@ module Hanami
250
265
  end
251
266
 
252
267
  def ensure_root
253
- unless configuration.root
268
+ unless config.root
254
269
  raise SliceLoadError, "Slice must have a `config.root` before it can be prepared"
255
270
  end
256
271
  end
257
272
 
258
- def ensure_prepared
259
- # Load settings so we can fail early in case of non-conformant values
260
- self[:settings] if key?(:settings)
261
- end
262
-
263
273
  def prepare_all
274
+ prepare_settings
264
275
  prepare_container_consts
265
276
  prepare_container_plugins
266
277
  prepare_container_base_config
@@ -269,6 +280,15 @@ module Hanami
269
280
  prepare_container_providers
270
281
  end
271
282
 
283
+ def prepare_settings
284
+ container.register(:settings, settings) if settings
285
+ end
286
+
287
+ def prepare_container_consts
288
+ namespace.const_set :Container, container
289
+ namespace.const_set :Deps, container.injector
290
+ end
291
+
272
292
  def prepare_container_plugins
273
293
  container.use(:env, inferrer: -> { Hanami.env })
274
294
 
@@ -285,8 +305,8 @@ module Hanami
285
305
  container.config.root = root
286
306
  container.config.provider_dirs = [File.join("config", "providers")]
287
307
 
288
- container.config.env = configuration.env
289
- container.config.inflector = configuration.inflector
308
+ container.config.env = config.env
309
+ container.config.inflector = config.inflector
290
310
  end
291
311
 
292
312
  def prepare_container_component_dirs
@@ -304,7 +324,7 @@ module Hanami
304
324
  # When auto-registering components in the root, ignore files in `config/` (this is
305
325
  # for framework config only), `lib/` (these will be auto-registered as above), as
306
326
  # well as the configured no_auto_register_paths
307
- no_auto_register_paths = ([LIB_DIR, CONFIG_DIR] + configuration.no_auto_register_paths)
327
+ no_auto_register_paths = ([LIB_DIR, CONFIG_DIR] + config.no_auto_register_paths)
308
328
  .map { |path|
309
329
  path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
310
330
  }
@@ -335,8 +355,6 @@ module Hanami
335
355
  require_relative "providers/routes"
336
356
  register_provider(:routes, source: Providers::Routes.for_slice(self))
337
357
  end
338
-
339
- Providers::Settings.register_with_slice(self)
340
358
  end
341
359
 
342
360
  def prepare_autoloader
@@ -358,17 +376,14 @@ module Hanami
358
376
  autoloader.setup
359
377
  end
360
378
 
361
- def prepare_container_consts
362
- namespace.const_set :Container, container
363
- namespace.const_set :Deps, container.injector
364
- end
365
-
366
379
  def prepare_slices
367
380
  slices.load_slices.each(&:prepare)
368
381
  slices.freeze
369
382
  end
370
383
 
371
384
  def load_routes
385
+ return false unless Hanami.bundled?("hanami-router")
386
+
372
387
  if root.directory?
373
388
  routes_require_path = File.join(root, ROUTES_PATH)
374
389
 
@@ -393,14 +408,14 @@ module Hanami
393
408
 
394
409
  require_relative "slice/router"
395
410
 
396
- config = configuration
411
+ config = self.config
397
412
  rack_monitor = self["rack.monitor"]
398
413
 
399
414
  Slice::Router.new(
400
415
  inspector: inspector,
401
416
  routes: routes,
402
- resolver: configuration.router.resolver.new(slice: self),
403
- **configuration.router.options
417
+ resolver: config.router.resolver.new(slice: self),
418
+ **config.router.options
404
419
  ) do
405
420
  use(rack_monitor)
406
421
  use(*config.sessions.middleware) if config.sessions.enabled?
@@ -56,7 +56,7 @@ module Hanami
56
56
  def slice_for(klass)
57
57
  return unless klass.name
58
58
 
59
- slices = Hanami.app.slices.to_a + [Hanami.app]
59
+ slices = Hanami.app.slices.with_nested + [Hanami.app]
60
60
 
61
61
  slices.detect { |slice| klass.name.include?(slice.namespace.to_s) }
62
62
  end
@@ -75,6 +75,12 @@ module Hanami
75
75
  slices.values
76
76
  end
77
77
 
78
+ def with_nested
79
+ to_a.flat_map { |slice|
80
+ [slice] + slice.slices.with_nested
81
+ }
82
+ end
83
+
78
84
  private
79
85
 
80
86
  def root
@@ -8,7 +8,7 @@ module Hanami
8
8
  module Version
9
9
  # @since 0.9.0
10
10
  # @api private
11
- VERSION = "2.0.0.beta3"
11
+ VERSION = "2.0.0.beta4"
12
12
 
13
13
  # @since 0.9.0
14
14
  # @api private
data/lib/hanami.rb CHANGED
@@ -114,7 +114,6 @@ module Hanami
114
114
  @_mutex.synchronize do
115
115
  @_bundled[gem_name] ||= begin
116
116
  gem(gem_name)
117
- true
118
117
  rescue Gem::LoadError
119
118
  false
120
119
  end
@@ -23,6 +23,8 @@ RSpec.describe "Container / prepare_container", :app_integration do
23
23
 
24
24
  # The prepare_container block is called when the slice is prepared
25
25
  slice.prepare
26
+
27
+ autoloaders_teardown!
26
28
  end
27
29
 
28
30
  describe "in app", :in_prepare_container do
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/test"
4
+
5
+ RSpec.describe "Hanami web app", :app_integration do
6
+ include Rack::Test::Methods
7
+
8
+ let(:app) { Hanami.app }
9
+
10
+ around do |example|
11
+ with_tmp_directory(Dir.mktmpdir, &example)
12
+ end
13
+
14
+ specify "Setting middlewares in the config" do
15
+ write "config/app.rb", <<~RUBY
16
+ require "hanami"
17
+
18
+ module TestApp
19
+ class App < Hanami::App
20
+ config.middleware.use :body_parser, :json
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ write "config/routes.rb", <<~RUBY
26
+ module TestApp
27
+ class Routes < Hanami::Routes
28
+ post "/users", to: "users.create"
29
+ end
30
+ end
31
+ RUBY
32
+
33
+ write "app/actions/users/create.rb", <<~RUBY
34
+ module TestApp
35
+ module Actions
36
+ module Users
37
+ class Create < Hanami::Action
38
+ accept :json
39
+
40
+ def handle(req, res)
41
+ res.body = req.params[:users].join("-")
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ RUBY
48
+
49
+ require "hanami/boot"
50
+
51
+ post(
52
+ "/users",
53
+ JSON.dump("users" => %w[jane john jade joe]),
54
+ "CONTENT_TYPE" => "application/json"
55
+ )
56
+
57
+ expect(last_response).to be_successful
58
+ expect(last_response.body).to eql("jane-john-jade-joe")
59
+ end
60
+
61
+ specify "Configuring custom mime-types and body parser" do
62
+ write "config/app.rb", <<~RUBY
63
+ require "hanami"
64
+
65
+ module TestApp
66
+ class App < Hanami::App
67
+ config.actions.formats["application/json+scim"] = :json
68
+ config.middleware.use :body_parser, [json: "application/json+scim"]
69
+ end
70
+ end
71
+ RUBY
72
+
73
+ write "config/routes.rb", <<~RUBY
74
+ module TestApp
75
+ class Routes < Hanami::Routes
76
+ post "/users", to: "users.create"
77
+ end
78
+ end
79
+ RUBY
80
+
81
+ write "app/actions/users/create.rb", <<~RUBY
82
+ module TestApp
83
+ module Actions
84
+ module Users
85
+ class Create < Hanami::Action
86
+ accept :json
87
+
88
+ def handle(req, res)
89
+ res.body = req.params[:users].join("-")
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ RUBY
96
+
97
+ require "hanami/boot"
98
+
99
+ post(
100
+ "/users",
101
+ JSON.dump("users" => %w[jane john jade joe]),
102
+ "CONTENT_TYPE" => "application/json+scim"
103
+ )
104
+
105
+ expect(last_response).to be_successful
106
+ expect(last_response.body).to eql("jane-john-jade-joe")
107
+ end
108
+ end
@@ -206,4 +206,32 @@ RSpec.describe "Hanami web app", :app_integration do
206
206
  expect(last_response).to be_successful
207
207
  expect(last_response.body).to eql("yes")
208
208
  end
209
+
210
+ context "Using module as a middleware" do
211
+ it "sets the module as the middleware" do
212
+ mod = Module.new
213
+ app = Class.new(Hanami::App) { config.middleware.use(mod) }
214
+
215
+ expect(app.config.middleware.stack["/"][0]).to include(mod)
216
+ end
217
+ end
218
+
219
+ context "Setting an unsupported middleware" do
220
+ it "raises meaningful error when an unsupported middleware spec was passed" do
221
+ expect {
222
+ Class.new(Hanami::App) do
223
+ config.middleware.use("oops")
224
+ end
225
+ }.to raise_error(Hanami::UnsupportedMiddlewareSpecError)
226
+ end
227
+
228
+ it "raises meaningful error when corresponding file failed to load" do
229
+ expect {
230
+ Class.new(Hanami::App) do
231
+ config.middleware.namespaces.delete(Hanami::Middleware)
232
+ config.middleware.use(:body_parser)
233
+ end
234
+ }.to raise_error(Hanami::UnsupportedMiddlewareSpecError)
235
+ end
236
+ end
209
237
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Settings / Access within slice class bodies", :app_integration do
4
+ before do
5
+ @env = ENV.to_h
6
+ end
7
+
8
+ after do
9
+ ENV.replace(@env)
10
+ end
11
+
12
+ context "app class" do
13
+ it "provides access to the settings inside the class body" do
14
+ with_directory(make_tmp_directory) do
15
+ write "config/app.rb", <<~'RUBY'
16
+ require "hanami"
17
+
18
+ module TestApp
19
+ class App < Hanami::App
20
+ @some_flag = settings.some_flag
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ write ".env", <<~'TEXT'
26
+ SOME_FLAG=true
27
+ TEXT
28
+
29
+ write "config/settings.rb", <<~'RUBY'
30
+ module TestApp
31
+ class Settings < Hanami::Settings
32
+ setting :some_flag
33
+ end
34
+ end
35
+ RUBY
36
+
37
+ require "hanami/setup"
38
+
39
+ expect(Hanami.app.instance_variable_get(:@some_flag)).to eq "true"
40
+ end
41
+ end
42
+ end
43
+
44
+ context "slice class" do
45
+ it "provides access to the settings inside the class body" do
46
+ with_directory(make_tmp_directory) do
47
+ write "config/app.rb", <<~'RUBY'
48
+ require "hanami"
49
+
50
+ module TestApp
51
+ class App < Hanami::App
52
+ end
53
+ end
54
+ RUBY
55
+
56
+ write "config/slices/main.rb", <<~'RUBY'
57
+ module Main
58
+ class Slice < Hanami::Slice
59
+ @some_flag = settings.some_flag
60
+ end
61
+ end
62
+ RUBY
63
+
64
+ write ".env", <<~'TEXT'
65
+ SOME_FLAG=true
66
+ TEXT
67
+
68
+ write "slices/main/config/settings.rb", <<~'RUBY'
69
+ module Main
70
+ class Settings < Hanami::Settings
71
+ setting :some_flag
72
+ end
73
+ end
74
+ RUBY
75
+
76
+ require "hanami/prepare"
77
+
78
+ expect(Main::Slice.instance_variable_get(:@some_flag)).to eq "true"
79
+ end
80
+ end
81
+ end
82
+ end