hanami 2.0.0.rc1 → 2.0.1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/FEATURES.md +15 -21
  4. data/README.md +7 -37
  5. data/hanami.gemspec +5 -5
  6. data/lib/hanami/app.rb +2 -33
  7. data/lib/hanami/config/actions/content_security_policy.rb +1 -1
  8. data/lib/hanami/config/actions.rb +0 -3
  9. data/lib/hanami/config/logger.rb +52 -29
  10. data/lib/hanami/config.rb +22 -1
  11. data/lib/hanami/env.rb +52 -0
  12. data/lib/hanami/extensions/action/slice_configured_action.rb +5 -2
  13. data/lib/hanami/extensions/view/slice_configured_view.rb +4 -1
  14. data/lib/hanami/port.rb +45 -0
  15. data/lib/hanami/providers/rack.rb +15 -7
  16. data/lib/hanami/rake_tasks.rb +4 -5
  17. data/lib/hanami/slice.rb +3 -1
  18. data/lib/hanami/version.rb +1 -1
  19. data/lib/hanami/web/rack_logger.rb +64 -15
  20. data/lib/hanami.rb +24 -5
  21. data/spec/integration/action/format_config_spec.rb +194 -0
  22. data/spec/integration/action/slice_configuration_spec.rb +39 -42
  23. data/spec/integration/code_loading/loading_from_lib_spec.rb +34 -0
  24. data/spec/integration/dotenv_loading_spec.rb +1 -0
  25. data/spec/integration/rack_app/body_parser_spec.rb +2 -5
  26. data/spec/integration/rack_app/rack_app_spec.rb +132 -5
  27. data/spec/integration/settings/access_in_slice_class_body_spec.rb +1 -0
  28. data/spec/integration/settings/loading_from_env_spec.rb +1 -0
  29. data/spec/integration/setup_spec.rb +2 -2
  30. data/spec/spec_helper.rb +12 -0
  31. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +14 -14
  32. data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -8
  33. data/spec/unit/hanami/config/actions_spec.rb +7 -10
  34. data/spec/unit/hanami/config/logger_spec.rb +4 -4
  35. data/spec/unit/hanami/port_spec.rb +117 -0
  36. data/spec/unit/hanami/version_spec.rb +1 -1
  37. data/spec/unit/hanami/web/rack_logger_spec.rb +4 -1
  38. metadata +36 -12
@@ -8,6 +8,9 @@ module Hanami
8
8
  # @api private
9
9
  # @since 2.0.0
10
10
  class RackLogger
11
+ EMPTY_PARAMS = {}.freeze
12
+ private_constant :EMPTY_PARAMS
13
+
11
14
  REQUEST_METHOD = "REQUEST_METHOD"
12
15
  private_constant :REQUEST_METHOD
13
16
 
@@ -29,10 +32,46 @@ module Hanami
29
32
  CONTENT_LENGTH = "Content-Length"
30
33
  private_constant :CONTENT_LENGTH
31
34
 
35
+ MILISECOND = "ms"
36
+ private_constant :MILISECOND
37
+
38
+ MICROSECOND = "µs"
39
+ private_constant :MICROSECOND
40
+
41
+ # Dynamic extension used in production environments
42
+ # @api private
43
+ module Production
44
+ private
45
+
46
+ # @since 1.0.0
47
+ # @api private
48
+ def data(env, status:, elapsed:)
49
+ payload = super
50
+ payload[:elapsed] = elapsed
51
+ payload[:elapsed_unit] = MICROSECOND
52
+ payload
53
+ end
54
+ end
55
+
56
+ # Dynamic extension used in non-production environments
57
+ # @api private
58
+ module Development
59
+ private
60
+
61
+ # @since 1.0.0
62
+ # @api private
63
+ def data(env, status:, elapsed:)
64
+ payload = super
65
+ payload[:elapsed] = elapsed > 1000 ? "#{elapsed / 1000}ms" : "#{elapsed}#{MICROSECOND}"
66
+ payload
67
+ end
68
+ end
69
+
32
70
  # @api private
33
71
  # @since 2.0.0
34
- def initialize(logger)
72
+ def initialize(logger, env: :development)
35
73
  @logger = logger
74
+ extend(env == :production ? Production : Development)
36
75
  end
37
76
 
38
77
  # @api private
@@ -43,36 +82,46 @@ module Hanami
43
82
  end
44
83
 
45
84
  rack_monitor.on :error do |event|
46
- log_exception event[:exception]
85
+ # TODO: why we don't provide time on error?
86
+ log_exception event[:env], event[:exception], 500, 0
47
87
  end
48
88
  end
49
89
 
50
90
  # @api private
51
91
  # @since 2.0.0
52
92
  def log_request(env, status, elapsed)
53
- data = {
54
- verb: env[REQUEST_METHOD],
55
- status: status,
56
- elapsed: "#{elapsed}ms",
57
- ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
58
- path: env[SCRIPT_NAME] + env[PATH_INFO].to_s,
59
- length: extract_content_length(env),
60
- params: env[ROUTER_PARAMS]
61
- }
62
-
63
- logger.info(data)
93
+ logger.tagged(:rack) do
94
+ logger.info(data(env, status: status, elapsed: elapsed))
95
+ end
64
96
  end
65
97
 
66
98
  # @api private
67
99
  # @since 2.0.0
68
- def log_exception(exception)
69
- logger.error(exception)
100
+ def log_exception(env, exception, status, elapsed)
101
+ logger.tagged(:rack) do
102
+ logger.error(exception, **data(env, status: status, elapsed: elapsed))
103
+ end
70
104
  end
71
105
 
72
106
  private
73
107
 
74
108
  attr_reader :logger
75
109
 
110
+ # @api private
111
+ # @since 2.0.0
112
+ def data(env, status:, elapsed:)
113
+ {
114
+ verb: env[REQUEST_METHOD],
115
+ status: status,
116
+ ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
117
+ path: "#{env[SCRIPT_NAME]}#{env[PATH_INFO]}",
118
+ length: extract_content_length(env),
119
+ params: env.fetch(ROUTER_PARAMS, EMPTY_PARAMS)
120
+ }
121
+ end
122
+
123
+ # @api private
124
+ # @since 2.0.0
76
125
  def extract_content_length(env)
77
126
  value = env[CONTENT_LENGTH]
78
127
  !value || value.to_s == "0" ? "-" : value
data/lib/hanami.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
3
4
  require "zeitwerk"
4
5
  require_relative "hanami/constants"
5
6
 
@@ -36,7 +37,8 @@ module Hanami
36
37
  app_path = self.app_path
37
38
 
38
39
  if app_path
39
- require(app_path)
40
+ prepare_load_path
41
+ require(app_path.to_s)
40
42
  app
41
43
  elsif raise_exception
42
44
  raise(
@@ -47,6 +49,23 @@ module Hanami
47
49
  end
48
50
  end
49
51
 
52
+ # Prepare the load path as early as possible (based on the default root inferred from the location
53
+ # of `config/app.rb`), so `require` can work at the top of `config/app.rb`. This may be useful
54
+ # when external classes are needed for configuring certain aspects of the app.
55
+ #
56
+ # @api private
57
+ # @since 2.0.0
58
+ private_class_method def self.prepare_load_path
59
+ lib_path = app_path&.join("..", "..", LIB_DIR)
60
+
61
+ if lib_path&.directory?
62
+ path = lib_path.realpath.to_s
63
+ $LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
64
+ end
65
+
66
+ lib_path
67
+ end
68
+
50
69
  # Returns the Hamami app class.
51
70
  #
52
71
  # To ensure your Hanami app is loaded, run {.setup} (or `require "hanami/setup"`) first.
@@ -98,10 +117,10 @@ module Hanami
98
117
  # Searches within the given directory, then searches upwards through parent directories until the
99
118
  # app file can be found.
100
119
  #
101
- # @param dir [String] The directory from which to start searching. Defaults to the current
102
- # directory.
120
+ # @param dir [String, Pathname] The directory from which to start searching. Defaults to the
121
+ # current directory.
103
122
  #
104
- # @return [String, nil] the app file path, or nil if not found.
123
+ # @return [Pathname, nil] the app file path, or nil if not found.
105
124
  #
106
125
  # @api public
107
126
  # @since 2.0.0
@@ -110,7 +129,7 @@ module Hanami
110
129
  path = dir.join(APP_PATH)
111
130
 
112
131
  if path.file?
113
- path.to_s
132
+ path
114
133
  elsif !dir.root?
115
134
  app_path(dir.parent)
116
135
  end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rack/test"
5
+
6
+ RSpec.describe "App action / Format config", :app_integration do
7
+ include Rack::Test::Methods
8
+
9
+ let(:app) { Hanami.app }
10
+
11
+ around do |example|
12
+ with_tmp_directory(Dir.mktmpdir, &example)
13
+ end
14
+
15
+ it "adds a body parser middleware for the accepted formats from the action config" do
16
+ write "config/app.rb", <<~RUBY
17
+ require "hanami"
18
+
19
+ module TestApp
20
+ class App < Hanami::App
21
+ config.logger.stream = StringIO.new
22
+
23
+ config.actions.format :json
24
+ end
25
+ end
26
+ RUBY
27
+
28
+ write "config/routes.rb", <<~RUBY
29
+ module TestApp
30
+ class Routes < Hanami::Routes
31
+ post "/users", to: "users.create"
32
+ end
33
+ end
34
+ RUBY
35
+
36
+ write "app/action.rb", <<~RUBY
37
+ # auto_register: false
38
+
39
+ module TestApp
40
+ class Action < Hanami::Action
41
+ end
42
+ end
43
+ RUBY
44
+
45
+ write "app/actions/users/create.rb", <<~RUBY
46
+ module TestApp
47
+ module Actions
48
+ module Users
49
+ class Create < TestApp::Action
50
+ def handle(req, res)
51
+ res.body = req.params[:users].join("-")
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ RUBY
58
+
59
+ require "hanami/boot"
60
+
61
+ post(
62
+ "/users",
63
+ JSON.generate("users" => %w[jane john jade joe]),
64
+ "CONTENT_TYPE" => "application/json"
65
+ )
66
+
67
+ expect(last_response).to be_successful
68
+ expect(last_response.body).to eql("jane-john-jade-joe")
69
+ end
70
+
71
+ specify "adds a body parser middleware configured to parse any custom content type for the accepted formats" do
72
+ write "config/app.rb", <<~RUBY
73
+ require "hanami"
74
+
75
+ module TestApp
76
+ class App < Hanami::App
77
+ config.logger.stream = StringIO.new
78
+
79
+ config.actions.formats.add :json, ["application/json+scim", "application/json"]
80
+ end
81
+ end
82
+ RUBY
83
+
84
+ write "config/routes.rb", <<~RUBY
85
+ module TestApp
86
+ class Routes < Hanami::Routes
87
+ post "/users", to: "users.create"
88
+ end
89
+ end
90
+ RUBY
91
+
92
+ write "app/action.rb", <<~RUBY
93
+ # auto_register: false
94
+
95
+ module TestApp
96
+ class Action < Hanami::Action
97
+ end
98
+ end
99
+ RUBY
100
+
101
+ write "app/actions/users/create.rb", <<~RUBY
102
+ module TestApp
103
+ module Actions
104
+ module Users
105
+ class Create < TestApp::Action
106
+ def handle(req, res)
107
+ res.body = req.params[:users].join("-")
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ RUBY
114
+
115
+ require "hanami/boot"
116
+
117
+ post(
118
+ "/users",
119
+ JSON.generate("users" => %w[jane john jade joe]),
120
+ "CONTENT_TYPE" => "application/json+scim"
121
+ )
122
+
123
+ expect(last_response).to be_successful
124
+ expect(last_response.body).to eql("jane-john-jade-joe")
125
+
126
+ post(
127
+ "/users",
128
+ JSON.generate("users" => %w[jane john jade joe]),
129
+ "CONTENT_TYPE" => "application/json"
130
+ )
131
+
132
+ expect(last_response).to be_successful
133
+ expect(last_response.body).to eql("jane-john-jade-joe")
134
+ end
135
+
136
+ it "does not add a body parser middleware if one is already added" do
137
+ write "config/app.rb", <<~RUBY
138
+ require "hanami"
139
+
140
+ module TestApp
141
+ class App < Hanami::App
142
+ config.logger.stream = StringIO.new
143
+
144
+ config.actions.format :json
145
+ config.middleware.use :body_parser, [json: "application/json+custom"]
146
+ end
147
+ end
148
+ RUBY
149
+
150
+ write "config/routes.rb", <<~RUBY
151
+ module TestApp
152
+ class Routes < Hanami::Routes
153
+ post "/users", to: "users.create"
154
+ end
155
+ end
156
+ RUBY
157
+
158
+ write "app/action.rb", <<~RUBY
159
+ # auto_register: false
160
+
161
+ module TestApp
162
+ class Action < Hanami::Action
163
+ end
164
+ end
165
+ RUBY
166
+
167
+ write "app/actions/users/create.rb", <<~RUBY
168
+ module TestApp
169
+ module Actions
170
+ module Users
171
+ class Create < TestApp::Action
172
+ config.formats.clear
173
+
174
+ def handle(req, res)
175
+ res.body = req.params[:users].join("-")
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ RUBY
182
+
183
+ require "hanami/boot"
184
+
185
+ post(
186
+ "/users",
187
+ JSON.generate("users" => %w[jane john jade joe]),
188
+ "CONTENT_TYPE" => "application/json+custom"
189
+ )
190
+
191
+ expect(last_response).to be_successful
192
+ expect(last_response.body).to eql("jane-john-jade-joe")
193
+ end
194
+ end
@@ -34,24 +34,25 @@ RSpec.describe "App action / Slice configuration", :app_integration do
34
34
  it "applies default actions config from the app", :aggregate_failures do
35
35
  prepare_app
36
36
 
37
- expect(TestApp::Action.config.default_request_format).to eq :html
38
- expect(TestApp::Action.config.default_response_format).to eq :html
37
+ expect(TestApp::Action.config.formats.values).to eq []
39
38
  end
40
39
 
41
40
  it "applies actions config from the app" do
42
- Hanami.app.config.actions.default_response_format = :json
41
+ Hanami.app.config.actions.format :json
43
42
 
44
43
  prepare_app
45
44
 
46
- expect(TestApp::Action.config.default_response_format).to eq :json
45
+ expect(TestApp::Action.config.formats.values).to eq [:json]
47
46
  end
48
47
 
49
48
  it "does not override config in the base class" do
50
- Hanami.app.config.actions.default_response_format = :csv
49
+ Hanami.app.config.actions.format :csv
51
50
 
52
51
  prepare_app
53
52
 
54
- TestApp::Action.config.default_response_format = :json
53
+ TestApp::Action.config.format :json
54
+
55
+ expect(TestApp::Action.config.formats.values).to eq [:json]
55
56
  end
56
57
  end
57
58
 
@@ -74,24 +75,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
74
75
  it "applies default actions config from the app", :aggregate_failures do
75
76
  prepare_app
76
77
 
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
78
+ expect(TestApp::Actions::Articles::Index.config.formats.values).to eq []
79
79
  end
80
80
 
81
81
  it "applies actions config from the app" do
82
- Hanami.app.config.actions.default_response_format = :json
82
+ Hanami.app.config.actions.format :json
83
83
 
84
84
  prepare_app
85
85
 
86
- expect(TestApp::Actions::Articles::Index.config.default_response_format).to eq :json
86
+ expect(TestApp::Actions::Articles::Index.config.formats.values).to eq [:json]
87
87
  end
88
88
 
89
89
  it "applies config from the base class" do
90
90
  prepare_app
91
91
 
92
- TestApp::Action.config.default_response_format = :json
92
+ TestApp::Action.config.format :json
93
93
 
94
- expect(TestApp::Actions::Articles::Index.config.default_response_format).to eq :json
94
+ expect(TestApp::Actions::Articles::Index.config.formats.values).to eq [:json]
95
95
  end
96
96
  end
97
97
 
@@ -114,24 +114,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
114
114
  it "applies default actions config from the app", :aggregate_failures do
115
115
  prepare_app
116
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
117
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq []
119
118
  end
120
119
 
121
120
  it "applies actions config from the app" do
122
- Hanami.app.config.actions.default_response_format = :json
121
+ Hanami.app.config.actions.format :json
123
122
 
124
123
  prepare_app
125
124
 
126
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
125
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
127
126
  end
128
127
 
129
128
  it "applies config from the base class" do
130
129
  prepare_app
131
130
 
132
- TestApp::Action.config.default_response_format = :json
131
+ TestApp::Action.config.format :json
133
132
 
134
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
133
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
135
134
  end
136
135
  end
137
136
  end
@@ -152,24 +151,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
152
151
  it "applies default actions config from the app", :aggregate_failures do
153
152
  prepare_app
154
153
 
155
- expect(Admin::Action.config.default_request_format).to eq :html
156
- expect(Admin::Action.config.default_response_format).to eq :html
154
+ expect(Admin::Action.config.formats.values).to eq []
157
155
  end
158
156
 
159
157
  it "applies actions config from the app" do
160
- Hanami.app.config.actions.default_response_format = :json
158
+ Hanami.app.config.actions.format :json
161
159
 
162
160
  prepare_app
163
161
 
164
- expect(Admin::Action.config.default_response_format).to eq :json
162
+ expect(Admin::Action.config.formats.values).to eq [:json]
165
163
  end
166
164
 
167
165
  it "applies config from the app base class" do
168
166
  prepare_app
169
167
 
170
- TestApp::Action.config.default_response_format = :json
168
+ TestApp::Action.config.format :json
171
169
 
172
- expect(Admin::Action.config.default_response_format).to eq :json
170
+ expect(Admin::Action.config.formats.values).to eq [:json]
173
171
  end
174
172
 
175
173
  context "slice actions config present" do
@@ -178,7 +176,7 @@ RSpec.describe "App action / Slice configuration", :app_integration do
178
176
  write "config/slices/admin.rb", <<~'RUBY'
179
177
  module Admin
180
178
  class Slice < Hanami::Slice
181
- config.actions.default_response_format = :csv
179
+ config.actions.format :csv
182
180
  end
183
181
  end
184
182
  RUBY
@@ -188,24 +186,24 @@ RSpec.describe "App action / Slice configuration", :app_integration do
188
186
  it "applies actions config from the slice" do
189
187
  prepare_app
190
188
 
191
- expect(Admin::Action.config.default_response_format).to eq :csv
189
+ expect(Admin::Action.config.formats.values).to eq [:csv]
192
190
  end
193
191
 
194
192
  it "prefers actions config from the slice over config from the app-level base class" do
195
193
  prepare_app
196
194
 
197
- TestApp::Action.config.default_response_format = :json
195
+ TestApp::Action.config.format :json
198
196
 
199
- expect(Admin::Action.config.default_response_format).to eq :csv
197
+ expect(Admin::Action.config.formats.values).to eq [:csv]
200
198
  end
201
199
 
202
200
  it "prefers config from the base class over actions config from the slice" do
203
201
  prepare_app
204
202
 
205
- TestApp::Action.config.default_response_format = :csv
206
- Admin::Action.config.default_response_format = :json
203
+ TestApp::Action.config.format :csv
204
+ Admin::Action.config.format :json
207
205
 
208
- expect(Admin::Action.config.default_response_format).to eq :json
206
+ expect(Admin::Action.config.formats.values).to eq [:json]
209
207
  end
210
208
  end
211
209
  end
@@ -229,16 +227,15 @@ RSpec.describe "App action / Slice configuration", :app_integration do
229
227
  it "applies default actions config from the app", :aggregate_failures do
230
228
  prepare_app
231
229
 
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
230
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq []
234
231
  end
235
232
 
236
233
  it "applies actions config from the app" do
237
- Hanami.app.config.actions.default_response_format = :json
234
+ Hanami.app.config.actions.format :json
238
235
 
239
236
  prepare_app
240
237
 
241
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
238
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
242
239
  end
243
240
 
244
241
  it "applies actions config from the slice" do
@@ -246,7 +243,7 @@ RSpec.describe "App action / Slice configuration", :app_integration do
246
243
  write "config/slices/admin.rb", <<~'RUBY'
247
244
  module Admin
248
245
  class Slice < Hanami::Slice
249
- config.actions.default_response_format = :json
246
+ config.actions.format :json
250
247
  end
251
248
  end
252
249
  RUBY
@@ -254,15 +251,15 @@ RSpec.describe "App action / Slice configuration", :app_integration do
254
251
 
255
252
  prepare_app
256
253
 
257
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
254
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
258
255
  end
259
256
 
260
257
  it "applies config from the slice base class" do
261
258
  prepare_app
262
259
 
263
- Admin::Action.config.default_response_format = :json
260
+ Admin::Action.config.format :json
264
261
 
265
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
262
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
266
263
  end
267
264
 
268
265
  it "prefers config from the slice base class over actions config from the slice" do
@@ -270,7 +267,7 @@ RSpec.describe "App action / Slice configuration", :app_integration do
270
267
  write "config/slices/admin.rb", <<~'RUBY'
271
268
  module Admin
272
269
  class Slice < Hanami::Slice
273
- config.actions.default_response_format = :csv
270
+ config.actions.format :csv
274
271
  end
275
272
  end
276
273
  RUBY
@@ -278,9 +275,9 @@ RSpec.describe "App action / Slice configuration", :app_integration do
278
275
 
279
276
  prepare_app
280
277
 
281
- Admin::Action.config.default_response_format = :json
278
+ Admin::Action.config.format :json
282
279
 
283
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
280
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
284
281
  end
285
282
  end
286
283
  end
@@ -82,6 +82,40 @@ RSpec.describe "Code loading / Loading from lib directory", :app_integration do
82
82
  end
83
83
  end
84
84
 
85
+ describe "default root with requires at top of app file" do
86
+ before :context do
87
+ with_directory(@dir = make_tmp_directory.realpath) do
88
+ write "config/app.rb", <<~'RUBY'
89
+ require "hanami"
90
+ require "external_class"
91
+
92
+ module TestApp
93
+ class App < Hanami::App
94
+ @class_from_lib = ExternalClass
95
+
96
+ def self.class_from_lib
97
+ @class_from_lib
98
+ end
99
+ end
100
+ end
101
+ RUBY
102
+
103
+ write "lib/external_class.rb", <<~'RUBY'
104
+ class ExternalClass
105
+ end
106
+ RUBY
107
+ end
108
+ end
109
+
110
+ before do
111
+ with_directory(@dir) { require "hanami/setup" }
112
+ end
113
+
114
+ specify "classes in lib/ can be required directly from the top of the app file" do
115
+ expect(Hanami.app.class_from_lib).to be ExternalClass
116
+ end
117
+ end
118
+
85
119
  context "app root reconfigured" do
86
120
  before :context do
87
121
  with_directory(@dir = make_tmp_directory.realpath) do
@@ -5,6 +5,7 @@
5
5
  RSpec.describe "Dotenv loading", :app_integration do
6
6
  before do
7
7
  @orig_env = ENV.to_h
8
+ allow(Hanami::Env).to receive(:loaded?).and_return(false)
8
9
  end
9
10
 
10
11
  after do
@@ -18,6 +18,7 @@ RSpec.describe "Hanami web app", :app_integration do
18
18
 
19
19
  module TestApp
20
20
  class App < Hanami::App
21
+ config.actions.format :json
21
22
  config.middleware.use :body_parser, :json
22
23
  config.logger.stream = StringIO.new
23
24
  end
@@ -37,8 +38,6 @@ RSpec.describe "Hanami web app", :app_integration do
37
38
  module Actions
38
39
  module Users
39
40
  class Create < Hanami::Action
40
- accept :json
41
-
42
41
  def handle(req, res)
43
42
  res.body = req.params[:users].join("-")
44
43
  end
@@ -66,7 +65,7 @@ RSpec.describe "Hanami web app", :app_integration do
66
65
 
67
66
  module TestApp
68
67
  class App < Hanami::App
69
- config.actions.formats["application/json+scim"] = :json
68
+ config.actions.formats.add :json, ["application/json+scim"]
70
69
  config.middleware.use :body_parser, [json: "application/json+scim"]
71
70
  config.logger.stream = StringIO.new
72
71
  end
@@ -86,8 +85,6 @@ RSpec.describe "Hanami web app", :app_integration do
86
85
  module Actions
87
86
  module Users
88
87
  class Create < Hanami::Action
89
- accept :json
90
-
91
88
  def handle(req, res)
92
89
  res.body = req.params[:users].join("-")
93
90
  end