hanami 2.0.0.alpha6 → 2.0.0.alpha8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/hanami/slice.rb CHANGED
@@ -1,130 +1,187 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/system/container"
4
+ require "hanami/errors"
4
5
  require "pathname"
6
+ require_relative "constants"
7
+ require_relative "slice_name"
5
8
 
6
9
  module Hanami
7
10
  # Distinct area of concern within an Hanami application
8
11
  #
9
12
  # @since 2.0.0
10
13
  class Slice
11
- attr_reader :application, :name, :namespace, :root
12
-
13
- def initialize(application, name:, namespace: nil, root: nil, container: nil)
14
- @application = application
15
- @name = name.to_sym
16
- @namespace = namespace
17
- @root = root ? Pathname(root) : root
18
- @container = container
19
- end
14
+ def self.inherited(subclass)
15
+ super
20
16
 
21
- def inflector
22
- application.inflector
23
- end
17
+ subclass.extend(ClassMethods)
24
18
 
25
- def namespace_path
26
- @namespace_path ||= inflector.underscore(namespace.to_s)
19
+ # Eagerly initialize any variables that may be accessed inside the subclass body
20
+ subclass.instance_variable_set(:@application, Hanami.application)
21
+ subclass.instance_variable_set(:@container, Class.new(Dry::System::Container))
27
22
  end
28
23
 
29
- def prepare(provider_name = nil)
30
- if provider_name
31
- container.prepare(provider_name)
32
- return self
24
+ # rubocop:disable Metrics/ModuleLength
25
+ module ClassMethods
26
+ attr_reader :application, :container
27
+
28
+ def slice_name
29
+ @slice_name ||= SliceName.new(self, inflector: method(:inflector))
33
30
  end
34
31
 
35
- @container ||= define_container
32
+ def namespace
33
+ slice_name.namespace
34
+ end
36
35
 
37
- container.import from: application.container, as: :application
36
+ def root
37
+ application.root.join(SLICES_DIR, slice_name.to_s)
38
+ end
38
39
 
39
- slice_block = application.configuration.slices[name]
40
- instance_eval(&slice_block) if slice_block
40
+ def inflector
41
+ application.inflector
42
+ end
41
43
 
42
- # This is here and not inside define_container to allow for the slice block to
43
- # interact with container config
44
- container.configured!
44
+ def prepare(provider_name = nil)
45
+ container.prepare(provider_name) and return self if provider_name
45
46
 
46
- self
47
- end
47
+ return self if prepared?
48
48
 
49
- def boot
50
- container.finalize!
49
+ ensure_slice_name
50
+ ensure_slice_consts
51
51
 
52
- @booted = true
53
- self
54
- end
52
+ prepare_all
55
53
 
56
- # rubocop:disable Style/DoubleNegation
57
- def booted?
58
- !!@booted
59
- end
60
- # rubocop:enable Style/DoubleNegation
54
+ @prepared = true
55
+ self
56
+ end
61
57
 
62
- def container
63
- @container ||= define_container
64
- end
58
+ def prepare_container(&block)
59
+ @prepare_container_block = block
60
+ end
65
61
 
66
- def import(from:, **kwargs)
67
- # TODO: This should be handled via dry-system (see dry-rb/dry-system#228)
68
- raise "Cannot import after booting" if booted?
62
+ def boot
63
+ return self if booted?
69
64
 
70
- if from.is_a?(Symbol) || from.is_a?(String)
71
- slice_name = from
72
- # TODO: better error than the KeyError from fetch if the slice doesn't exist
73
- from = application.slices.fetch(from.to_sym).container
65
+ container.finalize!
66
+
67
+ @booted = true
68
+
69
+ self
74
70
  end
75
71
 
76
- as = kwargs[:as] || slice_name
72
+ def shutdown
73
+ container.shutdown!
74
+ self
75
+ end
77
76
 
78
- container.import(from: from, as: as, **kwargs)
79
- end
77
+ def prepared?
78
+ !!@prepared
79
+ end
80
80
 
81
- def register(*args, &block)
82
- container.register(*args, &block)
83
- end
81
+ def booted?
82
+ !!@booted
83
+ end
84
84
 
85
- def register_provider(...)
86
- container.register_provider(...)
87
- end
85
+ def register(...)
86
+ container.register(...)
87
+ end
88
88
 
89
- def start(...)
90
- container.start(...)
91
- end
89
+ def register_provider(...)
90
+ container.register_provider(...)
91
+ end
92
92
 
93
- def key?(...)
94
- container.key?(...)
95
- end
93
+ def start(...)
94
+ container.start(...)
95
+ end
96
96
 
97
- def keys
98
- container.keys
99
- end
97
+ def key?(...)
98
+ container.key?(...)
99
+ end
100
100
 
101
- def [](...)
102
- container.[](...)
103
- end
101
+ def keys
102
+ container.keys
103
+ end
104
104
 
105
- def resolve(...)
106
- container.resolve(...)
107
- end
105
+ def [](...)
106
+ container.[](...)
107
+ end
108
108
 
109
- private
109
+ def resolve(...)
110
+ container.resolve(...)
111
+ end
112
+
113
+ def export(keys)
114
+ container.config.exports = keys
115
+ end
116
+
117
+ def import(from:, **kwargs)
118
+ # TODO: This should be handled via dry-system (see dry-rb/dry-system#228)
119
+ raise "Cannot import after booting" if booted?
120
+
121
+ application = self.application
122
+
123
+ container.after(:configure) do
124
+ if from.is_a?(Symbol) || from.is_a?(String)
125
+ slice_name = from
126
+ from = application.slices[from.to_sym].container
127
+ end
128
+
129
+ as = kwargs[:as] || slice_name
110
130
 
111
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
112
- def define_container
113
- container = Class.new(Dry::System::Container)
131
+ import(from: from, as: as, **kwargs)
132
+ end
133
+ end
114
134
 
115
- container.use :env
116
- container.use :zeitwerk,
117
- loader: application.autoloader,
118
- run_setup: false,
119
- eager_load: false
135
+ private
136
+
137
+ def ensure_slice_name
138
+ unless name
139
+ raise SliceLoadError, "Slice must have a class name before it can be prepared"
140
+ end
141
+ end
120
142
 
121
- container.config.name = name
122
- container.config.env = application.configuration.env
123
- container.config.inflector = application.configuration.inflector
143
+ def ensure_slice_consts
144
+ if namespace.const_defined?(:Container) || namespace.const_defined?(:Deps)
145
+ raise(
146
+ SliceLoadError,
147
+ "#{namespace}::Container and #{namespace}::Deps constants must not already be defined"
148
+ )
149
+ end
150
+ end
124
151
 
125
- if root&.directory?
152
+ def prepare_all
153
+ prepare_container_plugins
154
+ prepare_container_base_config
155
+ prepare_container_component_dirs
156
+ prepare_autoloader
157
+ prepare_container_imports
158
+ prepare_container_consts
159
+ instance_exec(container, &@prepare_container_block) if @prepare_container_block
160
+ container.configured!
161
+ end
162
+
163
+ def prepare_container_plugins
164
+ container.use(:env, inferrer: -> { Hanami.env })
165
+
166
+ container.use(
167
+ :zeitwerk,
168
+ loader: application.autoloader,
169
+ run_setup: false,
170
+ eager_load: false
171
+ )
172
+ end
173
+
174
+ def prepare_container_base_config
175
+ container.config.name = slice_name.to_sym
126
176
  container.config.root = root
127
- container.config.provider_dirs = ["config/providers"]
177
+ container.config.provider_dirs = [File.join("config", "providers")]
178
+
179
+ container.config.env = application.configuration.env
180
+ container.config.inflector = application.configuration.inflector
181
+ end
182
+
183
+ def prepare_container_component_dirs # rubocop:disable Metrics/AbcSize
184
+ return unless root&.directory?
128
185
 
129
186
  # Add component dirs for each configured component path
130
187
  application.configuration.source_dirs.component_dirs.each do |component_dir|
@@ -132,17 +189,14 @@ module Hanami
132
189
 
133
190
  component_dir = component_dir.dup
134
191
 
135
- # TODO: this `== "lib"` check should be codified into a method somewhere
136
- if component_dir.path == "lib"
192
+ if component_dir.path == LIB_DIR
137
193
  # Expect component files in the root of the lib/ component dir to define
138
194
  # classes inside the slice's namespace.
139
195
  #
140
196
  # e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
141
197
  # "foo"
142
198
  component_dir.namespaces.delete_root
143
- component_dir.namespaces.add_root(key: nil, const: namespace_path)
144
-
145
- container.config.component_dirs.add(component_dir)
199
+ component_dir.namespaces.add_root(key: nil, const: slice_name.name)
146
200
  else
147
201
  # Expect component files in the root of non-lib/ component dirs to define
148
202
  # classes inside a namespace matching that dir.
@@ -150,22 +204,24 @@ module Hanami
150
204
  # e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
151
205
  # registered as "actions.foo"
152
206
 
153
- dir_namespace_path = File.join(namespace_path, component_dir.path)
207
+ dir_namespace_path = File.join(slice_name.name, component_dir.path)
154
208
 
155
209
  component_dir.namespaces.delete_root
156
210
  component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path)
157
-
158
- container.config.component_dirs.add(component_dir)
159
211
  end
212
+
213
+ container.config.component_dirs.add(component_dir)
160
214
  end
161
215
  end
162
216
 
163
- if root&.directory?
217
+ def prepare_autoloader # rubocop:disable Metrics/AbcSize
218
+ return unless root&.directory?
219
+
164
220
  # Pass configured autoload dirs to the autoloader
165
221
  application.configuration.source_dirs.autoload_paths.each do |autoload_path|
166
222
  next unless root.join(autoload_path).directory?
167
223
 
168
- dir_namespace_path = File.join(namespace_path, autoload_path)
224
+ dir_namespace_path = File.join(slice_name.name, autoload_path)
169
225
 
170
226
  autoloader_namespace = begin
171
227
  inflector.constantize(inflector.camelize(dir_namespace_path))
@@ -180,13 +236,15 @@ module Hanami
180
236
  end
181
237
  end
182
238
 
183
- if namespace
239
+ def prepare_container_imports
240
+ container.import from: application.container, as: :application
241
+ end
242
+
243
+ def prepare_container_consts
184
244
  namespace.const_set :Container, container
185
245
  namespace.const_set :Deps, container.injector
186
246
  end
187
-
188
- container
189
247
  end
190
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
248
+ # rubocop:enable Metrics/ModuleLength
191
249
  end
192
250
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors"
4
+
5
+ module Hanami
6
+ # Calls `configure_for_slice(slice)` on the extended class whenever it is first
7
+ # subclassed within a module namespace corresponding to a slice.
8
+ #
9
+ # @example
10
+ # class BaseClass
11
+ # extend Hanami::SliceConfigurable
12
+ # end
13
+ #
14
+ # # slices/main/lib/my_class.rb
15
+ # module Main
16
+ # class MyClass < BaseClass
17
+ # # Will be called with `Main::Slice`
18
+ # def self.configure_for_slice(slice)
19
+ # # ...
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ # @api private
25
+ # @since 2.0.0
26
+ module SliceConfigurable
27
+ class << self
28
+ def extended(klass)
29
+ slice_for = method(:slice_for)
30
+
31
+ inherited_mod = Module.new do
32
+ define_method(:inherited) do |subclass|
33
+ unless Hanami.application?
34
+ raise ComponentLoadError, "Class #{klass} must be defined within an Hanami application"
35
+ end
36
+
37
+ super(subclass)
38
+
39
+ subclass.instance_variable_set(:@configured_for_slices, configured_for_slices.dup)
40
+
41
+ slice = slice_for.(subclass)
42
+ return unless slice
43
+
44
+ unless subclass.configured_for_slice?(slice)
45
+ subclass.configure_for_slice(slice)
46
+ subclass.configured_for_slices << slice
47
+ end
48
+ end
49
+ end
50
+
51
+ klass.singleton_class.prepend(inherited_mod)
52
+ end
53
+
54
+ private
55
+
56
+ def slice_for(klass)
57
+ return unless klass.name
58
+
59
+ slices = Hanami.application.slices.to_a + [Hanami.application]
60
+
61
+ slices.detect { |slice| klass.name.include?(slice.namespace.to_s) }
62
+ end
63
+ end
64
+
65
+ def configure_for_slice(slice); end
66
+
67
+ def configured_for_slice?(slice)
68
+ configured_for_slices.include?(slice)
69
+ end
70
+
71
+ def configured_for_slices
72
+ @configured_for_slices ||= []
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+
5
+ module Hanami
6
+ # Represents the name of an {Application} or {Slice}.
7
+ #
8
+ # @see Application::ClassMethods#application_name
9
+ # @see Slice::ClassMethods#slice_name
10
+ #
11
+ # @api public
12
+ # @since 2.0.0
13
+ class SliceName
14
+ # Returns a new SliceName for the slice or application.
15
+ #
16
+ # You must provide an inflector for the manipulation of the name into various formats.
17
+ # This should be given in the form of a Proc that returns the inflector when called.
18
+ # The reason for this is that the inflector may be replaced by the user during the
19
+ # application configuration phase, so the proc should ensure that the current instance
20
+ # of the inflector is returned whenever needed.
21
+ #
22
+ # @param slice [#name] the slice or application object
23
+ # @param inflector [Proc] Proc returning the application's inflector when called
24
+ #
25
+ # @api private
26
+ def initialize(slice, inflector:)
27
+ @slice = slice
28
+ @inflector = inflector
29
+ end
30
+
31
+ # Returns the name of the slice as a downcased, underscored string.
32
+ #
33
+ # This is considered the canonical name of the slice.
34
+ #
35
+ # @example
36
+ # slice_name.name # => "main"
37
+ #
38
+ # @return [String] the slice name
39
+ #
40
+ # @api public
41
+ # @since 2.0.0
42
+ def name
43
+ inflector.underscore(namespace_name)
44
+ end
45
+
46
+ # @api public
47
+ # @since 2.0.0
48
+ alias_method :path, :name
49
+
50
+ # Returns the name of the slice's module namespace.
51
+ #
52
+ # @example
53
+ # slice_name.namespace_name # => "Main"
54
+ #
55
+ # @return [String] the namespace name
56
+ #
57
+ # @api public
58
+ # @since 2.0.0
59
+ def namespace_name
60
+ slice_name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER)
61
+ end
62
+
63
+ # Returns the constant for the slice's module namespace.
64
+ #
65
+ # @example
66
+ # slice_name.namespace_const # => Main
67
+ #
68
+ # @return [Module] the namespace module constant
69
+ #
70
+ # @api public
71
+ # @since 2.0.0
72
+ def namespace_const
73
+ inflector.constantize(namespace_name)
74
+ end
75
+
76
+ # @api public
77
+ # @since 2.0.0
78
+ alias_method :namespace, :namespace_const
79
+
80
+ # @api public
81
+ # @since 2.0.0
82
+ alias_method :to_s, :name
83
+
84
+ # Returns the name of a slice as a downcased, underscored symbol.
85
+ #
86
+ # @example
87
+ # slice_name.name # => :main
88
+ #
89
+ # @return [Symbol] the slice name
90
+ #
91
+ # @see name, to_s
92
+ #
93
+ # @api public
94
+ # @since 2.0.0
95
+ def to_sym
96
+ name.to_sym
97
+ end
98
+
99
+ private
100
+
101
+ def slice_name
102
+ @slice.name
103
+ end
104
+
105
+ # The inflector is callable to allow for it to be configured/replaced after this
106
+ # object has been initialized
107
+ def inflector
108
+ @inflector.()
109
+ end
110
+ end
111
+ 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.alpha6"
11
+ VERSION = "2.0.0.alpha8"
12
12
 
13
13
  # @since 0.9.0
14
14
  # @api private
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
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.alpha6
4
+ version: 2.0.0.alpha8
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-02-10 00:00:00.000000000 Z
11
+ date: 2022-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -217,6 +217,8 @@ files:
217
217
  - hanami.gemspec
218
218
  - lib/hanami.rb
219
219
  - lib/hanami/application.rb
220
+ - lib/hanami/application/action.rb
221
+ - lib/hanami/application/action/slice_configured_action.rb
220
222
  - lib/hanami/application/container/providers/inflector.rb
221
223
  - lib/hanami/application/container/providers/logger.rb
222
224
  - lib/hanami/application/container/providers/rack_logger.rb
@@ -230,8 +232,15 @@ files:
230
232
  - lib/hanami/application/routing/resolver.rb
231
233
  - lib/hanami/application/routing/resolver/node.rb
232
234
  - lib/hanami/application/routing/resolver/trie.rb
235
+ - lib/hanami/application/routing/router.rb
233
236
  - lib/hanami/application/settings.rb
234
237
  - lib/hanami/application/settings/dotenv_store.rb
238
+ - lib/hanami/application/slice_registrar.rb
239
+ - lib/hanami/application/view.rb
240
+ - lib/hanami/application/view/context.rb
241
+ - lib/hanami/application/view/slice_configured_context.rb
242
+ - lib/hanami/application/view/slice_configured_view.rb
243
+ - lib/hanami/application/view_name_inferrer.rb
235
244
  - lib/hanami/assets/application_configuration.rb
236
245
  - lib/hanami/assets/configuration.rb
237
246
  - lib/hanami/boot.rb
@@ -244,16 +253,25 @@ files:
244
253
  - lib/hanami/cli/commands/command.rb
245
254
  - lib/hanami/cli/commands/server.rb
246
255
  - lib/hanami/configuration.rb
256
+ - lib/hanami/configuration/actions.rb
257
+ - lib/hanami/configuration/actions/content_security_policy.rb
258
+ - lib/hanami/configuration/actions/cookies.rb
259
+ - lib/hanami/configuration/actions/sessions.rb
247
260
  - lib/hanami/configuration/logger.rb
248
261
  - lib/hanami/configuration/middleware.rb
249
262
  - lib/hanami/configuration/null_configuration.rb
250
263
  - lib/hanami/configuration/router.rb
251
264
  - lib/hanami/configuration/sessions.rb
252
265
  - lib/hanami/configuration/source_dirs.rb
266
+ - lib/hanami/configuration/views.rb
267
+ - lib/hanami/constants.rb
268
+ - lib/hanami/errors.rb
253
269
  - lib/hanami/prepare.rb
254
270
  - lib/hanami/server.rb
255
271
  - lib/hanami/setup.rb
256
272
  - lib/hanami/slice.rb
273
+ - lib/hanami/slice_configurable.rb
274
+ - lib/hanami/slice_name.rb
257
275
  - lib/hanami/version.rb
258
276
  - lib/hanami/web/rack_logger.rb
259
277
  homepage: http://hanamirb.org
@@ -277,7 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
277
295
  - !ruby/object:Gem::Version
278
296
  version: 1.3.1
279
297
  requirements: []
280
- rubygems_version: 3.3.3
298
+ rubygems_version: 3.3.7
281
299
  signing_key:
282
300
  specification_version: 4
283
301
  summary: The web, with simplicity