hanami 2.0.0.beta1.1 → 2.0.0.beta2

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -0
  3. data/hanami.gemspec +1 -2
  4. data/lib/hanami/app.rb +76 -16
  5. data/lib/hanami/assets/{application_configuration.rb → app_configuration.rb} +1 -1
  6. data/lib/hanami/configuration.rb +20 -20
  7. data/lib/hanami/extensions/action/slice_configured_action.rb +44 -1
  8. data/lib/hanami/extensions/view/slice_configured_view.rb +47 -7
  9. data/lib/hanami/providers/rack.rb +2 -0
  10. data/lib/hanami/providers/settings.rb +81 -6
  11. data/lib/hanami/settings/env_store.rb +32 -0
  12. data/lib/hanami/settings.rb +8 -12
  13. data/lib/hanami/setup.rb +1 -6
  14. data/lib/hanami/slice/routing/middleware/stack.rb +26 -5
  15. data/lib/hanami/slice.rb +38 -45
  16. data/lib/hanami/slice_configurable.rb +14 -1
  17. data/lib/hanami/slice_registrar.rb +65 -5
  18. data/lib/hanami/version.rb +1 -1
  19. data/lib/hanami.rb +53 -2
  20. data/spec/new_integration/action/slice_configuration_spec.rb +287 -0
  21. data/spec/new_integration/code_loading/loading_from_lib_spec.rb +208 -0
  22. data/spec/new_integration/dotenv_loading_spec.rb +137 -0
  23. data/spec/new_integration/settings/access_to_constants_spec.rb +169 -0
  24. data/spec/new_integration/settings/loading_from_env_spec.rb +187 -0
  25. data/spec/new_integration/settings/settings_component_loading_spec.rb +113 -0
  26. data/spec/new_integration/settings/using_types_spec.rb +87 -0
  27. data/spec/new_integration/setup_spec.rb +145 -0
  28. data/spec/new_integration/slices/slice_loading_spec.rb +171 -0
  29. data/spec/new_integration/view/context/settings_spec.rb +5 -1
  30. data/spec/new_integration/view/slice_configuration_spec.rb +289 -0
  31. data/spec/support/app_integration.rb +4 -5
  32. data/spec/unit/hanami/configuration/slices_spec.rb +34 -0
  33. data/spec/unit/hanami/settings/env_store_spec.rb +52 -0
  34. data/spec/unit/hanami/slice_configurable_spec.rb +2 -2
  35. data/spec/unit/hanami/version_spec.rb +1 -1
  36. metadata +30 -28
  37. data/lib/hanami/settings/dotenv_store.rb +0 -58
  38. data/spec/new_integration/action/configuration_spec.rb +0 -26
  39. data/spec/new_integration/settings_spec.rb +0 -115
  40. data/spec/new_integration/view/configuration_spec.rb +0 -49
  41. data/spec/unit/hanami/settings/dotenv_store_spec.rb +0 -119
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/test"
4
+
5
+ RSpec.describe "Slices / Slice loading", :app_integration, :aggregate_failures do
6
+ let(:app_modules) { %i[TestApp Admin Editorial Main Shop] }
7
+
8
+ describe "loading specific slices with config.slices" do
9
+ describe "setup app" do
10
+ it "ignores any explicitly registered slices not included in load_slices" do
11
+ with_tmp_directory(Dir.mktmpdir) do
12
+ write "config/app.rb", <<~'RUBY'
13
+ require "hanami"
14
+
15
+ module TestApp
16
+ class App < Hanami::App
17
+ config.slices = %w[admin]
18
+ end
19
+ end
20
+ RUBY
21
+
22
+ require "hanami/setup"
23
+
24
+ expect { Hanami.app.register_slice :main }.not_to(change { Hanami.app.slices.keys })
25
+ expect { Main }.to raise_error(NameError)
26
+
27
+ expect { Hanami.app.register_slice :admin }.to change { Hanami.app.slices.keys }.to [:admin]
28
+ expect(Admin::Slice).to be
29
+ end
30
+ end
31
+
32
+ describe "nested slices" do
33
+ it "ignores any explicitly registered slices not included in load_slices" do
34
+ with_tmp_directory(Dir.mktmpdir) do
35
+ write "config/app.rb", <<~'RUBY'
36
+ require "hanami"
37
+
38
+ module TestApp
39
+ class App < Hanami::App
40
+ config.slices = %w[admin.shop]
41
+ end
42
+ end
43
+ RUBY
44
+
45
+ require "hanami/setup"
46
+
47
+ expect { Hanami.app.register_slice :admin }.to change { Hanami.app.slices.keys }.to [:admin]
48
+
49
+ expect { Admin::Slice.register_slice :editorial }.not_to(change { Admin::Slice.slices.keys })
50
+ expect { Admin::Slice.register_slice :shop }.to change { Admin::Slice.slices.keys }.to [:shop]
51
+ expect(Shop::Slice).to be
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "prepared app" do
58
+ it "loads only the slices included in load_slices" do
59
+ with_tmp_directory(Dir.mktmpdir) do
60
+ write "config/app.rb", <<~'RUBY'
61
+ require "hanami"
62
+
63
+ module TestApp
64
+ class App < Hanami::App
65
+ config.slices = %w[admin]
66
+ end
67
+ end
68
+ RUBY
69
+
70
+ write "slices/admin/.keep"
71
+ write "slices/main/.keep"
72
+
73
+ require "hanami/prepare"
74
+
75
+ expect(Hanami.app.slices.keys).to eq [:admin]
76
+ expect(Admin::Slice).to be
77
+ expect { Main }.to raise_error(NameError)
78
+ end
79
+ end
80
+
81
+ it "ignores unknown slices in load_slices" do
82
+ with_tmp_directory(Dir.mktmpdir) do
83
+ write "config/app.rb", <<~'RUBY'
84
+ require "hanami"
85
+
86
+ module TestApp
87
+ class App < Hanami::App
88
+ config.slices = %w[admin meep morp]
89
+ end
90
+ end
91
+ RUBY
92
+
93
+ write "slices/admin/.keep"
94
+ write "slices/main/.keep"
95
+
96
+ require "hanami/prepare"
97
+
98
+ expect(Hanami.app.slices.keys).to eq [:admin]
99
+ end
100
+ end
101
+
102
+ describe "nested slices" do
103
+ it "loads only the dot-delimited nested slices (and their parents) included in load_slices" do
104
+ with_tmp_directory(Dir.mktmpdir) do
105
+ write "config/app.rb", <<~'RUBY'
106
+ require "hanami"
107
+
108
+ module TestApp
109
+ class App < Hanami::App
110
+ config.slices = %w[admin.shop]
111
+ end
112
+ end
113
+ RUBY
114
+
115
+ write "slices/admin/.keep"
116
+ write "slices/admin/slices/shop/.keep"
117
+ write "slices/admin/slices/editorial/.keep"
118
+ write "slices/main/.keep"
119
+
120
+ require "hanami/prepare"
121
+
122
+ expect(Hanami.app.slices.keys).to eq [:admin]
123
+ expect(Admin::Slice.slices.keys).to eq [:shop]
124
+
125
+ expect(Admin::Slice).to be
126
+ expect(Shop::Slice).to be
127
+
128
+ expect { Editorial }.to raise_error(NameError)
129
+ expect { Main }.to raise_error(NameError)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ describe "using ENV vars" do
137
+ before do
138
+ @orig_env = ENV.to_h
139
+ end
140
+
141
+ after do
142
+ ENV.replace(@orig_env)
143
+ end
144
+
145
+ it "uses HANAMI_SLICES" do
146
+ ENV["HANAMI_SLICES"] = "admin"
147
+
148
+ with_tmp_directory(Dir.mktmpdir) do
149
+ write "config/app.rb", <<~'RUBY'
150
+ require "hanami"
151
+
152
+ module TestApp
153
+ class App < Hanami::App
154
+ end
155
+ end
156
+ RUBY
157
+
158
+ write "slices/admin/.keep"
159
+ write "slices/main/.keep"
160
+
161
+ require "hanami/setup"
162
+
163
+ expect(Hanami.app.config.slices).to eq %w[admin]
164
+
165
+ require "hanami/prepare"
166
+
167
+ expect(Hanami.app.slices.keys).to eq [:admin]
168
+ end
169
+ end
170
+ end
171
+ end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami"
4
+ require "hanami/settings"
4
5
 
5
6
  RSpec.describe "App view / Context / Settings", :app_integration do
6
7
  before do
7
8
  module TestApp
8
9
  class App < Hanami::App
9
10
  end
11
+
12
+ class Settings < Hanami::Settings
13
+ end
10
14
  end
11
15
 
12
16
  Hanami.prepare
@@ -24,7 +28,7 @@ RSpec.describe "App view / Context / Settings", :app_integration do
24
28
 
25
29
  describe "#settings" do
26
30
  it "is the app settings by default" do
27
- expect(context.settings).to be TestApp::App.settings
31
+ expect(context.settings).to be TestApp::App["settings"]
28
32
  end
29
33
 
30
34
  context "injected settings" do
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App view / Slice configuration", :app_integration do
4
+ before do
5
+ with_directory(@dir = make_tmp_directory) do
6
+ write "config/app.rb", <<~'RUBY'
7
+ require "hanami"
8
+
9
+ module TestApp
10
+ class App < Hanami::App
11
+ end
12
+ end
13
+ RUBY
14
+
15
+ write "app/view.rb", <<~'RUBY'
16
+ require "hanami/view"
17
+
18
+ module TestApp
19
+ class View < Hanami::View
20
+ end
21
+ end
22
+ RUBY
23
+
24
+ require "hanami/setup"
25
+ end
26
+ end
27
+
28
+ def prepare_app
29
+ with_directory(@dir) { require "hanami/prepare" }
30
+ end
31
+
32
+ describe "inheriting from app-level base class" do
33
+ describe "app-level base class" do
34
+ it "applies default views config from the app", :aggregate_failures do
35
+ prepare_app
36
+
37
+ expect(TestApp::View.config.layouts_dir).to eq "layouts"
38
+ expect(TestApp::View.config.layout).to eq "app"
39
+ end
40
+
41
+ it "applies views config from the app" do
42
+ Hanami.app.config.views.layout = "app_layout"
43
+
44
+ prepare_app
45
+
46
+ expect(TestApp::View.config.layout).to eq "app_layout"
47
+ end
48
+
49
+ it "does not override config in the base class" do
50
+ Hanami.app.config.views.layout = "app_layout"
51
+
52
+ prepare_app
53
+
54
+ TestApp::View.config.layout = "custom_layout"
55
+
56
+ expect(TestApp::View.config.layout).to eq "custom_layout"
57
+ end
58
+ end
59
+
60
+ describe "subclass in app" do
61
+ before do
62
+ with_directory(@dir) do
63
+ write "app/views/articles/index.rb", <<~'RUBY'
64
+ module TestApp
65
+ module Views
66
+ module Articles
67
+ class Index < TestApp::View
68
+ end
69
+ end
70
+ end
71
+ end
72
+ RUBY
73
+ end
74
+ end
75
+
76
+ it "applies default views config from the app", :aggregate_failures do
77
+ prepare_app
78
+
79
+ expect(TestApp::Views::Articles::Index.config.layouts_dir).to eq "layouts"
80
+ expect(TestApp::Views::Articles::Index.config.layout).to eq "app"
81
+ end
82
+
83
+ it "applies views config from the app" do
84
+ Hanami.app.config.views.layout = "app_layout"
85
+
86
+ prepare_app
87
+
88
+ expect(TestApp::Views::Articles::Index.config.layout).to eq "app_layout"
89
+ end
90
+
91
+ it "applies config from the base class" do
92
+ prepare_app
93
+
94
+ TestApp::View.config.layout = "base_class_layout"
95
+
96
+ expect(TestApp::Views::Articles::Index.config.layout).to eq "base_class_layout"
97
+ end
98
+ end
99
+
100
+ describe "subclass in slice" do
101
+ before do
102
+ with_directory(@dir) do
103
+ write "slices/admin/views/articles/index.rb", <<~'RUBY'
104
+ module Admin
105
+ module Views
106
+ module Articles
107
+ class Index < TestApp::View
108
+ end
109
+ end
110
+ end
111
+ end
112
+ RUBY
113
+ end
114
+ end
115
+
116
+ it "applies default views config from the app", :aggregate_failures do
117
+ prepare_app
118
+
119
+ expect(Admin::Views::Articles::Index.config.layouts_dir).to eq "layouts"
120
+ expect(Admin::Views::Articles::Index.config.layout).to eq "app"
121
+ end
122
+
123
+ it "applies views config from the app" do
124
+ Hanami.app.config.views.layout = "app_layout"
125
+
126
+ prepare_app
127
+
128
+ expect(Admin::Views::Articles::Index.config.layout).to eq "app_layout"
129
+ end
130
+
131
+ it "applies config from the base class" do
132
+ prepare_app
133
+
134
+ TestApp::View.config.layout = "base_class_layout"
135
+
136
+ expect(Admin::Views::Articles::Index.config.layout).to eq "base_class_layout"
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "inheriting from a slice-level base class, in turn inheriting from an app-level base class" do
142
+ before do
143
+ with_directory(@dir) do
144
+ write "slices/admin/view.rb", <<~'RUBY'
145
+ module Admin
146
+ class View < TestApp::View
147
+ end
148
+ end
149
+ RUBY
150
+ end
151
+ end
152
+
153
+ describe "slice-level base class" do
154
+ it "applies default views config from the app", :aggregate_failures do
155
+ prepare_app
156
+
157
+ expect(Admin::View.config.layouts_dir).to eq "layouts"
158
+ expect(Admin::View.config.layout).to eq "app"
159
+ end
160
+
161
+ it "applies views config from the app" do
162
+ Hanami.app.config.views.layout = "app_layout"
163
+
164
+ prepare_app
165
+
166
+ expect(Admin::View.config.layout).to eq "app_layout"
167
+ end
168
+
169
+ it "applies config from the app base class" do
170
+ prepare_app
171
+
172
+ TestApp::View.config.layout = "app_base_class_layout"
173
+
174
+ expect(Admin::View.config.layout).to eq "app_base_class_layout"
175
+ end
176
+
177
+ context "slice views config present" do
178
+ before do
179
+ with_directory(@dir) do
180
+ write "config/slices/admin.rb", <<~'RUBY'
181
+ module Admin
182
+ class Slice < Hanami::Slice
183
+ config.views.layout = "slice_layout"
184
+ end
185
+ end
186
+ RUBY
187
+ end
188
+ end
189
+
190
+ it "applies views config from the slice" do
191
+ prepare_app
192
+
193
+ expect(Admin::View.config.layout).to eq "slice_layout"
194
+ end
195
+
196
+ it "prefers views config from the slice over config from the app-level base class" do
197
+ prepare_app
198
+
199
+ TestApp::View.config.layout = "app_base_class_layout"
200
+
201
+ expect(Admin::View.config.layout).to eq "slice_layout"
202
+ end
203
+
204
+ it "prefers config from the base class over views config from the slice" do
205
+ prepare_app
206
+
207
+ TestApp::View.config.layout = "app_base_class_layout"
208
+ Admin::View.config.layout = "slice_base_class_layout"
209
+
210
+ expect(Admin::View.config.layout).to eq "slice_base_class_layout"
211
+ end
212
+ end
213
+ end
214
+
215
+ describe "subclass in slice" do
216
+ before do
217
+ with_directory(@dir) do
218
+ write "slices/admin/views/articles/index.rb", <<~'RUBY'
219
+ module Admin
220
+ module Views
221
+ module Articles
222
+ class Index < Admin::View
223
+ end
224
+ end
225
+ end
226
+ end
227
+ RUBY
228
+ end
229
+ end
230
+
231
+ it "applies default views config from the app", :aggregate_failures do
232
+ prepare_app
233
+
234
+ expect(Admin::Views::Articles::Index.config.layouts_dir).to eq "layouts"
235
+ expect(Admin::Views::Articles::Index.config.layout).to eq "app"
236
+ end
237
+
238
+ it "applies views config from the app" do
239
+ Hanami.app.config.views.layout = "app_layout"
240
+
241
+ prepare_app
242
+
243
+ expect(Admin::Views::Articles::Index.config.layout).to eq "app_layout"
244
+ end
245
+
246
+ it "applies views config from the slice" do
247
+ with_directory(@dir) do
248
+ write "config/slices/admin.rb", <<~'RUBY'
249
+ module Admin
250
+ class Slice < Hanami::Slice
251
+ config.views.layout = "slice_layout"
252
+ end
253
+ end
254
+ RUBY
255
+ end
256
+
257
+ prepare_app
258
+
259
+ expect(Admin::Views::Articles::Index.config.layout).to eq "slice_layout"
260
+ end
261
+
262
+ it "applies config from the slice base class" do
263
+ prepare_app
264
+
265
+ Admin::View.config.layout = "slice_base_class_layout"
266
+
267
+ expect(Admin::Views::Articles::Index.config.layout).to eq "slice_base_class_layout"
268
+ end
269
+
270
+ it "prefers config from the slice base class over views config from the slice" do
271
+ with_directory(@dir) do
272
+ write "config/slices/admin.rb", <<~'RUBY'
273
+ module Admin
274
+ class Slice < Hanami::Slice
275
+ config.views.layout = "slice_layout"
276
+ end
277
+ end
278
+ RUBY
279
+ end
280
+
281
+ prepare_app
282
+
283
+ Admin::View.config.layout = "slice_base_class_layout"
284
+
285
+ expect(Admin::Views::Articles::Index.config.layout).to eq "slice_base_class_layout"
286
+ end
287
+ end
288
+ end
289
+ end
@@ -20,7 +20,7 @@ module RSpec
20
20
  end
21
21
 
22
22
  RSpec.shared_context "Application integration" do
23
- let(:application_modules) { %i[TestApp Admin Main Search] }
23
+ let(:app_modules) { %i[TestApp Admin Main Search] }
24
24
  end
25
25
 
26
26
  RSpec.configure do |config|
@@ -52,9 +52,8 @@ RSpec.configure do |config|
52
52
  Zeitwerk::ExplicitNamespace.cpaths.clear
53
53
  Zeitwerk::ExplicitNamespace.tracer.disable
54
54
 
55
- if Hanami.instance_variable_defined?(:@_app)
56
- Hanami.remove_instance_variable(:@_app)
57
- end
55
+ Hanami.instance_variable_set(:@_bundled, {})
56
+ Hanami.remove_instance_variable(:@_app) if Hanami.instance_variable_defined?(:@_app)
58
57
 
59
58
  $LOAD_PATH.replace(@load_paths)
60
59
 
@@ -68,7 +67,7 @@ RSpec.configure do |config|
68
67
  }
69
68
  $LOADED_FEATURES.replace(@loaded_features + new_features_to_keep)
70
69
 
71
- application_modules.each do |app_module_name|
70
+ app_modules.each do |app_module_name|
72
71
  next unless Object.const_defined?(app_module_name)
73
72
 
74
73
  Object.const_get(app_module_name).tap do |mod|
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+ require "hanami/configuration"
5
+ require "hanami/slice_name"
6
+
7
+ RSpec.describe Hanami::Configuration, "#slices" do
8
+ subject(:config) { described_class.new(app_name: app_name, env: :development) }
9
+ let(:app_name) { Hanami::SliceName.new(double(name: "MyApp::App"), inflector: Dry::Inflector.new) }
10
+
11
+ subject(:slices) { config.slices }
12
+
13
+ before do
14
+ @orig_env = ENV.to_h
15
+ end
16
+
17
+ after do
18
+ ENV.replace(@orig_env)
19
+ end
20
+
21
+ it "is nil by default" do
22
+ is_expected.to be nil
23
+ end
24
+
25
+ it "defaults to the HANAMI_LOAD_SLICES env var, separated by commas" do
26
+ ENV["HANAMI_SLICES"] = "main,admin"
27
+ is_expected.to eq %w[main admin]
28
+ end
29
+
30
+ it "strips spaces from HANAMI_LOAD_SLICES env var entries" do
31
+ ENV["HANAMI_SLICES"] = "main, admin"
32
+ is_expected.to eq %w[main admin]
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/settings/env_store"
4
+
5
+ RSpec.describe Hanami::Settings::EnvStore do
6
+ it "defaults to using ENV as the store" do
7
+ orig_env = ENV.to_h
8
+
9
+ ENV["FOO"] = "bar"
10
+ expect(described_class.new.fetch("FOO")).to eq "bar"
11
+
12
+ ENV.replace(orig_env)
13
+ end
14
+
15
+ describe "#fetch" do
16
+ it "fetches from ENV" do
17
+ store = described_class.new(store: {"FOO" => "bar"})
18
+
19
+ expect(store.fetch("FOO")).to eq("bar")
20
+ end
21
+
22
+ it "capitalizes name" do
23
+ store = described_class.new(store: {"FOO" => "bar"})
24
+
25
+ expect(store.fetch("foo")).to eq("bar")
26
+ end
27
+
28
+ it "coerces name to string" do
29
+ store = described_class.new(store: {"FOO" => "bar"})
30
+
31
+ expect(store.fetch(:foo)).to eq("bar")
32
+ end
33
+
34
+ it "returns default when value is not found" do
35
+ store = described_class.new(store: {"FOO" => "bar"})
36
+
37
+ expect(store.fetch("BAZ", "qux")).to eq("qux")
38
+ end
39
+
40
+ it "returns the block execution when value is not found" do
41
+ store = described_class.new(store: {"FOO" => "bar"})
42
+
43
+ expect(store.fetch("BAZ") { "qux" }).to eq("qux") # rubocop:disable Style/RedundantFetchBlock
44
+ end
45
+
46
+ it "raises KeyError when value is not found and no default is given" do
47
+ store = described_class.new(store: {"FOO" => "bar"})
48
+
49
+ expect { store.fetch("BAZ") }.to raise_error(KeyError)
50
+ end
51
+ end
52
+ end
@@ -54,8 +54,8 @@ RSpec.describe Hanami::SliceConfigurable, :app_integration do
54
54
 
55
55
  subject(:subclass) { Main::MySubSubclass }
56
56
 
57
- it "calls `configure_for_slice` again for the same slice" do
58
- expect(subclass.traces).to eq [Main::Slice, Main::Slice]
57
+ it "does not call `configure_for_slice` again" do
58
+ expect(subclass.traces).to eq [Main::Slice]
59
59
  end
60
60
  end
61
61
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  RSpec.describe "Hanami::VERSION" do
4
4
  it "returns current version" do
5
- expect(Hanami::VERSION).to eq("2.0.0.beta1.1")
5
+ expect(Hanami::VERSION).to eq("2.0.0.beta2")
6
6
  end
7
7
  end