hanami 2.0.0.beta1.1 → 2.0.0.beta2

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