hanami 2.0.3 → 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -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/views.rb +0 -4
  8. data/lib/hanami/config.rb +54 -0
  9. data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
  10. data/lib/hanami/extensions/action.rb +4 -4
  11. data/lib/hanami/extensions/router/errors.rb +58 -0
  12. data/lib/hanami/extensions/view/context.rb +129 -60
  13. data/lib/hanami/extensions/view/part.rb +26 -0
  14. data/lib/hanami/extensions/view/scope.rb +26 -0
  15. data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
  16. data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
  17. data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
  18. data/lib/hanami/extensions/view/standard_helpers.rb +14 -0
  19. data/lib/hanami/extensions.rb +10 -3
  20. data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
  21. data/lib/hanami/helpers/form_helper/values.rb +75 -0
  22. data/lib/hanami/helpers/form_helper.rb +213 -0
  23. data/lib/hanami/middleware/public_errors_app.rb +75 -0
  24. data/lib/hanami/middleware/render_errors.rb +93 -0
  25. data/lib/hanami/slice.rb +27 -2
  26. data/lib/hanami/slice_configurable.rb +3 -2
  27. data/lib/hanami/version.rb +1 -1
  28. data/lib/hanami/web/rack_logger.rb +1 -1
  29. data/lib/hanami.rb +1 -1
  30. data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
  31. data/spec/integration/action/view_rendering_spec.rb +0 -18
  32. data/spec/integration/rack_app/middleware_spec.rb +23 -23
  33. data/spec/integration/rack_app/rack_app_spec.rb +5 -1
  34. data/spec/integration/view/config/default_context_spec.rb +149 -0
  35. data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
  36. data/spec/integration/view/config/part_class_spec.rb +147 -0
  37. data/spec/integration/view/config/part_namespace_spec.rb +103 -0
  38. data/spec/integration/view/config/paths_spec.rb +119 -0
  39. data/spec/integration/view/config/scope_class_spec.rb +147 -0
  40. data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
  41. data/spec/integration/view/config/template_spec.rb +38 -0
  42. data/spec/integration/view/context/request_spec.rb +3 -7
  43. data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
  44. data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
  45. data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
  46. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
  47. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
  48. data/spec/integration/view/slice_configuration_spec.rb +9 -9
  49. data/spec/integration/web/render_detailed_errors_spec.rb +90 -0
  50. data/spec/integration/web/render_errors_spec.rb +240 -0
  51. data/spec/spec_helper.rb +1 -1
  52. data/spec/support/matchers.rb +32 -0
  53. data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -4
  54. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
  55. data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
  56. data/spec/unit/hanami/config/views_spec.rb +0 -18
  57. data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
  58. data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
  59. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
  60. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
  61. data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
  62. data/spec/unit/hanami/version_spec.rb +1 -1
  63. data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
  64. metadata +65 -33
  65. data/spec/integration/action/view_integration_spec.rb +0 -165
  66. data/spec/integration/view/part_namespace_spec.rb +0 -96
  67. data/spec/integration/view/path_spec.rb +0 -56
  68. data/spec/integration/view/template_spec.rb +0 -68
  69. data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
  70. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
  71. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
  72. data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
  73. data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
  74. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
  75. 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
@@ -948,6 +948,7 @@ module Hanami
948
948
 
949
949
  require_relative "slice/router"
950
950
 
951
+ slice = self
951
952
  config = self.config
952
953
  rack_monitor = self["rack.monitor"]
953
954
 
@@ -955,17 +956,41 @@ module Hanami
955
956
  inspector: inspector,
956
957
  routes: routes,
957
958
  resolver: config.router.resolver.new(slice: self),
959
+ not_allowed: ROUTER_NOT_ALLOWED_HANDLER,
960
+ not_found: ROUTER_NOT_FOUND_HANDLER,
958
961
  **config.router.options
959
962
  ) do
960
963
  use(rack_monitor)
961
- if Hanami.bundled?("hanami-controller")
962
- 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)
963
978
  end
964
979
 
965
980
  middleware_stack.update(config.middleware_stack)
966
981
  end
967
982
  end
968
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
+
969
994
  # rubocop:enable Metrics/AbcSize
970
995
  end
971
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.3"
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"
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