hanami 2.0.0.alpha2 → 2.0.0.alpha3

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,8 +9,11 @@ module Hanami
9
9
  #
10
10
  # @since 2.0.0
11
11
  class Sessions
12
+ NULL_SESSION_OPTION = :null
13
+ private_constant :NULL_SESSION_OPTION
14
+
12
15
  def self.null
13
- NULL_STORAGE
16
+ self.class.new(NULL_SESSION_OPTION)
14
17
  end
15
18
 
16
19
  attr_reader :storage, :options
@@ -23,7 +26,7 @@ module Hanami
23
26
  end
24
27
 
25
28
  def enabled?
26
- storage != NULL_STORAGE
29
+ storage != NULL_SESSION_OPTION
27
30
  end
28
31
 
29
32
  def middleware
@@ -32,9 +35,6 @@ module Hanami
32
35
 
33
36
  private
34
37
 
35
- NULL_STORAGE = :null
36
- private_constant :NULL_STORAGE
37
-
38
38
  def storage_middleware
39
39
  require_storage
40
40
 
@@ -3,9 +3,15 @@
3
3
  require "uri"
4
4
  require "concurrent/hash"
5
5
  require "concurrent/array"
6
+ require "dry/configurable"
6
7
  require "dry/inflector"
7
8
  require "pathname"
8
- require "zeitwerk"
9
+
10
+ require_relative "application/settings/dotenv_store"
11
+ require_relative "configuration/logger"
12
+ require_relative "configuration/middleware"
13
+ require_relative "configuration/router"
14
+ require_relative "configuration/sessions"
9
15
 
10
16
  module Hanami
11
17
  # Hanami application configuration
@@ -14,207 +20,124 @@ module Hanami
14
20
  #
15
21
  # rubocop:disable Metrics/ClassLength
16
22
  class Configuration
17
- require_relative "configuration/middleware"
18
- require_relative "configuration/router"
19
- require_relative "configuration/sessions"
23
+ include Dry::Configurable
24
+
25
+ DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
26
+ private_constant :DEFAULT_ENVIRONMENTS
20
27
 
21
28
  attr_reader :actions
29
+ attr_reader :middleware
30
+ attr_reader :router
22
31
  attr_reader :views
23
32
 
24
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
25
- def initialize(env:)
26
- @settings = Concurrent::Hash.new
27
-
28
- self.autoloader = Zeitwerk::Loader.new
33
+ attr_reader :environments
34
+ private :environments
29
35
 
30
- self.env = env
31
- self.environments = DEFAULT_ENVIRONMENTS.clone
36
+ def initialize(env:)
37
+ @environments = DEFAULT_ENVIRONMENTS.clone
38
+ config.env = env
32
39
 
40
+ # Some default setting values must be assigned at initialize-time to ensure they
41
+ # have appropriate values for the current application
33
42
  self.root = Dir.pwd
34
- self.slices_dir = DEFAULT_SLICES_DIR
35
- settings[:slices] = {}
36
-
37
- self.settings_path = DEFAULT_SETTINGS_PATH
38
- self.settings_loader_options = {}
39
-
40
- self.base_url = DEFAULT_BASE_URL
41
-
42
- self.logger = DEFAULT_LOGGER.clone
43
- self.rack_logger_filter_params = DEFAULT_RACK_LOGGER_FILTER_PARAMS.clone
44
- self.sessions = DEFAULT_SESSIONS
45
-
46
- self.router = Router.new(base_url)
47
- self.middleware = Middleware.new
48
-
49
- self.inflections = Dry::Inflector.new
43
+ self.settings_store = Application::Settings::DotenvStore.new.with_dotenv_loaded
50
44
 
45
+ # Config for actions (same for views, below) may not be available if the gem isn't
46
+ # loaded; fall back to a null config object if it's missing
51
47
  @actions = begin
52
48
  require_path = "hanami/action/application_configuration"
53
49
  require require_path
54
50
  Hanami::Action::ApplicationConfiguration.new
55
51
  rescue LoadError => e
56
52
  raise e unless e.path == require_path
57
- Object.new
53
+ require_relative "configuration/null_configuration"
54
+ NullConfiguration.new
58
55
  end
59
56
 
57
+ @middleware = Middleware.new
58
+
59
+ @router = Router.new(self)
60
+
60
61
  @views = begin
61
62
  require_path = "hanami/view/application_configuration"
62
63
  require require_path
63
64
  Hanami::View::ApplicationConfiguration.new
64
65
  rescue LoadError => e
65
66
  raise e unless e.path == require_path
66
- Object.new
67
+ require_relative "configuration/null_configuration"
68
+ NullConfiguration.new
67
69
  end
68
- end
69
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
70
-
71
- def finalize
72
- environment_for(env).each do |blk|
73
- instance_eval(&blk)
74
- end
75
-
76
- # Finalize nested configuration
77
- #
78
- # TODO: would be good to just create empty configurations for actions/views
79
- # instead of plain objects
80
- actions.finalize! if actions.respond_to?(:finalize!)
81
- views.finalize! if views.respond_to?(:finalize!)
82
-
83
- self
84
- end
85
-
86
- def environment(name, &blk)
87
- environment_for(name).push(blk)
88
- end
89
-
90
- def autoloader=(loader)
91
- settings[:autoloader] = loader || nil
92
- end
93
-
94
- def autoloader
95
- settings.fetch(:autoloader)
96
- end
97
-
98
- def env=(value)
99
- settings[:env] = value
100
- end
101
70
 
102
- def env
103
- settings.fetch(:env)
71
+ yield self if block_given?
104
72
  end
105
73
 
106
- def root=(root)
107
- settings[:root] = Pathname(root)
108
- end
109
-
110
- def root
111
- settings.fetch(:root)
112
- end
113
-
114
- def slices_dir=(dir)
115
- settings[:slices_dir] = dir
116
- end
117
-
118
- def slices_dir
119
- settings.fetch(:slices_dir)
120
- end
121
-
122
- def slices_namespace=(namespace)
123
- settings[:slices_namespace] = namespace
124
- end
74
+ def environment(env_name, &block)
75
+ environments[env_name] << block
76
+ apply_env_config
125
77
 
126
- def slices_namespace
127
- settings.fetch(:slices_namespace) { Object }
78
+ self
128
79
  end
129
80
 
130
- def slice(slice_name, &block)
131
- settings[:slices][slice_name] = block
132
- end
81
+ def finalize!
82
+ apply_env_config
133
83
 
134
- def slices
135
- settings[:slices]
136
- end
84
+ # Finalize nested configurations
85
+ actions.finalize!
86
+ views.finalize!
87
+ logger.finalize!
88
+ router.finalize!
137
89
 
138
- def settings_path=(value)
139
- settings[:settings_path] = value
90
+ super
140
91
  end
141
92
 
142
- def settings_path
143
- settings.fetch(:settings_path)
144
- end
93
+ setting :env
145
94
 
146
- def settings_loader=(loader)
147
- settings[:settings_loader] = loader
95
+ def env=(new_env)
96
+ config.env = env
97
+ apply_env_config(new_env)
148
98
  end
149
99
 
150
- def settings_loader
151
- settings.fetch(:settings_loader) {
152
- require "hanami/application/settings/loader"
153
- settings[:settings_loader] = Application::Settings::Loader
154
- }
155
- end
100
+ setting :root, constructor: -> path { Pathname(path) }
156
101
 
157
- def settings_loader_options=(options)
158
- settings[:settings_loader_options] = options
159
- end
102
+ setting :inflector, default: Dry::Inflector.new, cloneable: true
160
103
 
161
- def settings_loader_options
162
- settings[:settings_loader_options]
104
+ def inflections(&block)
105
+ self.inflector = Dry::Inflector.new(&block)
163
106
  end
164
107
 
165
- def base_url=(value)
166
- settings[:base_url] = URI.parse(value)
167
- end
108
+ setting :logger, default: Configuration::Logger.new, cloneable: true
168
109
 
169
- def base_url
170
- settings.fetch(:base_url)
110
+ def logger=(logger_instance)
111
+ @logger_instance = logger_instance
171
112
  end
172
113
 
173
- def logger=(options)
174
- settings[:logger] = options
114
+ def logger_instance
115
+ @logger_instance || logger.logger_class.new(**logger.options)
175
116
  end
176
117
 
177
- def logger
178
- settings.fetch(:logger)
179
- end
118
+ setting :settings_path, default: File.join("config", "settings")
180
119
 
181
- def rack_logger_filter_params=(params)
182
- settings[:rack_logger_filter_params] = params
183
- end
120
+ setting :settings_class_name, default: "Settings"
184
121
 
185
- def rack_logger_filter_params
186
- settings[:rack_logger_filter_params]
187
- end
122
+ setting :settings_store, default: Application::Settings::DotenvStore
188
123
 
189
- def router=(value)
190
- settings[:router] = value
191
- end
124
+ setting :slices_dir, default: "slices"
192
125
 
193
- def router
194
- settings.fetch(:router)
195
- end
126
+ setting :slices_namespace, default: Object
196
127
 
197
- def sessions=(*args)
198
- settings[:sessions] = Sessions.new(args)
199
- end
128
+ # TODO: convert into a dedicated object with explicit behaviour around blocks per
129
+ # slice, etc.
130
+ setting :slices, default: {}, constructor: :dup.to_proc
200
131
 
201
- def sessions
202
- settings.fetch(:sessions)
203
- end
132
+ # TODO: turn this into a richer "source dirs" setting that can support enabling
133
+ # of container component loading as an opt in behvior
134
+ setting :component_dir_paths, default: %w[actions repositories views]
204
135
 
205
- def middleware
206
- settings.fetch(:middleware)
207
- end
208
-
209
- def inflections(&blk)
210
- if blk.nil?
211
- settings.fetch(:inflections)
212
- else
213
- settings[:inflections] = Dry::Inflector.new(&blk)
214
- end
136
+ def slice(slice_name, &block)
137
+ slices[slice_name] = block
215
138
  end
216
139
 
217
- alias inflector inflections
140
+ setting :base_url, default: "http://0.0.0.0:2300", constructor: -> url { URI(url) }
218
141
 
219
142
  def for_each_middleware(&blk)
220
143
  stack = middleware.stack.dup
@@ -223,48 +146,26 @@ module Hanami
223
146
  stack.each(&blk)
224
147
  end
225
148
 
226
- protected
149
+ setting :sessions, default: :null, constructor: -> *args { Sessions.new(*args) }
227
150
 
228
- def environment_for(name)
229
- settings[:environments][name]
230
- end
151
+ private
231
152
 
232
- def environments=(values)
233
- settings[:environments] = values
153
+ def apply_env_config(env = self.env)
154
+ environments[env].each do |block|
155
+ instance_eval(&block)
156
+ end
234
157
  end
235
158
 
236
- def middleware=(value)
237
- settings[:middleware] = value
159
+ def method_missing(name, *args, &block)
160
+ if config.respond_to?(name)
161
+ config.public_send(name, *args, &block)
162
+ else
163
+ super
164
+ end
238
165
  end
239
166
 
240
- def inflections=(value)
241
- settings[:inflections] = value
167
+ def respond_to_missing?(name, _incude_all = false)
168
+ config.respond_to?(name) || super
242
169
  end
243
-
244
- private
245
-
246
- DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
247
- private_constant :DEFAULT_ENVIRONMENTS
248
-
249
- DEFAULT_SLICES_DIR = "slices"
250
- private_constant :DEFAULT_SLICES_DIR
251
-
252
- DEFAULT_BASE_URL = "http://0.0.0.0:2300"
253
- private_constant :DEFAULT_BASE_URL
254
-
255
- DEFAULT_LOGGER = { level: :debug }.freeze
256
- private_constant :DEFAULT_LOGGER
257
-
258
- DEFAULT_RACK_LOGGER_FILTER_PARAMS = %w[_csrf password password_confirmation].freeze
259
- private_constant :DEFAULT_RACK_LOGGER_FILTER_PARAMS
260
-
261
- DEFAULT_SETTINGS_PATH = File.join("config", "settings")
262
- private_constant :DEFAULT_SETTINGS_PATH
263
-
264
- DEFAULT_SESSIONS = Sessions.null
265
- private_constant :DEFAULT_SESSIONS
266
-
267
- attr_reader :settings
268
170
  end
269
- # rubocop:enable Metrics/ClassLength
270
171
  end
data/lib/hanami/slice.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/system/container"
4
+ require "dry/system/loader/autoloading"
4
5
  require "pathname"
5
6
 
6
7
  module Hanami
@@ -103,29 +104,57 @@ module Hanami
103
104
  config.name = name
104
105
  config.inflector = application.configuration.inflector
105
106
 
106
- if application.configuration.autoloader
107
- require "dry/system/loader/autoloading"
108
- config.component_dirs.loader = Dry::System::Loader::Autoloading
109
- config.component_dirs.add_to_load_path = false
110
- end
107
+ config.component_dirs.loader = Dry::System::Loader::Autoloading
108
+ config.component_dirs.add_to_load_path = false
111
109
 
112
110
  if root&.directory?
113
111
  config.root = root
114
112
  config.bootable_dirs = ["config/boot"]
115
113
 
114
+ # Add the "lib" component dir; all slices will load components from lib
116
115
  if root.join("lib").directory?
117
- config.component_dirs.add "lib" do |dir|
118
- dir.default_namespace = namespace_path.tr(File::SEPARATOR, config.namespace_separator)
116
+ config.component_dirs.add("lib") do |component_dir|
117
+ # Expect component files in the root of the lib
118
+ # component dir to define classes inside the slice's namespace.
119
+ #
120
+ # e.g. "lib/foo.rb" should define SliceNamespace::Foo, and will be
121
+ # registered as "foo"
122
+ component_dir.namespaces.root(key: nil, const: namespace_path)
123
+
124
+ application.autoloader.push_dir(root.join("lib"), namespace: namespace)
119
125
  end
126
+ end
127
+
128
+ # Add component dirs for each configured component path
129
+ application.configuration.component_dir_paths.each do |slice_dir|
130
+ next unless root.join(slice_dir).directory?
131
+
132
+ config.component_dirs.add(slice_dir) do |component_dir|
133
+ # Expect component files in the root of these component dirs to define
134
+ # classes inside a namespace matching the dir.
135
+ #
136
+ # e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, and
137
+ # will be registered as "actions.foo"
138
+
139
+ dir_namespace_path = File.join(namespace_path, slice_dir)
120
140
 
121
- application.configuration.autoloader&.push_dir(root.join("lib"))
141
+ autoloader_namespace = begin
142
+ inflector.constantize(inflector.camelize(dir_namespace_path))
143
+ rescue NameError
144
+ namespace.const_set(inflector.camelize(slice_dir), Module.new)
145
+ end
146
+
147
+ component_dir.namespaces.root(const: dir_namespace_path, key: slice_dir)
148
+
149
+ application.autoloader.push_dir(
150
+ container.root.join(slice_dir),
151
+ namespace: autoloader_namespace
152
+ )
153
+ end
122
154
  end
123
155
  end
124
156
  end
125
157
 
126
- # Force after configure hook to run
127
- container.configure do; end
128
-
129
158
  if namespace
130
159
  namespace.const_set :Container, container
131
160
  namespace.const_set :Deps, container.injector
@@ -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.alpha2"
11
+ VERSION = "2.0.0.alpha3"
12
12
 
13
13
  # @since 0.9.0
14
14
  # @api private
data/lib/hanami.rb CHANGED
@@ -73,6 +73,10 @@ module Hanami
73
73
  end
74
74
  end
75
75
 
76
+ def self.shutdown
77
+ application.shutdown
78
+ end
79
+
76
80
  def self.bundler_groups
77
81
  [:plugins]
78
82
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.alpha2
4
+ version: 2.0.0.alpha3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-04 00:00:00.000000000 Z
11
+ date: 2021-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,6 +30,26 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '3'
33
+ - !ruby/object:Gem::Dependency
34
+ name: dry-configurable
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.12'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.12.1
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.12'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.12.1
33
53
  - !ruby/object:Gem::Dependency
34
54
  name: dry-core
35
55
  requirement: !ruby/object:Gem::Requirement
@@ -50,20 +70,20 @@ dependencies:
50
70
  requirements:
51
71
  - - "~>"
52
72
  - !ruby/object:Gem::Version
53
- version: '0.1'
73
+ version: '0.2'
54
74
  - - ">="
55
75
  - !ruby/object:Gem::Version
56
- version: 0.1.2
76
+ version: 0.2.1
57
77
  type: :runtime
58
78
  prerelease: false
59
79
  version_requirements: !ruby/object:Gem::Requirement
60
80
  requirements:
61
81
  - - "~>"
62
82
  - !ruby/object:Gem::Version
63
- version: '0.1'
83
+ version: '0.2'
64
84
  - - ">="
65
85
  - !ruby/object:Gem::Version
66
- version: 0.1.2
86
+ version: 0.2.1
67
87
  - !ruby/object:Gem::Dependency
68
88
  name: dry-monitor
69
89
  requirement: !ruby/object:Gem::Requirement
@@ -87,7 +107,7 @@ dependencies:
87
107
  version: '0.19'
88
108
  - - ">="
89
109
  - !ruby/object:Gem::Version
90
- version: 0.19.0
110
+ version: 0.21.0
91
111
  type: :runtime
92
112
  prerelease: false
93
113
  version_requirements: !ruby/object:Gem::Requirement
@@ -97,7 +117,7 @@ dependencies:
97
117
  version: '0.19'
98
118
  - - ">="
99
119
  - !ruby/object:Gem::Version
100
- version: 0.19.0
120
+ version: 0.21.0
101
121
  - !ruby/object:Gem::Dependency
102
122
  name: hanami-cli
103
123
  requirement: !ruby/object:Gem::Requirement
@@ -204,14 +224,13 @@ files:
204
224
  - lib/hanami/application/container/boot/rack_monitor.rb
205
225
  - lib/hanami/application/container/boot/settings.rb
206
226
  - lib/hanami/application/router.rb
227
+ - lib/hanami/application/routes.rb
207
228
  - lib/hanami/application/routing/middleware/stack.rb
208
229
  - lib/hanami/application/routing/resolver.rb
209
230
  - lib/hanami/application/routing/resolver/node.rb
210
231
  - lib/hanami/application/routing/resolver/trie.rb
211
232
  - lib/hanami/application/settings.rb
212
- - lib/hanami/application/settings/definition.rb
213
- - lib/hanami/application/settings/loader.rb
214
- - lib/hanami/application/settings/struct.rb
233
+ - lib/hanami/application/settings/dotenv_store.rb
215
234
  - lib/hanami/boot.rb
216
235
  - lib/hanami/cli/application/cli.rb
217
236
  - lib/hanami/cli/application/command.rb
@@ -222,7 +241,9 @@ files:
222
241
  - lib/hanami/cli/commands/command.rb
223
242
  - lib/hanami/cli/commands/server.rb
224
243
  - lib/hanami/configuration.rb
244
+ - lib/hanami/configuration/logger.rb
225
245
  - lib/hanami/configuration/middleware.rb
246
+ - lib/hanami/configuration/null_configuration.rb
226
247
  - lib/hanami/configuration/router.rb
227
248
  - lib/hanami/configuration/sessions.rb
228
249
  - lib/hanami/init.rb
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "hanami/utils/basic_object"
4
-
5
- module Hanami
6
- class Application
7
- module Settings
8
- # Application settings definition DSL
9
- #
10
- # @since 2.0.0
11
- # @api private
12
- class Definition < Hanami::Utils::BasicObject
13
- attr_reader :settings
14
-
15
- def initialize(&block)
16
- @settings = []
17
- instance_eval(&block) if block
18
- end
19
-
20
- def setting(name, *args)
21
- @settings << [name, args]
22
- end
23
- end
24
- end
25
- end
26
- end