hanami 2.0.2 → 2.1.0.beta1

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +25 -9
  5. data/hanami.gemspec +2 -2
  6. data/lib/hanami/config/actions.rb +0 -4
  7. data/lib/hanami/config/logger.rb +1 -1
  8. data/lib/hanami/config/views.rb +0 -4
  9. data/lib/hanami/config.rb +54 -0
  10. data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
  11. data/lib/hanami/extensions/action.rb +4 -4
  12. data/lib/hanami/extensions/router/errors.rb +58 -0
  13. data/lib/hanami/extensions/view/context.rb +129 -60
  14. data/lib/hanami/extensions/view/part.rb +26 -0
  15. data/lib/hanami/extensions/view/scope.rb +26 -0
  16. data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
  17. data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
  18. data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
  19. data/lib/hanami/extensions/view/standard_helpers.rb +14 -0
  20. data/lib/hanami/extensions.rb +10 -3
  21. data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
  22. data/lib/hanami/helpers/form_helper/values.rb +75 -0
  23. data/lib/hanami/helpers/form_helper.rb +213 -0
  24. data/lib/hanami/middleware/public_errors_app.rb +75 -0
  25. data/lib/hanami/middleware/render_errors.rb +93 -0
  26. data/lib/hanami/slice.rb +28 -2
  27. data/lib/hanami/slice_configurable.rb +3 -2
  28. data/lib/hanami/version.rb +1 -1
  29. data/lib/hanami/web/rack_logger.rb +8 -20
  30. data/lib/hanami.rb +1 -1
  31. data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
  32. data/spec/integration/action/view_rendering_spec.rb +0 -18
  33. data/spec/integration/rack_app/middleware_spec.rb +23 -23
  34. data/spec/integration/rack_app/rack_app_spec.rb +5 -1
  35. data/spec/integration/slices/slice_registrations_spec.rb +80 -0
  36. data/spec/integration/view/config/default_context_spec.rb +149 -0
  37. data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
  38. data/spec/integration/view/config/part_class_spec.rb +147 -0
  39. data/spec/integration/view/config/part_namespace_spec.rb +103 -0
  40. data/spec/integration/view/config/paths_spec.rb +119 -0
  41. data/spec/integration/view/config/scope_class_spec.rb +147 -0
  42. data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
  43. data/spec/integration/view/config/template_spec.rb +38 -0
  44. data/spec/integration/view/context/request_spec.rb +3 -7
  45. data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
  46. data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
  47. data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
  48. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
  49. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
  50. data/spec/integration/view/slice_configuration_spec.rb +9 -9
  51. data/spec/integration/web/render_detailed_errors_spec.rb +90 -0
  52. data/spec/integration/web/render_errors_spec.rb +240 -0
  53. data/spec/spec_helper.rb +1 -1
  54. data/spec/support/matchers.rb +32 -0
  55. data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -4
  56. data/spec/unit/hanami/config/logger_spec.rb +9 -0
  57. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
  58. data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
  59. data/spec/unit/hanami/config/views_spec.rb +0 -18
  60. data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
  61. data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
  62. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
  63. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
  64. data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
  65. data/spec/unit/hanami/version_spec.rb +1 -1
  66. data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
  67. metadata +67 -33
  68. data/spec/integration/action/view_integration_spec.rb +0 -165
  69. data/spec/integration/view/part_namespace_spec.rb +0 -96
  70. data/spec/integration/view/path_spec.rb +0 -56
  71. data/spec/integration/view/template_spec.rb +0 -68
  72. data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
  73. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
  74. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
  75. data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
  76. data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
  77. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
  78. data/spec/isolation/hanami/boot/success_spec.rb +0 -50
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Helpers
5
+ module FormHelper
6
+ # Values from params and form helpers.
7
+ #
8
+ # It's responsible to populate input values with data coming from params
9
+ # and inline values specified via form helpers like `text_field`.
10
+ #
11
+ # @since 2.0.0
12
+ # @api private
13
+ class Values
14
+ # @since 2.0.0
15
+ # @api private
16
+ GET_SEPARATOR = "."
17
+
18
+ # @api private
19
+ # @since 2.0.0
20
+ attr_reader :csrf_token
21
+
22
+ # @since 2.0.0
23
+ # @api private
24
+ def initialize(values: {}, params: {}, csrf_token: nil)
25
+ @values = values.to_h
26
+ @params = params.to_h
27
+ @csrf_token = csrf_token
28
+ end
29
+
30
+ # Returns the value (if present) for the given key.
31
+ # Nested values are expressed with an array if symbols.
32
+ #
33
+ # @since 2.0.0
34
+ # @api private
35
+ def get(*keys)
36
+ get_from_params(*keys) || get_from_values(*keys)
37
+ end
38
+
39
+ private
40
+
41
+ # @since 2.0.0
42
+ # @api private
43
+ def get_from_params(*keys)
44
+ keys.map! { |key| /\A\d+\z/.match?(key.to_s) ? key.to_s.to_i : key }
45
+ @params.dig(*keys)
46
+ end
47
+
48
+ # @since 2.0.0
49
+ # @api private
50
+ def get_from_values(*keys)
51
+ head, *tail = *keys
52
+ result = @values[head]
53
+
54
+ tail.each do |k|
55
+ break if result.nil?
56
+
57
+ result = dig(result, k)
58
+ end
59
+
60
+ result
61
+ end
62
+
63
+ # @since 2.0.0
64
+ # @api private
65
+ def dig(base, key)
66
+ case base
67
+ when ::Hash then base[key]
68
+ when Array then base[key.to_s.to_i]
69
+ when ->(r) { r.respond_to?(key) } then base.public_send(key)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/view"
4
+
5
+ module Hanami
6
+ module Helpers
7
+ # Helper methods for generating HTML forms.
8
+ #
9
+ # These helpers will be automatically available in your view templates, part classes and scope
10
+ # classes.
11
+ #
12
+ # This module provides one primary method: {#form_for}, yielding an HTML form builder. This
13
+ # integrates with request params and template locals to populate the form with appropriate
14
+ # values.
15
+ #
16
+ # @api public
17
+ # @since 2.0.0
18
+ module FormHelper
19
+ require_relative "form_helper/form_builder"
20
+
21
+ # Default HTTP method for form
22
+ #
23
+ # @since 2.0.0
24
+ # @api private
25
+ DEFAULT_METHOD = "POST"
26
+
27
+ # Default charset
28
+ #
29
+ # @since 2.0.0
30
+ # @api private
31
+ DEFAULT_CHARSET = "utf-8"
32
+
33
+ # CSRF Token session key
34
+ #
35
+ # This name of this key is shared with the hanami and hanami-controller gems.
36
+ #
37
+ # @since 2.0.0
38
+ # @api private
39
+ CSRF_TOKEN = :_csrf_token
40
+
41
+ include Hanami::View::Helpers::TagHelper
42
+
43
+ # Yields a form builder for constructing an HTML form and returns the resulting form string.
44
+ #
45
+ # See {FormHelper::FormBuilder} for the methods for building the form's fields.
46
+ #
47
+ # @overload form_for(base_name, url, values: _form_for_values, params: _form_for_params, **attributes)
48
+ # Builds the form using the given base name for all fields.
49
+ #
50
+ # @param base_name [String] the base
51
+ # @param url [String] the URL for submitting the form
52
+ # @param values [Hash] values to be used for populating form field values; optional,
53
+ # defaults to the template's locals or to a part's `{name => self}`
54
+ # @param params [Hash] request param values to be used for populating form field values;
55
+ # these are used in preference over the `values`; optional, defaults to the current
56
+ # request's params
57
+ # @param attributes [Hash] the HTML attributes for the form tag
58
+ # @yieldparam [FormHelper::FormBuilder] f the form builder
59
+ #
60
+ # @overload form_for(url, values: _form_for_values, params: _form_for_params, **attributes)
61
+ # @param url [String] the URL for submitting the form
62
+ # @param values [Hash] values to be used for populating form field values; optional,
63
+ # defaults to the template's locals or to a part's `{name => self}`
64
+ # @param params [Hash] request param values to be used for populating form field values;
65
+ # these are used in preference over the `values`; optional, defaults to the current
66
+ # request's params
67
+ # @param attributes [Hash] the HTML attributes for the form tag
68
+ # @yieldparam [FormHelper::FormBuilder] f the form builder
69
+ #
70
+ # @return [String] the form HTML
71
+ #
72
+ # @see FormHelper
73
+ # @see FormHelper::FormBuilder
74
+ #
75
+ # @example Basic usage
76
+ # <%= form_for("book", "/books", class: "form-horizontal") do |f| %>
77
+ # <div>
78
+ # <%= f.label "title" %>
79
+ # <%= f.text_field "title", class: "form-control" %>
80
+ # </div>
81
+ #
82
+ # <%= f.submit "Create" %>
83
+ # <% end %>
84
+ #
85
+ # =>
86
+ # <form action="/books" method="POST" accept-charset="utf-8" class="form-horizontal">
87
+ # <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
88
+ # <div>
89
+ # <label for="book-title">Title</label>
90
+ # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
91
+ # </div>
92
+ #
93
+ # <button type="submit">Create</button>
94
+ # </form>
95
+ #
96
+ # @example Without base name
97
+ #
98
+ # <%= form_for("/books", class: "form-horizontal") do |f| %>
99
+ # <div>
100
+ # <%= f.label "books.title" %>
101
+ # <%= f.text_field "books.title", class: "form-control" %>
102
+ # </div>
103
+ #
104
+ # <%= f.submit "Create" %>
105
+ # <% end %>
106
+ #
107
+ # =>
108
+ # <form action="/books" method="POST" accept-charset="utf-8" class="form-horizontal">
109
+ # <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
110
+ # <div>
111
+ # <label for="book-title">Title</label>
112
+ # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
113
+ # </div>
114
+ #
115
+ # <button type="submit">Create</button>
116
+ # </form>
117
+ #
118
+ # @example Method override
119
+ # <%= form_for("/books/123", method: :put) do |f|
120
+ # <%= f.text_field "book.title" %>
121
+ # <%= f.submit "Update" %>
122
+ # <% end %>
123
+ #
124
+ # =>
125
+ # <form action="/books/123" accept-charset="utf-8" method="POST">
126
+ # <input type="hidden" name="_method" value="PUT">
127
+ # <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
128
+ # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
129
+ #
130
+ # <button type="submit">Update</button>
131
+ # </form>
132
+ #
133
+ # @example Overriding values
134
+ # <%= form_for("/songs", values: {song: {title: "Envision"}}) do |f| %>
135
+ # <%= f.text_field "song.title" %>
136
+ # <%= f.submit "Create" %>
137
+ # <%= end %>
138
+ #
139
+ # =>
140
+ # <form action="/songs" accept-charset="utf-8" method="POST">
141
+ # <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
142
+ # <input type="text" name="song[title]" id="song-title" value="Envision">
143
+ #
144
+ # <button type="submit">Create</button>
145
+ # </form>
146
+ #
147
+ # @api public
148
+ # @since 2.0.0
149
+ def form_for(base_name, url = nil, values: _form_for_values, params: _form_for_params, **attributes)
150
+ url, base_name = base_name, nil if url.nil?
151
+
152
+ values = Values.new(values: values, params: params, csrf_token: _form_csrf_token)
153
+
154
+ builder = FormBuilder.new(
155
+ base_name: base_name,
156
+ values: values,
157
+ inflector: _context.inflector,
158
+ form_attributes: attributes
159
+ )
160
+
161
+ content = (block_given? ? yield(builder) : "").html_safe
162
+
163
+ builder.call(content, action: url, **attributes)
164
+ end
165
+
166
+ # Returns CSRF meta tags for use via unobtrusive JavaScript (UJS) libraries.
167
+ #
168
+ # @return [String, nil] the tags, if a CSRF token is available, or nil
169
+ #
170
+ # @example
171
+ # csrf_meta_tags
172
+ #
173
+ # =>
174
+ # <meta name="csrf-param" content="_csrf_token">
175
+ # <meta name="csrf-token" content="4a038be85b7603c406dcbfad4b9cdf91ec6ca138ed6441163a07bb0fdfbe25b5">
176
+ #
177
+ # @api public
178
+ # @since 2.0.0
179
+ def csrf_meta_tags
180
+ return unless (token = _form_csrf_token)
181
+
182
+ tag.meta(name: "csrf-param", content: CSRF_TOKEN) +
183
+ tag.meta(name: "csrf-token", content: token)
184
+ end
185
+
186
+ # @api private
187
+ # @since 2.0.0
188
+ def _form_for_values
189
+ if respond_to?(:_locals) # Scope
190
+ _locals
191
+ elsif respond_to?(:_name) # Part
192
+ {_name => self}
193
+ else
194
+ {}
195
+ end
196
+ end
197
+
198
+ # @api private
199
+ # @since 2.0.0
200
+ def _form_for_params
201
+ _context.request.params
202
+ end
203
+
204
+ # @since 2.0.0
205
+ # @api private
206
+ def _form_csrf_token
207
+ return unless _context.request.session_enabled?
208
+
209
+ _context.csrf_token
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+
5
+ module Hanami
6
+ module Middleware
7
+ # The errors app given to {Hanami::Middleware::RenderErrors}, which renders a error responses
8
+ # from HTML pages kept in `public/` or as simple JSON structures.
9
+ #
10
+ # @see Hanami::Middleware::RenderErrors
11
+ #
12
+ # @api private
13
+ # @since 2.1.0
14
+ class PublicErrorsApp
15
+ # @api private
16
+ # @since 2.1.0
17
+ attr_reader :public_path
18
+
19
+ # @api private
20
+ # @since 2.1.0
21
+ def initialize(public_path)
22
+ @public_path = public_path
23
+ end
24
+
25
+ # @api private
26
+ # @since 2.1.0
27
+ def call(env)
28
+ request = Rack::Request.new(env)
29
+ status = request.path_info[1..].to_i
30
+ content_type = request.get_header("HTTP_ACCEPT")
31
+
32
+ default_body = {
33
+ status: status,
34
+ error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500])
35
+ }
36
+
37
+ render(status, content_type, default_body)
38
+ end
39
+
40
+ private
41
+
42
+ def render(status, content_type, default_body)
43
+ body, rendered_content_type = render_content(status, content_type, default_body)
44
+
45
+ [
46
+ status,
47
+ {
48
+ "Content-Type" => "#{rendered_content_type}; charset=utf-8",
49
+ "Content-Length" => body.bytesize.to_s
50
+ },
51
+ [body]
52
+ ]
53
+ end
54
+
55
+ def render_content(status, content_type, default_body)
56
+ if content_type.to_s.start_with?("application/json")
57
+ require "json"
58
+ [JSON.generate(default_body), "application/json"]
59
+ else
60
+ [render_html_content(status, default_body), "text/html"]
61
+ end
62
+ end
63
+
64
+ def render_html_content(status, default_body)
65
+ path = "#{public_path}/#{status}.html"
66
+
67
+ if File.exist?(path)
68
+ File.read(path)
69
+ else
70
+ default_body[:error]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+
5
+ # rubocop:disable Lint/RescueException
6
+
7
+ module Hanami
8
+ module Middleware
9
+ # Rack middleware that rescues errors raised by the app renders friendly error responses, via a
10
+ # given "errors app".
11
+ #
12
+ # By default, this is enabled only in production mode.
13
+ #
14
+ # @see Hanami::Config#render_errors
15
+ # @see Hanani::Middleware::PublicErrorsApp
16
+ #
17
+ # @api private
18
+ # @since 2.1.0
19
+ class RenderErrors
20
+ # @api private
21
+ # @since 2.1.0
22
+ class RenderableException
23
+ attr_reader :exception
24
+ attr_reader :responses
25
+
26
+ # @api private
27
+ # @since 2.1.0
28
+ def initialize(exception, responses:)
29
+ @exception = exception
30
+ @responses = responses
31
+ end
32
+
33
+ # @api private
34
+ # @since 2.1.0
35
+ def rescue_response?
36
+ responses.key?(exception.class.name)
37
+ end
38
+
39
+ # @api private
40
+ # @since 2.1.0
41
+ def status_code
42
+ Rack::Utils.status_code(responses[exception.class.name])
43
+ end
44
+ end
45
+
46
+ # @api private
47
+ # @since 2.1.0
48
+ def initialize(app, config, errors_app)
49
+ @app = app
50
+ @config = config
51
+ @errors_app = errors_app
52
+ end
53
+
54
+ # @api private
55
+ # @since 2.1.0
56
+ def call(env)
57
+ @app.call(env)
58
+ rescue Exception => exception
59
+ request = Rack::Request.new(env)
60
+
61
+ if @config.render_errors
62
+ render_exception(request, exception)
63
+ else
64
+ raise exception
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def render_exception(request, exception)
71
+ renderable = RenderableException.new(exception, responses: @config.render_error_responses)
72
+
73
+ status = renderable.status_code
74
+ request.path_info = "/#{status}"
75
+ request.set_header(Rack::REQUEST_METHOD, "GET")
76
+
77
+ @errors_app.call(request.env)
78
+ rescue Exception => failsafe_error
79
+ # rubocop:disable Style/StderrPuts
80
+ $stderr.puts "Error during exception rendering: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
81
+ # rubocop:enable Style/StderrPuts
82
+
83
+ [
84
+ 500,
85
+ {"Content-Type" => "text/plain; charset=utf-8"},
86
+ ["Internal Server Error"]
87
+ ]
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ # rubocop:enable Lint/RescueException
data/lib/hanami/slice.rb CHANGED
@@ -843,6 +843,7 @@ module Hanami
843
843
  container.config.name = slice_name.to_sym
844
844
  container.config.root = root
845
845
  container.config.provider_dirs = [File.join("config", "providers")]
846
+ container.config.registrations_dir = File.join("config", "registrations")
846
847
 
847
848
  container.config.env = config.env
848
849
  container.config.inflector = config.inflector
@@ -947,6 +948,7 @@ module Hanami
947
948
 
948
949
  require_relative "slice/router"
949
950
 
951
+ slice = self
950
952
  config = self.config
951
953
  rack_monitor = self["rack.monitor"]
952
954
 
@@ -954,17 +956,41 @@ module Hanami
954
956
  inspector: inspector,
955
957
  routes: routes,
956
958
  resolver: config.router.resolver.new(slice: self),
959
+ not_allowed: ROUTER_NOT_ALLOWED_HANDLER,
960
+ not_found: ROUTER_NOT_FOUND_HANDLER,
957
961
  **config.router.options
958
962
  ) do
959
963
  use(rack_monitor)
960
- if Hanami.bundled?("hanami-controller")
961
- use(*config.actions.sessions.middleware) if config.actions.sessions.enabled?
964
+
965
+ use(
966
+ Hanami::Middleware::RenderErrors,
967
+ config,
968
+ Hanami::Middleware::PublicErrorsApp.new(slice.root.join("public"))
969
+ )
970
+
971
+ if config.render_detailed_errors && Hanami.bundled?("hanami-webconsole")
972
+ require "hanami/webconsole"
973
+ use(Hanami::Webconsole::Middleware)
974
+ end
975
+
976
+ if Hanami.bundled?("hanami-controller") && config.actions.sessions.enabled?
977
+ use(*config.actions.sessions.middleware)
962
978
  end
963
979
 
964
980
  middleware_stack.update(config.middleware_stack)
965
981
  end
966
982
  end
967
983
 
984
+ ROUTER_NOT_ALLOWED_HANDLER = -> env, allowed_http_methods {
985
+ raise Hanami::Router::NotAllowedError.new(env, allowed_http_methods)
986
+ }.freeze
987
+ private_constant :ROUTER_NOT_ALLOWED_HANDLER
988
+
989
+ ROUTER_NOT_FOUND_HANDLER = -> env {
990
+ raise Hanami::Router::NotFoundError.new(env)
991
+ }.freeze
992
+ private_constant :ROUTER_NOT_FOUND_HANDLER
993
+
968
994
  # rubocop:enable Metrics/AbcSize
969
995
  end
970
996
  # rubocop:enable Metrics/ModuleLength
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "constants"
3
4
  require_relative "errors"
4
5
 
5
6
  module Hanami
@@ -43,7 +44,7 @@ module Hanami
43
44
 
44
45
  unless subclass.configured_for_slice?(slice)
45
46
  subclass.configure_for_slice(slice)
46
- subclass.configured_for_slices << slice # WIP
47
+ subclass.configured_for_slices << slice
47
48
  end
48
49
  end
49
50
  end
@@ -58,7 +59,7 @@ module Hanami
58
59
 
59
60
  slices = Hanami.app.slices.with_nested + [Hanami.app]
60
61
 
61
- slices.detect { |slice| klass.name.include?(slice.namespace.to_s) }
62
+ slices.detect { |slice| klass.name.start_with?("#{slice.namespace}#{MODULE_DELIMITER}") }
62
63
  end
63
64
  end
64
65
 
@@ -7,7 +7,7 @@ module Hanami
7
7
  # @api private
8
8
  module Version
9
9
  # @api public
10
- VERSION = "2.0.2"
10
+ VERSION = "2.1.0.beta1"
11
11
 
12
12
  # @since 0.9.0
13
13
  # @api private
@@ -29,7 +29,7 @@ module Hanami
29
29
  ROUTER_PARAMS = "router.params"
30
30
  private_constant :ROUTER_PARAMS
31
31
 
32
- CONTENT_LENGTH = "Content-Length"
32
+ CONTENT_LENGTH = "CONTENT_LENGTH"
33
33
  private_constant :CONTENT_LENGTH
34
34
 
35
35
  MILISECOND = "ms"
@@ -38,30 +38,16 @@ module Hanami
38
38
  MICROSECOND = "µs"
39
39
  private_constant :MICROSECOND
40
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
41
+ # Dynamic extension used in development and test environments
57
42
  # @api private
58
43
  module Development
59
44
  private
60
45
 
61
- # @since 1.0.0
46
+ # @since 2.0.0
62
47
  # @api private
63
48
  def data(env, status:, elapsed:)
64
49
  payload = super
50
+ payload.delete(:elapsed_unit)
65
51
  payload[:elapsed] = elapsed > 1000 ? "#{elapsed / 1000}ms" : "#{elapsed}#{MICROSECOND}"
66
52
  payload
67
53
  end
@@ -71,7 +57,7 @@ module Hanami
71
57
  # @since 2.0.0
72
58
  def initialize(logger, env: :development)
73
59
  @logger = logger
74
- extend(env == :production ? Production : Development)
60
+ extend(Development) if %i[development test].include?(env)
75
61
  end
76
62
 
77
63
  # @api private
@@ -116,7 +102,9 @@ module Hanami
116
102
  ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
117
103
  path: "#{env[SCRIPT_NAME]}#{env[PATH_INFO]}",
118
104
  length: extract_content_length(env),
119
- params: env.fetch(ROUTER_PARAMS, EMPTY_PARAMS)
105
+ params: env.fetch(ROUTER_PARAMS, EMPTY_PARAMS),
106
+ elapsed: elapsed,
107
+ elapsed_unit: MICROSECOND,
120
108
  }
121
109
  end
122
110
 
data/lib/hanami.rb CHANGED
@@ -18,7 +18,7 @@ module Hanami
18
18
  def self.loader
19
19
  @loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
20
20
  loader.ignore(
21
- "#{loader.dirs.first}/hanami/{constants,boot,errors,prepare,rake_tasks,setup}.rb"
21
+ "#{loader.dirs.first}/hanami/{constants,boot,errors,extensions/router/errors,prepare,rake_tasks,setup}.rb"
22
22
  )
23
23
  end
24
24
  end