hanami 2.0.0.alpha5 → 2.0.0.alpha7.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/hanami/slice.rb CHANGED
@@ -1,189 +1,253 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/system/container"
4
- require "dry/system/loader/autoloading"
4
+ require "hanami/errors"
5
5
  require "pathname"
6
+ require_relative "constants"
6
7
 
7
8
  module Hanami
8
9
  # Distinct area of concern within an Hanami application
9
10
  #
10
11
  # @since 2.0.0
11
12
  class Slice
12
- attr_reader :application, :name, :namespace, :root
13
-
14
- def initialize(application, name:, namespace: nil, root: nil, container: nil)
15
- @application = application
16
- @name = name.to_sym
17
- @namespace = namespace
18
- @root = root ? Pathname(root) : root
19
- @container = container || define_container
20
- end
13
+ def self.inherited(klass)
14
+ super
21
15
 
22
- def inflector
23
- application.inflector
24
- end
16
+ klass.extend(ClassMethods)
25
17
 
26
- def namespace_path
27
- @namespace_path ||= inflector.underscore(namespace.to_s)
18
+ # Eagerly initialize any variables that may be accessed inside the subclass body
19
+ klass.instance_variable_set(:@application, Hanami.application)
20
+ klass.instance_variable_set(:@container, Class.new(Dry::System::Container))
28
21
  end
29
22
 
30
- def init
31
- container.import application: application.container
23
+ # rubocop:disable Metrics/ModuleLength
24
+ module ClassMethods
25
+ attr_reader :application, :container
32
26
 
33
- slice_block = application.configuration.slices[name]
34
- instance_eval(&slice_block) if slice_block
35
- end
27
+ def slice_name
28
+ inflector.underscore(name.split(MODULE_DELIMITER)[-2]).to_sym
29
+ end
36
30
 
37
- def boot
38
- container.finalize! do
39
- container.config.env = application.container.config.env
31
+ def namespace
32
+ inflector.constantize(name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER))
40
33
  end
41
34
 
42
- @booted = true
43
- self
44
- end
35
+ def namespace_path
36
+ inflector.underscore(namespace)
37
+ end
45
38
 
46
- # rubocop:disable Style/DoubleNegation
47
- def booted?
48
- !!@booted
49
- end
50
- # rubocop:enable Style/DoubleNegation
39
+ def root
40
+ application.root.join(SLICES_DIR, slice_name.to_s)
41
+ end
51
42
 
52
- def container
53
- @container ||= define_container
54
- end
43
+ def inflector
44
+ application.inflector
45
+ end
46
+
47
+ def prepare(provider_name = nil)
48
+ container.prepare(provider_name) and return self if provider_name
49
+
50
+ return self if prepared?
55
51
 
56
- def import(*slice_names)
57
- raise "Cannot import after booting" if booted?
52
+ ensure_slice_name
53
+ ensure_slice_consts
58
54
 
59
- slice_names.each do |slice_name|
60
- container.import slice_name.to_sym => application.slices.fetch(slice_name.to_sym).container
55
+ prepare_all
56
+
57
+ @prepared = true
58
+ self
61
59
  end
62
- end
63
60
 
64
- def register(*args, &block)
65
- container.register(*args, &block)
66
- end
61
+ def prepare_container(&block)
62
+ @prepare_container_block = block
63
+ end
67
64
 
68
- def register_bootable(*args, &block)
69
- container.boot(*args, &block)
70
- end
65
+ def boot
66
+ return self if booted?
71
67
 
72
- def init_bootable(*args)
73
- container.init(*args)
74
- end
68
+ container.finalize!
75
69
 
76
- def start_bootable(*args)
77
- container.start(*args)
78
- end
70
+ @booted = true
79
71
 
80
- def key?(*args)
81
- container.key?(*args)
82
- end
72
+ self
73
+ end
83
74
 
84
- def keys
85
- container.keys
86
- end
75
+ def shutdown
76
+ container.shutdown!
77
+ self
78
+ end
87
79
 
88
- def [](*args)
89
- container[*args]
90
- end
80
+ def prepared?
81
+ !!@prepared
82
+ end
91
83
 
92
- def resolve(*args)
93
- container.resolve(*args)
94
- end
84
+ def booted?
85
+ !!@booted
86
+ end
95
87
 
96
- private
88
+ def register(...)
89
+ container.register(...)
90
+ end
97
91
 
98
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
99
- def define_container
100
- container = Class.new(Dry::System::Container)
101
- container.use :env
92
+ def register_provider(...)
93
+ container.register_provider(...)
94
+ end
102
95
 
103
- container.configure do |config|
104
- config.name = name
105
- config.inflector = application.configuration.inflector
96
+ def start(...)
97
+ container.start(...)
98
+ end
106
99
 
107
- config.component_dirs.loader = Dry::System::Loader::Autoloading
108
- config.component_dirs.add_to_load_path = false
100
+ def key?(...)
101
+ container.key?(...)
102
+ end
109
103
 
110
- if root&.directory?
111
- config.root = root
112
- config.bootable_dirs = ["config/boot"]
104
+ def keys
105
+ container.keys
106
+ end
113
107
 
114
- # Add component dirs for each configured component path
115
- application.configuration.source_dirs.component_dirs.each do |component_dir|
116
- next unless root.join(component_dir.path).directory?
108
+ def [](...)
109
+ container.[](...)
110
+ end
117
111
 
118
- component_dir = component_dir.dup
112
+ def resolve(...)
113
+ container.resolve(...)
114
+ end
119
115
 
120
- # TODO: this `== "lib"` check should be codified into a method somewhere
121
- if component_dir.path == "lib"
122
- # Expect component files in the root of the lib/ component dir to define
123
- # classes inside the slice's namespace.
124
- #
125
- # e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
126
- # "foo"
127
- component_dir.namespaces.delete_root
128
- component_dir.namespaces.add_root(key: nil, const: namespace_path)
116
+ def export(keys)
117
+ container.config.exports = keys
118
+ end
129
119
 
130
- config.component_dirs.add(component_dir)
120
+ def import(from:, **kwargs)
121
+ # TODO: This should be handled via dry-system (see dry-rb/dry-system#228)
122
+ raise "Cannot import after booting" if booted?
131
123
 
132
- application.autoloader.push_dir(root.join("lib"), namespace: namespace)
133
- else
134
- # Expect component files in the root of non-lib/ component dirs to define
135
- # classes inside a namespace matching that dir.
136
- #
137
- # e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
138
- # registered as "actions.foo"
124
+ application = self.application
139
125
 
140
- dir_namespace_path = File.join(namespace_path, component_dir.path)
126
+ container.after(:configure) do
127
+ if from.is_a?(Symbol) || from.is_a?(String)
128
+ slice_name = from
129
+ from = application.slices[from.to_sym].container
130
+ end
141
131
 
142
- autoloader_namespace = begin
143
- inflector.constantize(inflector.camelize(dir_namespace_path))
144
- rescue NameError
145
- namespace.const_set(inflector.camelize(component_dir.path), Module.new)
146
- end
132
+ as = kwargs[:as] || slice_name
147
133
 
148
- component_dir.namespaces.delete_root
149
- component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path) # TODO: do we need to swap path delimiters for key delimiters here?
134
+ import(from: from, as: as, **kwargs)
135
+ end
136
+ end
150
137
 
151
- config.component_dirs.add(component_dir)
138
+ private
152
139
 
153
- application.autoloader.push_dir(
154
- container.root.join(component_dir.path),
155
- namespace: autoloader_namespace
156
- )
157
- end
140
+ def ensure_slice_name
141
+ unless name
142
+ raise SliceLoadError, "Slice must have a class name before it can be prepared"
143
+ end
144
+ end
145
+
146
+ def ensure_slice_consts
147
+ if namespace.const_defined?(:Container) || namespace.const_defined?(:Deps)
148
+ raise(
149
+ SliceLoadError,
150
+ "#{namespace}::Container and #{namespace}::Deps constants must not already be defined"
151
+ )
152
+ end
153
+ end
154
+
155
+ def prepare_all
156
+ prepare_container_plugins
157
+ prepare_container_base_config
158
+ prepare_container_component_dirs
159
+ prepare_autoloader
160
+ prepare_container_imports
161
+ prepare_container_consts
162
+ instance_exec(container, &@prepare_container_block) if @prepare_container_block
163
+ container.configured!
164
+ end
165
+
166
+ def prepare_container_plugins
167
+ container.use(:env, inferrer: -> { Hanami.env })
168
+
169
+ container.use(
170
+ :zeitwerk,
171
+ loader: application.autoloader,
172
+ run_setup: false,
173
+ eager_load: false
174
+ )
175
+ end
176
+
177
+ def prepare_container_base_config
178
+ container.config.name = slice_name
179
+ container.config.root = root
180
+ container.config.provider_dirs = [File.join("config", "providers")]
181
+
182
+ container.config.env = application.configuration.env
183
+ container.config.inflector = application.configuration.inflector
184
+ end
185
+
186
+ def prepare_container_component_dirs # rubocop:disable Metrics/AbcSize
187
+ return unless root&.directory?
188
+
189
+ # Add component dirs for each configured component path
190
+ application.configuration.source_dirs.component_dirs.each do |component_dir|
191
+ next unless root.join(component_dir.path).directory?
192
+
193
+ component_dir = component_dir.dup
194
+
195
+ if component_dir.path == LIB_DIR
196
+ # Expect component files in the root of the lib/ component dir to define
197
+ # classes inside the slice's namespace.
198
+ #
199
+ # e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
200
+ # "foo"
201
+ component_dir.namespaces.delete_root
202
+ component_dir.namespaces.add_root(key: nil, const: namespace_path)
203
+ else
204
+ # Expect component files in the root of non-lib/ component dirs to define
205
+ # classes inside a namespace matching that dir.
206
+ #
207
+ # e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
208
+ # registered as "actions.foo"
209
+
210
+ dir_namespace_path = File.join(namespace_path, component_dir.path)
211
+
212
+ component_dir.namespaces.delete_root
213
+ component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path)
158
214
  end
159
215
 
160
- # Pass configured autoload dirs to the autoloader
161
- application.configuration.source_dirs.autoload_paths.each do |autoload_path|
162
- next unless root.join(autoload_path).directory?
216
+ container.config.component_dirs.add(component_dir)
217
+ end
218
+ end
163
219
 
164
- dir_namespace_path = File.join(namespace_path, autoload_path)
220
+ def prepare_autoloader # rubocop:disable Metrics/AbcSize
221
+ return unless root&.directory?
165
222
 
166
- autoloader_namespace = begin
167
- inflector.constantize(inflector.camelize(dir_namespace_path))
168
- rescue NameError
169
- namespace.const_set(inflector.camelize(autoload_path), Module.new)
170
- end
223
+ # Pass configured autoload dirs to the autoloader
224
+ application.configuration.source_dirs.autoload_paths.each do |autoload_path|
225
+ next unless root.join(autoload_path).directory?
171
226
 
172
- application.autoloader.push_dir(
173
- container.root.join(autoload_path),
174
- namespace: autoloader_namespace
175
- )
227
+ dir_namespace_path = File.join(namespace_path, autoload_path)
228
+
229
+ autoloader_namespace = begin
230
+ inflector.constantize(inflector.camelize(dir_namespace_path))
231
+ rescue NameError
232
+ namespace.const_set(inflector.camelize(autoload_path), Module.new)
176
233
  end
234
+
235
+ container.config.autoloader.push_dir(
236
+ container.root.join(autoload_path),
237
+ namespace: autoloader_namespace
238
+ )
177
239
  end
178
240
  end
179
241
 
180
- if namespace
242
+ def prepare_container_imports
243
+ container.import from: application.container, as: :application
244
+ end
245
+
246
+ def prepare_container_consts
181
247
  namespace.const_set :Container, container
182
248
  namespace.const_set :Deps, container.injector
183
249
  end
184
-
185
- container
186
250
  end
187
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
251
+ # rubocop:enable Metrics/ModuleLength
188
252
  end
189
253
  end
@@ -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.alpha5"
11
+ VERSION = "2.0.0.alpha7.1"
12
12
 
13
13
  # @since 0.9.0
14
14
  # @api private
@@ -1,19 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
- require "rack/request"
5
- require "hanami/utils/hash"
6
-
7
3
  module Hanami
8
4
  module Web
9
5
  # Rack logger for Hanami applications
10
6
  class RackLogger
11
- attr_reader :logger
12
- attr_reader :filter_params
7
+ REQUEST_METHOD = "REQUEST_METHOD"
8
+ private_constant :REQUEST_METHOD
9
+
10
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
11
+ private_constant :HTTP_X_FORWARDED_FOR
12
+
13
+ REMOTE_ADDR = "REMOTE_ADDR"
14
+ private_constant :REMOTE_ADDR
15
+
16
+ SCRIPT_NAME = "SCRIPT_NAME"
17
+ private_constant :SCRIPT_NAME
18
+
19
+ PATH_INFO = "PATH_INFO"
20
+ private_constant :PATH_INFO
21
+
22
+ ROUTER_PARAMS = "router.params"
23
+ private_constant :ROUTER_PARAMS
24
+
25
+ CONTENT_LENGTH = "Content-Length"
26
+ private_constant :CONTENT_LENGTH
13
27
 
14
- def initialize(logger, filter_params: [])
28
+ def initialize(logger)
15
29
  @logger = logger
16
- @filter_params = filter_params
17
30
  end
18
31
 
19
32
  def attach(rack_monitor)
@@ -26,22 +39,20 @@ module Hanami
26
39
  end
27
40
  end
28
41
 
29
- # rubocop:disable Metrics/MethodLength
30
- def log_request(env, status, time)
42
+ def log_request(env, status, elapsed)
31
43
  data = {
32
- http: env[HTTP_VERSION],
33
44
  verb: env[REQUEST_METHOD],
34
45
  status: status,
46
+ elapsed: "#{elapsed}ms",
35
47
  ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
36
48
  path: env[SCRIPT_NAME] + env[PATH_INFO].to_s,
37
49
  length: extract_content_length(env),
38
- params: extract_params(env),
39
- elapsed: time,
50
+ params: env[ROUTER_PARAMS],
51
+ time: Time.now,
40
52
  }
41
53
 
42
- logger.info JSON.generate(data)
54
+ logger.info(data)
43
55
  end
44
- # rubocop:enable Metrics/MethodLength
45
56
 
46
57
  def log_exception(exception)
47
58
  logger.error exception.message
@@ -50,47 +61,12 @@ module Hanami
50
61
 
51
62
  private
52
63
 
53
- HTTP_VERSION = "HTTP_VERSION"
54
- REQUEST_METHOD = "REQUEST_METHOD"
55
- HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
56
- REMOTE_ADDR = "REMOTE_ADDR"
57
- SCRIPT_NAME = "SCRIPT_NAME"
58
- PATH_INFO = "PATH_INFO"
59
- RACK_ERRORS = "rack.errors"
60
- QUERY_HASH = "rack.request.query_hash"
61
- FORM_HASH = "rack.request.form_hash"
62
- ROUTER_PARAMS = "router.params"
63
- CONTENT_LENGTH = "Content-Length"
64
+ attr_reader :logger
64
65
 
65
66
  def extract_content_length(env)
66
67
  value = env[CONTENT_LENGTH]
67
68
  !value || value.to_s == "0" ? "-" : value
68
69
  end
69
-
70
- def extract_params(env)
71
- result = env.fetch(QUERY_HASH, {})
72
- result.merge!(env.fetch(FORM_HASH, {}))
73
- result.merge!(Hanami::Utils::Hash.deep_stringify(env.fetch(ROUTER_PARAMS, {})))
74
- result
75
- end
76
-
77
- FILTERED = "[FILTERED]"
78
-
79
- # rubocop:disable Metrics/MethodLength
80
- def filter(params)
81
- params.each_with_object({}) do |(k, v), h|
82
- if filter_params.include?(k)
83
- h.update(k => FILTERED)
84
- elsif v.is_a?(Hash)
85
- h.update(k => filter(v))
86
- elsif v.is_a?(Array)
87
- h.update(k => v.map { |m| m.is_a?(Hash) ? filter(m) : m })
88
- else
89
- h[k] = v
90
- end
91
- end
92
- end
93
- # rubocop:enable Metrics/MethodLength
94
70
  end
95
71
  end
96
72
  end
data/lib/hanami.rb CHANGED
@@ -1,19 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "hanami/application"
4
+ require_relative "hanami/errors"
5
+ require_relative "hanami/version"
6
+
7
+
3
8
  # A complete web framework for Ruby
4
9
  #
5
10
  # @since 0.1.0
6
11
  #
7
12
  # @see http://hanamirb.org
8
13
  module Hanami
9
- require "hanami/version"
10
- require "hanami/application"
11
-
12
14
  @_mutex = Mutex.new
13
15
 
14
16
  def self.application
15
17
  @_mutex.synchronize do
16
- raise "Hanami.application not configured" unless defined?(@_application)
18
+ unless defined?(@_application)
19
+ raise ApplicationLoadError,
20
+ "Hanami.application is not yet configured. " \
21
+ "You may need to `require \"hanami/setup\"` to load your config/application.rb file."
22
+ end
17
23
 
18
24
  @_application
19
25
  end
@@ -25,7 +31,9 @@ module Hanami
25
31
 
26
32
  def self.application=(klass)
27
33
  @_mutex.synchronize do
28
- raise "Hanami.application already configured" if defined?(@_application)
34
+ if defined?(@_application)
35
+ raise ApplicationLoadError, "Hanami.application is already configured."
36
+ end
29
37
 
30
38
  @_application = klass unless klass.name.nil?
31
39
  end
@@ -47,8 +55,8 @@ module Hanami
47
55
  application[:logger]
48
56
  end
49
57
 
50
- def self.init
51
- application.init
58
+ def self.prepare
59
+ application.prepare
52
60
  end
53
61
 
54
62
  def self.boot
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.alpha5
4
+ version: 2.0.0.alpha7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-12 00:00:00.000000000 Z
11
+ date: 2022-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -104,20 +104,20 @@ dependencies:
104
104
  requirements:
105
105
  - - "~>"
106
106
  - !ruby/object:Gem::Version
107
- version: '0.22'
107
+ version: '0.23'
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: 0.22.0
110
+ version: 0.23.0
111
111
  type: :runtime
112
112
  prerelease: false
113
113
  version_requirements: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0.22'
117
+ version: '0.23'
118
118
  - - ">="
119
119
  - !ruby/object:Gem::Version
120
- version: 0.22.0
120
+ version: 0.23.0
121
121
  - !ruby/object:Gem::Dependency
122
122
  name: hanami-cli
123
123
  requirement: !ruby/object:Gem::Requirement
@@ -217,13 +217,12 @@ files:
217
217
  - hanami.gemspec
218
218
  - lib/hanami.rb
219
219
  - lib/hanami/application.rb
220
- - lib/hanami/application/autoloader/inflector_adapter.rb
221
- - lib/hanami/application/container/boot/inflector.rb
222
- - lib/hanami/application/container/boot/logger.rb
223
- - lib/hanami/application/container/boot/rack_logger.rb
224
- - lib/hanami/application/container/boot/rack_monitor.rb
225
- - lib/hanami/application/container/boot/routes_helper.rb
226
- - lib/hanami/application/container/boot/settings.rb
220
+ - lib/hanami/application/container/providers/inflector.rb
221
+ - lib/hanami/application/container/providers/logger.rb
222
+ - lib/hanami/application/container/providers/rack_logger.rb
223
+ - lib/hanami/application/container/providers/rack_monitor.rb
224
+ - lib/hanami/application/container/providers/routes_helper.rb
225
+ - lib/hanami/application/container/providers/settings.rb
227
226
  - lib/hanami/application/router.rb
228
227
  - lib/hanami/application/routes.rb
229
228
  - lib/hanami/application/routes_helper.rb
@@ -234,6 +233,7 @@ files:
234
233
  - lib/hanami/application/routing/router.rb
235
234
  - lib/hanami/application/settings.rb
236
235
  - lib/hanami/application/settings/dotenv_store.rb
236
+ - lib/hanami/application/slice_registrar.rb
237
237
  - lib/hanami/assets/application_configuration.rb
238
238
  - lib/hanami/assets/configuration.rb
239
239
  - lib/hanami/boot.rb
@@ -247,13 +247,16 @@ files:
247
247
  - lib/hanami/cli/commands/command.rb
248
248
  - lib/hanami/cli/commands/server.rb
249
249
  - lib/hanami/configuration.rb
250
+ - lib/hanami/configuration/actions.rb
250
251
  - lib/hanami/configuration/logger.rb
251
252
  - lib/hanami/configuration/middleware.rb
252
253
  - lib/hanami/configuration/null_configuration.rb
253
254
  - lib/hanami/configuration/router.rb
254
255
  - lib/hanami/configuration/sessions.rb
255
256
  - lib/hanami/configuration/source_dirs.rb
256
- - lib/hanami/init.rb
257
+ - lib/hanami/constants.rb
258
+ - lib/hanami/errors.rb
259
+ - lib/hanami/prepare.rb
257
260
  - lib/hanami/server.rb
258
261
  - lib/hanami/setup.rb
259
262
  - lib/hanami/slice.rb
@@ -263,6 +266,7 @@ homepage: http://hanamirb.org
263
266
  licenses:
264
267
  - MIT
265
268
  metadata:
269
+ rubygems_mfa_required: 'true'
266
270
  allowed_push_host: https://rubygems.org
267
271
  post_install_message:
268
272
  rdoc_options: []
@@ -272,14 +276,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
272
276
  requirements:
273
277
  - - ">="
274
278
  - !ruby/object:Gem::Version
275
- version: 2.6.0
279
+ version: '3.0'
276
280
  required_rubygems_version: !ruby/object:Gem::Requirement
277
281
  requirements:
278
282
  - - ">"
279
283
  - !ruby/object:Gem::Version
280
284
  version: 1.3.1
281
285
  requirements: []
282
- rubygems_version: 3.2.3
286
+ rubygems_version: 3.3.3
283
287
  signing_key:
284
288
  specification_version: 4
285
289
  summary: The web, with simplicity