hanami 2.0.0.alpha6 → 2.0.0.alpha8

Sign up to get free protection for your applications and to get access to all the features.
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