hanami 2.0.3 → 2.1.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -2
  3. data/LICENSE.md +1 -1
  4. data/README.md +26 -10
  5. data/hanami.gemspec +2 -2
  6. data/lib/hanami/app.rb +5 -0
  7. data/lib/hanami/config/actions.rb +4 -11
  8. data/lib/hanami/config/assets.rb +84 -0
  9. data/lib/hanami/config/null_config.rb +3 -0
  10. data/lib/hanami/config/views.rb +0 -4
  11. data/lib/hanami/config.rb +71 -5
  12. data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
  13. data/lib/hanami/extensions/action.rb +8 -6
  14. data/lib/hanami/extensions/router/errors.rb +58 -0
  15. data/lib/hanami/extensions/view/context.rb +129 -60
  16. data/lib/hanami/extensions/view/part.rb +26 -0
  17. data/lib/hanami/extensions/view/scope.rb +26 -0
  18. data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
  19. data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
  20. data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
  21. data/lib/hanami/extensions/view/standard_helpers.rb +18 -0
  22. data/lib/hanami/extensions.rb +10 -3
  23. data/lib/hanami/helpers/assets_helper.rb +752 -0
  24. data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
  25. data/lib/hanami/helpers/form_helper/values.rb +75 -0
  26. data/lib/hanami/helpers/form_helper.rb +213 -0
  27. data/lib/hanami/middleware/assets.rb +21 -0
  28. data/lib/hanami/middleware/public_errors_app.rb +75 -0
  29. data/lib/hanami/middleware/render_errors.rb +90 -0
  30. data/lib/hanami/providers/assets.rb +44 -0
  31. data/lib/hanami/rake_tasks.rb +19 -18
  32. data/lib/hanami/settings.rb +1 -1
  33. data/lib/hanami/slice.rb +48 -2
  34. data/lib/hanami/slice_configurable.rb +3 -2
  35. data/lib/hanami/version.rb +1 -1
  36. data/lib/hanami/web/rack_logger.rb +1 -1
  37. data/lib/hanami.rb +3 -3
  38. data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
  39. data/spec/integration/action/view_rendering_spec.rb +0 -18
  40. data/spec/integration/assets/assets_spec.rb +101 -0
  41. data/spec/integration/assets/serve_static_assets_spec.rb +152 -0
  42. data/spec/integration/logging/exception_logging_spec.rb +115 -0
  43. data/spec/integration/logging/notifications_spec.rb +68 -0
  44. data/spec/integration/logging/request_logging_spec.rb +128 -0
  45. data/spec/integration/rack_app/middleware_spec.rb +22 -22
  46. data/spec/integration/rack_app/rack_app_spec.rb +3 -220
  47. data/spec/integration/rake_tasks_spec.rb +107 -0
  48. data/spec/integration/view/config/default_context_spec.rb +149 -0
  49. data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
  50. data/spec/integration/view/config/part_class_spec.rb +147 -0
  51. data/spec/integration/view/config/part_namespace_spec.rb +103 -0
  52. data/spec/integration/view/config/paths_spec.rb +119 -0
  53. data/spec/integration/view/config/scope_class_spec.rb +147 -0
  54. data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
  55. data/spec/integration/view/config/template_spec.rb +38 -0
  56. data/spec/integration/view/context/assets_spec.rb +3 -9
  57. data/spec/integration/view/context/request_spec.rb +3 -7
  58. data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
  59. data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
  60. data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
  61. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
  62. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
  63. data/spec/integration/view/slice_configuration_spec.rb +9 -9
  64. data/spec/integration/web/render_detailed_errors_spec.rb +107 -0
  65. data/spec/integration/web/render_errors_spec.rb +242 -0
  66. data/spec/spec_helper.rb +1 -1
  67. data/spec/support/app_integration.rb +46 -2
  68. data/spec/support/matchers.rb +32 -0
  69. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +24 -36
  70. data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +4 -3
  71. data/spec/unit/hanami/config/actions/default_values_spec.rb +3 -6
  72. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
  73. data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
  74. data/spec/unit/hanami/config/views_spec.rb +0 -18
  75. data/spec/unit/hanami/env_spec.rb +11 -25
  76. data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
  77. data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +109 -0
  78. data/spec/unit/hanami/helpers/assets_helper/audio_tag_spec.rb +132 -0
  79. data/spec/unit/hanami/helpers/assets_helper/favicon_link_tag_spec.rb +91 -0
  80. data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +92 -0
  81. data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +143 -0
  82. data/spec/unit/hanami/helpers/assets_helper/stylesheet_link_tag_spec.rb +126 -0
  83. data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +132 -0
  84. data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
  85. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
  86. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
  87. data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
  88. data/spec/unit/hanami/version_spec.rb +1 -1
  89. data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
  90. metadata +95 -35
  91. data/lib/hanami/assets/app_config.rb +0 -61
  92. data/lib/hanami/assets/config.rb +0 -53
  93. data/spec/integration/action/view_integration_spec.rb +0 -165
  94. data/spec/integration/view/part_namespace_spec.rb +0 -96
  95. data/spec/integration/view/path_spec.rb +0 -56
  96. data/spec/integration/view/template_spec.rb +0 -68
  97. data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
  98. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
  99. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
  100. data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
  101. data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
  102. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
  103. 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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/static"
4
+
5
+ module Hanami
6
+ module Middleware
7
+ class Assets < Rack::Static
8
+ def initialize(app, options = {}, config: Hanami.app.config)
9
+ root = config.actions.public_directory
10
+ urls = [config.assets.path_prefix]
11
+
12
+ defaults = {
13
+ root: root,
14
+ urls: urls
15
+ }
16
+
17
+ super(app, defaults.merge(options))
18
+ end
19
+ end
20
+ end
21
+ 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,90 @@
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
+ raise unless @config.render_errors
60
+
61
+ render_exception(env, exception)
62
+ end
63
+
64
+ private
65
+
66
+ def render_exception(env, exception)
67
+ request = Rack::Request.new(env)
68
+ renderable = RenderableException.new(exception, responses: @config.render_error_responses)
69
+
70
+ status = renderable.status_code
71
+ request.path_info = "/#{status}"
72
+ request.set_header(Rack::REQUEST_METHOD, "GET")
73
+
74
+ @errors_app.call(request.env)
75
+ rescue Exception => failsafe_error
76
+ # rubocop:disable Style/StderrPuts
77
+ $stderr.puts "Error during exception rendering: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
78
+ # rubocop:enable Style/StderrPuts
79
+
80
+ [
81
+ 500,
82
+ {"Content-Type" => "text/plain; charset=utf-8"},
83
+ ["Internal Server Error"]
84
+ ]
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ # rubocop:enable Lint/RescueException
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ # @api private
5
+ module Providers
6
+ # Provider source to register routes helper component in Hanami slices.
7
+ #
8
+ # @see Hanami::Slice::RoutesHelper
9
+ #
10
+ # @api private
11
+ # @since 2.0.0
12
+ class Assets < Dry::System::Provider::Source
13
+ # @api private
14
+ def self.for_slice(slice)
15
+ Class.new(self) do |klass|
16
+ klass.instance_variable_set(:@slice, slice)
17
+ end
18
+ end
19
+
20
+ # @api private
21
+ def self.slice
22
+ @slice || Hanami.app
23
+ end
24
+
25
+ # @api private
26
+ def prepare
27
+ require "hanami/assets"
28
+ end
29
+
30
+ # @api private
31
+ def start
32
+ assets = Hanami::Assets.new(config: slice.config.assets)
33
+
34
+ register(:assets, assets)
35
+ end
36
+
37
+ private
38
+
39
+ def slice
40
+ self.class.slice
41
+ end
42
+ end
43
+ end
44
+ end
@@ -10,7 +10,7 @@ Hanami::CLI::RakeTasks.register_tasks do
10
10
 
11
11
  # Ruby ecosystem compatibility
12
12
  #
13
- # Most of the SaaS automatic tasks are designed after Ruby on Rails.
13
+ # Most of the hosting SaaS automatic tasks are designed after Ruby on Rails.
14
14
  # They expect the following Rake tasks to be present:
15
15
  #
16
16
  # * db:migrate
@@ -20,31 +20,32 @@ Hanami::CLI::RakeTasks.register_tasks do
20
20
  #
21
21
  # ===
22
22
  #
23
- # These Rake tasks aren't listed when someone runs `rake -T`, because we
24
- # want to encourage developers to use `hanami` commands.
23
+ # These Rake tasks are **NOT** listed when someone runs `rake -T`, because we
24
+ # want to encourage developers to use `hanami` CLI commands.
25
25
  #
26
- # In order to migrate the database or precompile assets a developer should
27
- # use:
26
+ # In order to migrate the database or compile assets a developer should use:
28
27
  #
29
28
  # * hanami db migrate
30
- # * hanami assets precompile
29
+ # * hanami assets compile
31
30
  #
32
31
  # This is the preferred way to run Hanami command line tasks.
33
32
  # Please use them when you're in control of your deployment environment.
34
33
  #
35
34
  # If you're not in control and your deployment requires these "standard"
36
35
  # Rake tasks, they are here to solve this only specific problem.
37
- namespace :db do
38
- task :migrate do
39
- # TODO(@jodosha): Enable when we'll integrate with ROM
40
- # run_hanami_command("db migrate")
41
- end
42
- end
43
-
44
- namespace :assets do
45
- task :precompile do
46
- # TODO(@jodosha): Enable when we'll integrate with hanami-assets
47
- # run_hanami_command("assets precompile")
36
+ #
37
+ # namespace :db do
38
+ # task :migrate do
39
+ # # TODO(@jodosha): Enable when we'll integrate with ROM
40
+ # # run_hanami_command("db migrate")
41
+ # end
42
+ # end
43
+
44
+ if Hanami.bundled?("hanami-assets")
45
+ namespace :assets do
46
+ task :precompile do
47
+ run_hanami_command("assets compile")
48
+ end
48
49
  end
49
50
  end
50
51
 
@@ -53,7 +54,7 @@ Hanami::CLI::RakeTasks.register_tasks do
53
54
  @_hanami_cli_bundler = Hanami::CLI::Bundler.new
54
55
 
55
56
  def run_hanami_command(command)
56
- @_hanami_cli_bundler.exec(command)
57
+ @_hanami_cli_bundler.hanami_exec(command)
57
58
  end
58
59
  end
59
60
 
@@ -45,7 +45,7 @@ module Hanami
45
45
  #
46
46
  # Setting values are loaded from a configurable store, which defaults to
47
47
  # {Hanami::Settings::EnvStore}, which fetches the values from equivalent upper-cased keys in
48
- # `ENV`. You can configue an alternative store via {Hanami::Config#settings_store}. Setting stores
48
+ # `ENV`. You can configure an alternative store via {Hanami::Config#settings_store}. Setting stores
49
49
  # must implement a `#fetch` method with the same signature as `Hash#fetch`.
50
50
  #
51
51
  # [dry-c]: https://dry-rb.org/gems/dry-configurable/
data/lib/hanami/slice.rb CHANGED
@@ -948,24 +948,70 @@ 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
+ render_errors = render_errors?
956
+ render_detailed_errors = render_detailed_errors?
957
+
958
+ error_handlers = {}.tap do |hsh|
959
+ if render_errors || render_detailed_errors
960
+ hsh[:not_allowed] = ROUTER_NOT_ALLOWED_HANDLER
961
+ hsh[:not_found] = ROUTER_NOT_FOUND_HANDLER
962
+ end
963
+ end
964
+
954
965
  Slice::Router.new(
955
966
  inspector: inspector,
956
967
  routes: routes,
957
968
  resolver: config.router.resolver.new(slice: self),
969
+ **error_handlers,
958
970
  **config.router.options
959
971
  ) do
960
972
  use(rack_monitor)
961
- if Hanami.bundled?("hanami-controller")
962
- use(*config.actions.sessions.middleware) if config.actions.sessions.enabled?
973
+
974
+ use(
975
+ Hanami::Middleware::RenderErrors,
976
+ config,
977
+ Hanami::Middleware::PublicErrorsApp.new(slice.root.join("public"))
978
+ )
979
+
980
+ if render_detailed_errors
981
+ require "hanami/webconsole"
982
+ use(Hanami::Webconsole::Middleware, config)
983
+ end
984
+
985
+ if Hanami.bundled?("hanami-controller") && config.actions.sessions.enabled?
986
+ use(*config.actions.sessions.middleware)
987
+ end
988
+
989
+ if Hanami.bundled?("hanami-assets") && config.assets.serve
990
+ use(Hanami::Middleware::Assets)
963
991
  end
964
992
 
965
993
  middleware_stack.update(config.middleware_stack)
966
994
  end
967
995
  end
968
996
 
997
+ def render_errors?
998
+ config.render_errors
999
+ end
1000
+
1001
+ def render_detailed_errors?
1002
+ config.render_detailed_errors && Hanami.bundled?("hanami-webconsole")
1003
+ end
1004
+
1005
+ ROUTER_NOT_ALLOWED_HANDLER = -> env, allowed_http_methods {
1006
+ raise Hanami::Router::NotAllowedError.new(env, allowed_http_methods)
1007
+ }.freeze
1008
+ private_constant :ROUTER_NOT_ALLOWED_HANDLER
1009
+
1010
+ ROUTER_NOT_FOUND_HANDLER = -> env {
1011
+ raise Hanami::Router::NotFoundError.new(env)
1012
+ }.freeze
1013
+ private_constant :ROUTER_NOT_FOUND_HANDLER
1014
+
969
1015
  # rubocop:enable Metrics/AbcSize
970
1016
  end
971
1017
  # 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