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,287 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App action / 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/action.rb", <<~'RUBY'
16
+ require "hanami/action"
17
+
18
+ module TestApp
19
+ class Action < Hanami::Action
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 actions config from the app", :aggregate_failures do
35
+ prepare_app
36
+
37
+ expect(TestApp::Action.config.default_request_format).to eq :html
38
+ expect(TestApp::Action.config.default_response_format).to eq :html
39
+ end
40
+
41
+ it "applies actions config from the app" do
42
+ Hanami.app.config.actions.default_response_format = :json
43
+
44
+ prepare_app
45
+
46
+ expect(TestApp::Action.config.default_response_format).to eq :json
47
+ end
48
+
49
+ it "does not override config in the base class" do
50
+ Hanami.app.config.actions.default_response_format = :csv
51
+
52
+ prepare_app
53
+
54
+ TestApp::Action.config.default_response_format = :json
55
+ end
56
+ end
57
+
58
+ describe "subclass in app" do
59
+ before do
60
+ with_directory(@dir) do
61
+ write "app/actions/articles/index.rb", <<~'RUBY'
62
+ module TestApp
63
+ module Actions
64
+ module Articles
65
+ class Index < TestApp::Action
66
+ end
67
+ end
68
+ end
69
+ end
70
+ RUBY
71
+ end
72
+ end
73
+
74
+ it "applies default actions config from the app", :aggregate_failures do
75
+ prepare_app
76
+
77
+ expect(TestApp::Actions::Articles::Index.config.default_request_format).to eq :html
78
+ expect(TestApp::Actions::Articles::Index.config.default_response_format).to eq :html
79
+ end
80
+
81
+ it "applies actions config from the app" do
82
+ Hanami.app.config.actions.default_response_format = :json
83
+
84
+ prepare_app
85
+
86
+ expect(TestApp::Actions::Articles::Index.config.default_response_format).to eq :json
87
+ end
88
+
89
+ it "applies config from the base class" do
90
+ prepare_app
91
+
92
+ TestApp::Action.config.default_response_format = :json
93
+
94
+ expect(TestApp::Actions::Articles::Index.config.default_response_format).to eq :json
95
+ end
96
+ end
97
+
98
+ describe "subclass in slice" do
99
+ before do
100
+ with_directory(@dir) do
101
+ write "slices/admin/actions/articles/index.rb", <<~'RUBY'
102
+ module Admin
103
+ module Actions
104
+ module Articles
105
+ class Index < TestApp::Action
106
+ end
107
+ end
108
+ end
109
+ end
110
+ RUBY
111
+ end
112
+ end
113
+
114
+ it "applies default actions config from the app", :aggregate_failures do
115
+ prepare_app
116
+
117
+ expect(Admin::Actions::Articles::Index.config.default_request_format).to eq :html
118
+ expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :html
119
+ end
120
+
121
+ it "applies actions config from the app" do
122
+ Hanami.app.config.actions.default_response_format = :json
123
+
124
+ prepare_app
125
+
126
+ expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
127
+ end
128
+
129
+ it "applies config from the base class" do
130
+ prepare_app
131
+
132
+ TestApp::Action.config.default_response_format = :json
133
+
134
+ expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "inheriting from a slice-level base class, in turn inheriting from an app-level base class" do
140
+ before do
141
+ with_directory(@dir) do
142
+ write "slices/admin/action.rb", <<~'RUBY'
143
+ module Admin
144
+ class Action < TestApp::Action
145
+ end
146
+ end
147
+ RUBY
148
+ end
149
+ end
150
+
151
+ describe "slice-level base class" do
152
+ it "applies default actions config from the app", :aggregate_failures do
153
+ prepare_app
154
+
155
+ expect(Admin::Action.config.default_request_format).to eq :html
156
+ expect(Admin::Action.config.default_response_format).to eq :html
157
+ end
158
+
159
+ it "applies actions config from the app" do
160
+ Hanami.app.config.actions.default_response_format = :json
161
+
162
+ prepare_app
163
+
164
+ expect(Admin::Action.config.default_response_format).to eq :json
165
+ end
166
+
167
+ it "applies config from the app base class" do
168
+ prepare_app
169
+
170
+ TestApp::Action.config.default_response_format = :json
171
+
172
+ expect(Admin::Action.config.default_response_format).to eq :json
173
+ end
174
+
175
+ context "slice actions config present" do
176
+ before do
177
+ with_directory(@dir) do
178
+ write "config/slices/admin.rb", <<~'RUBY'
179
+ module Admin
180
+ class Slice < Hanami::Slice
181
+ config.actions.default_response_format = :csv
182
+ end
183
+ end
184
+ RUBY
185
+ end
186
+ end
187
+
188
+ it "applies actions config from the slice" do
189
+ prepare_app
190
+
191
+ expect(Admin::Action.config.default_response_format).to eq :csv
192
+ end
193
+
194
+ it "prefers actions config from the slice over config from the app-level base class" do
195
+ prepare_app
196
+
197
+ TestApp::Action.config.default_response_format = :json
198
+
199
+ expect(Admin::Action.config.default_response_format).to eq :csv
200
+ end
201
+
202
+ it "prefers config from the base class over actions config from the slice" do
203
+ prepare_app
204
+
205
+ TestApp::Action.config.default_response_format = :csv
206
+ Admin::Action.config.default_response_format = :json
207
+
208
+ expect(Admin::Action.config.default_response_format).to eq :json
209
+ end
210
+ end
211
+ end
212
+
213
+ describe "subclass in slice" do
214
+ before do
215
+ with_directory(@dir) do
216
+ write "slices/admin/actions/articles/index.rb", <<~'RUBY'
217
+ module Admin
218
+ module Actions
219
+ module Articles
220
+ class Index < Admin::Action
221
+ end
222
+ end
223
+ end
224
+ end
225
+ RUBY
226
+ end
227
+ end
228
+
229
+ it "applies default actions config from the app", :aggregate_failures do
230
+ prepare_app
231
+
232
+ expect(Admin::Actions::Articles::Index.config.default_request_format).to eq :html
233
+ expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :html
234
+ end
235
+
236
+ it "applies actions config from the app" do
237
+ Hanami.app.config.actions.default_response_format = :json
238
+
239
+ prepare_app
240
+
241
+ expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
242
+ end
243
+
244
+ it "applies actions config from the slice" do
245
+ with_directory(@dir) do
246
+ write "config/slices/admin.rb", <<~'RUBY'
247
+ module Admin
248
+ class Slice < Hanami::Slice
249
+ config.actions.default_response_format = :json
250
+ end
251
+ end
252
+ RUBY
253
+ end
254
+
255
+ prepare_app
256
+
257
+ expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
258
+ end
259
+
260
+ it "applies config from the slice base class" do
261
+ prepare_app
262
+
263
+ Admin::Action.config.default_response_format = :json
264
+
265
+ expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
266
+ end
267
+
268
+ it "prefers config from the slice base class over actions config from the slice" do
269
+ with_directory(@dir) do
270
+ write "config/slices/admin.rb", <<~'RUBY'
271
+ module Admin
272
+ class Slice < Hanami::Slice
273
+ config.actions.default_response_format = :csv
274
+ end
275
+ end
276
+ RUBY
277
+ end
278
+
279
+ prepare_app
280
+
281
+ Admin::Action.config.default_response_format = :json
282
+
283
+ expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Code loading / Loading from lib directory", :app_integration do
4
+ describe "default root" do
5
+ before :context do
6
+ with_directory(@dir = make_tmp_directory.realpath) do
7
+ write "config/app.rb", <<~'RUBY'
8
+ require "hanami"
9
+
10
+ module TestApp
11
+ class App < Hanami::App
12
+ end
13
+ end
14
+ RUBY
15
+
16
+ write "lib/external_class.rb", <<~'RUBY'
17
+ class ExternalClass
18
+ end
19
+ RUBY
20
+
21
+ write "lib/test_app/test_class.rb", <<~'RUBY'
22
+ module TestApp
23
+ class TestClass
24
+ end
25
+ end
26
+ RUBY
27
+ end
28
+ end
29
+
30
+ context "setup app" do
31
+ before do
32
+ with_directory(@dir) { require "hanami/setup" }
33
+ end
34
+
35
+ it "adds the lib directory to the load path" do
36
+ expect($LOAD_PATH).to include(@dir.join("lib").to_s)
37
+ end
38
+
39
+ specify "classes in lib/ can be required directly" do
40
+ expect(require("external_class")).to be true
41
+ expect(ExternalClass).to be
42
+ end
43
+
44
+ specify "classes in lib/[app_namespace]/ cannot yet be autoloaded" do
45
+ expect { TestApp::TestClass }.to raise_error(NameError)
46
+ end
47
+ end
48
+
49
+ context "prepared app" do
50
+ before do
51
+ with_directory(@dir) { require "hanami/prepare" }
52
+ end
53
+
54
+ it "leaves the lib directory already in the load path" do
55
+ expect($LOAD_PATH).to include(@dir.join("lib").to_s).exactly(1).times
56
+ end
57
+
58
+ specify "classes in lib/[app_namespace]/ can be autoloaded" do
59
+ expect(TestApp::TestClass).to be
60
+ end
61
+ end
62
+
63
+ context "lib dir missing" do
64
+ before do
65
+ with_directory(@dir = make_tmp_directory.realpath) do
66
+ write "config/app.rb", <<~'RUBY'
67
+ require "hanami"
68
+
69
+ module TestApp
70
+ class App < Hanami::App
71
+ end
72
+ end
73
+ RUBY
74
+
75
+ require "hanami/setup"
76
+ end
77
+ end
78
+
79
+ it "does not add the lib directory to the load path" do
80
+ expect($LOAD_PATH).not_to include(@dir.join("lib").to_s)
81
+ end
82
+ end
83
+ end
84
+
85
+ context "app root reconfigured" do
86
+ before :context do
87
+ with_directory(@dir = make_tmp_directory.realpath) do
88
+ write "config/app.rb", <<~'RUBY'
89
+ require "hanami"
90
+
91
+ module TestApp
92
+ class App < Hanami::App
93
+ config.root = Pathname(__dir__).join("..", "src").realpath
94
+ end
95
+ end
96
+ RUBY
97
+
98
+ write "src/lib/external_class.rb", <<~'RUBY'
99
+ class ExternalClass
100
+ end
101
+ RUBY
102
+
103
+ write "src/lib/test_app/test_class.rb", <<~'RUBY'
104
+ module TestApp
105
+ class TestClass
106
+ end
107
+ end
108
+ RUBY
109
+ end
110
+ end
111
+
112
+ context "setup app" do
113
+ before do
114
+ with_directory(@dir) { require "hanami/setup" }
115
+ end
116
+
117
+ it "does not add the lib directory to the load path (already done at time of subclassing)" do
118
+ expect($LOAD_PATH).not_to include(@dir.join("src", "lib").to_s)
119
+ end
120
+
121
+ it "adds the lib directory under the new root with `prepare_load_path`" do
122
+ expect { Hanami.app.prepare_load_path }
123
+ .to change { $LOAD_PATH }
124
+ .to include(@dir.join("src", "lib").to_s)
125
+ end
126
+ end
127
+
128
+ context "prepared app" do
129
+ before do
130
+ with_directory(@dir) { require "hanami/prepare" }
131
+ end
132
+
133
+ it "adds the lib directory to the load path" do
134
+ expect($LOAD_PATH).to include(@dir.join("src", "lib").to_s)
135
+ end
136
+
137
+ specify "classes in lib/ can be required directly" do
138
+ expect(require("external_class")).to be true
139
+ expect(ExternalClass).to be
140
+ end
141
+
142
+ specify "classes in lib/[app_namespace]/ can be autoloaded" do
143
+ expect(TestApp::TestClass).to be
144
+ end
145
+ end
146
+ end
147
+
148
+ context "app root reconfigured and load path immediately prepared" do
149
+ before :context do
150
+ with_directory(@dir = make_tmp_directory.realpath) do
151
+ write "config/app.rb", <<~'RUBY'
152
+ require "hanami"
153
+
154
+ module TestApp
155
+ class App < Hanami::App
156
+ config.root = Pathname(__dir__).join("..", "src").realpath and prepare_load_path
157
+ end
158
+ end
159
+ RUBY
160
+
161
+ write "src/lib/external_class.rb", <<~'RUBY'
162
+ class ExternalClass
163
+ end
164
+ RUBY
165
+
166
+ write "src/lib/test_app/test_class.rb", <<~'RUBY'
167
+ module TestApp
168
+ class TestClass
169
+ end
170
+ end
171
+ RUBY
172
+ end
173
+ end
174
+
175
+ context "setup app" do
176
+ before do
177
+ with_directory(@dir) { require "hanami/setup" }
178
+ end
179
+
180
+ it "adds the lib directory to the load path" do
181
+ expect($LOAD_PATH).to include(@dir.join("src", "lib").to_s)
182
+ end
183
+
184
+ specify "classes in lib/ can be required directly" do
185
+ expect(require("external_class")).to be true
186
+ expect(ExternalClass).to be
187
+ end
188
+
189
+ specify "classes in lib/[app_namespace]/ cannot yet be autoloaded" do
190
+ expect { TestApp::TestClass }.to raise_error(NameError)
191
+ end
192
+ end
193
+
194
+ context "prepared app" do
195
+ before do
196
+ with_directory(@dir) { require "hanami/prepare" }
197
+ end
198
+
199
+ it "leaves the lib directory to the load path" do
200
+ expect($LOAD_PATH).to include(@dir.join("src", "lib").to_s).exactly(1).times
201
+ end
202
+
203
+ specify "classes in lib/[app_namespace]/ can be autoloaded" do
204
+ expect(TestApp::TestClass).to be
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/FetchEnvVar
4
+
5
+ RSpec.describe "Dotenv loading", :app_integration do
6
+ before do
7
+ @orig_env = ENV.to_h
8
+ end
9
+
10
+ after do
11
+ ENV.replace(@orig_env)
12
+ end
13
+
14
+ context "dotenv gem is available" do
15
+ before do
16
+ require "dotenv"
17
+ end
18
+
19
+ context "hanami env is development" do
20
+ it "loads .env.development.local, .env.local, .env.development and .env (in this order) into ENV", :aggregate_failures do
21
+ with_tmp_directory(Dir.mktmpdir) do
22
+ write "config/app.rb", <<~'RUBY'
23
+ require "hanami"
24
+
25
+ module TestApp
26
+ class App < Hanami::App
27
+ end
28
+ end
29
+ RUBY
30
+
31
+ write ".env.development.local", <<~'TEXT'
32
+ FROM_SPECIFIC_ENV_LOCAL="from .env.development.local"
33
+ TEXT
34
+
35
+ write ".env.local", <<~'TEXT'
36
+ FROM_BASE_LOCAL="from .env.local"
37
+ FROM_SPECIFIC_ENV_LOCAL=nope
38
+ TEXT
39
+
40
+ write ".env.development", <<~'TEXT'
41
+ FROM_SPECIFIC_ENV="from .env.development"
42
+ FROM_SPECIFIC_ENV_LOCAL=nope
43
+ FROM_BASE_LOCAL=nope
44
+ TEXT
45
+
46
+ write ".env", <<~'TEXT'
47
+ FROM_BASE="from .env"
48
+ FROM_SPECIFIC_ENV_LOCAL=nope
49
+ FROM_BASE_LOCAL=nope
50
+ FROM_SPECIFIC_ENV=nope
51
+ TEXT
52
+
53
+ ENV["HANAMI_ENV"] = "development"
54
+
55
+ require "hanami/setup"
56
+
57
+ expect(ENV["FROM_SPECIFIC_ENV_LOCAL"]).to eq "from .env.development.local"
58
+ expect(ENV["FROM_BASE_LOCAL"]).to eq "from .env.local"
59
+ expect(ENV["FROM_SPECIFIC_ENV"]).to eq "from .env.development"
60
+ expect(ENV["FROM_BASE"]).to eq "from .env"
61
+ end
62
+ end
63
+ end
64
+
65
+ context "hanami env is test" do
66
+ it "loads .env.development.local, .env.development and .env (in this order) into ENV", :aggregate_failures do
67
+ with_tmp_directory(Dir.mktmpdir) do
68
+ write "config/app.rb", <<~'RUBY'
69
+ require "hanami"
70
+
71
+ module TestApp
72
+ class App < Hanami::App
73
+ end
74
+ end
75
+ RUBY
76
+
77
+ write ".env.test.local", <<~'TEXT'
78
+ FROM_SPECIFIC_ENV_LOCAL="from .env.test.local"
79
+ TEXT
80
+
81
+ write ".env.local", <<~'TEXT'
82
+ FROM_BASE_LOCAL="from .env.local"
83
+ TEXT
84
+
85
+ write ".env.test", <<~'TEXT'
86
+ FROM_SPECIFIC_ENV="from .env.test"
87
+ FROM_SPECIFIC_ENV_LOCAL=nope
88
+ TEXT
89
+
90
+ write ".env", <<~'TEXT'
91
+ FROM_BASE="from .env"
92
+ FROM_SPECIFIC_ENV_LOCAL=nope
93
+ FROM_SPECIFIC_ENV=nope
94
+ TEXT
95
+
96
+ ENV["HANAMI_ENV"] = "test"
97
+
98
+ require "hanami/prepare"
99
+
100
+ expect(ENV["FROM_SPECIFIC_ENV_LOCAL"]).to eq "from .env.test.local"
101
+ expect(ENV["FROM_BASE_LOCAL"]).to be nil
102
+ expect(ENV["FROM_SPECIFIC_ENV"]).to eq "from .env.test"
103
+ expect(ENV["FROM_BASE"]).to eq "from .env"
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ context "dotenv gem is unavailable" do
110
+ before do
111
+ allow_any_instance_of(Object).to receive(:gem).and_call_original
112
+ allow_any_instance_of(Object).to receive(:gem).with("dotenv").and_raise(Gem::LoadError)
113
+ end
114
+
115
+ it "does not load from .env files" do
116
+ with_tmp_directory(Dir.mktmpdir) do
117
+ write "config/app.rb", <<~'RUBY'
118
+ require "hanami"
119
+
120
+ module TestApp
121
+ class App < Hanami::App
122
+ end
123
+ end
124
+ RUBY
125
+
126
+ write ".env", <<~'TEXT'
127
+ FOO=bar
128
+ TEXT
129
+
130
+ expect { require "hanami/prepare" }.not_to(change { ENV.to_h })
131
+ expect(ENV.key?("FOO")).to be false
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ # rubocop:enable Style/FetchEnvVar