hanami 2.0.0.alpha8 → 2.0.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 (224) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +442 -241
  3. data/FEATURES.md +30 -9
  4. data/README.md +1 -3
  5. data/hanami.gemspec +21 -11
  6. data/lib/hanami/app.rb +141 -0
  7. data/lib/hanami/assets/application_configuration.rb +10 -4
  8. data/lib/hanami/configuration/actions/content_security_policy.rb +5 -5
  9. data/lib/hanami/configuration/actions/cookies.rb +2 -2
  10. data/lib/hanami/configuration/actions.rb +10 -4
  11. data/lib/hanami/configuration/logger.rb +4 -4
  12. data/lib/hanami/configuration/router.rb +2 -6
  13. data/lib/hanami/configuration/sessions.rb +1 -1
  14. data/lib/hanami/configuration/views.rb +9 -4
  15. data/lib/hanami/configuration.rb +118 -46
  16. data/lib/hanami/constants.rb +24 -2
  17. data/lib/hanami/errors.rb +1 -1
  18. data/lib/hanami/{application → extensions}/action/slice_configured_action.rb +9 -9
  19. data/lib/hanami/extensions/action.rb +79 -0
  20. data/lib/hanami/extensions/view/context.rb +106 -0
  21. data/lib/hanami/{application → extensions}/view/slice_configured_context.rb +10 -10
  22. data/lib/hanami/{application → extensions}/view/slice_configured_view.rb +12 -6
  23. data/lib/hanami/extensions/view.rb +33 -0
  24. data/lib/hanami/extensions.rb +10 -0
  25. data/lib/hanami/providers/inflector.rb +13 -0
  26. data/lib/hanami/providers/logger.rb +13 -0
  27. data/lib/hanami/providers/rack.rb +27 -0
  28. data/lib/hanami/providers/routes.rb +33 -0
  29. data/lib/hanami/providers/settings.rb +23 -0
  30. data/lib/hanami/rake_tasks.rb +61 -0
  31. data/lib/hanami/routes.rb +51 -0
  32. data/lib/hanami/server.rb +1 -1
  33. data/lib/hanami/settings/dotenv_store.rb +58 -0
  34. data/lib/hanami/settings.rb +90 -0
  35. data/lib/hanami/setup.rb +4 -2
  36. data/lib/hanami/{application → slice}/router.rb +18 -13
  37. data/lib/hanami/slice/routes_helper.rb +37 -0
  38. data/lib/hanami/{application → slice}/routing/middleware/stack.rb +43 -5
  39. data/lib/hanami/slice/routing/resolver.rb +97 -0
  40. data/lib/hanami/{application → slice}/view_name_inferrer.rb +3 -3
  41. data/lib/hanami/slice.rb +246 -73
  42. data/lib/hanami/slice_configurable.rb +4 -17
  43. data/lib/hanami/slice_name.rb +6 -6
  44. data/lib/hanami/slice_registrar.rb +119 -0
  45. data/lib/hanami/version.rb +1 -1
  46. data/lib/hanami/web/rack_logger.rb +1 -1
  47. data/lib/hanami.rb +34 -26
  48. data/spec/integration/application_middleware_stack_spec.rb +84 -0
  49. data/spec/integration/assets/cdn_spec.rb +48 -0
  50. data/spec/integration/assets/fingerprint_spec.rb +42 -0
  51. data/spec/integration/assets/helpers_spec.rb +50 -0
  52. data/spec/integration/assets/serve_spec.rb +70 -0
  53. data/spec/integration/assets/subresource_integrity_spec.rb +54 -0
  54. data/spec/integration/body_parsers_spec.rb +50 -0
  55. data/spec/integration/cli/assets/precompile_spec.rb +147 -0
  56. data/spec/integration/cli/assets_spec.rb +14 -0
  57. data/spec/integration/cli/console_spec.rb +105 -0
  58. data/spec/integration/cli/db/apply_spec.rb +74 -0
  59. data/spec/integration/cli/db/console_spec.rb +40 -0
  60. data/spec/integration/cli/db/create_spec.rb +50 -0
  61. data/spec/integration/cli/db/drop_spec.rb +54 -0
  62. data/spec/integration/cli/db/migrate_spec.rb +108 -0
  63. data/spec/integration/cli/db/prepare_spec.rb +36 -0
  64. data/spec/integration/cli/db/rollback_spec.rb +96 -0
  65. data/spec/integration/cli/db/version_spec.rb +38 -0
  66. data/spec/integration/cli/db_spec.rb +21 -0
  67. data/spec/integration/cli/destroy/action_spec.rb +143 -0
  68. data/spec/integration/cli/destroy/app_spec.rb +118 -0
  69. data/spec/integration/cli/destroy/mailer_spec.rb +74 -0
  70. data/spec/integration/cli/destroy/migration_spec.rb +70 -0
  71. data/spec/integration/cli/destroy/model_spec.rb +113 -0
  72. data/spec/integration/cli/destroy_spec.rb +18 -0
  73. data/spec/integration/cli/generate/action_spec.rb +469 -0
  74. data/spec/integration/cli/generate/app_spec.rb +215 -0
  75. data/spec/integration/cli/generate/mailer_spec.rb +189 -0
  76. data/spec/integration/cli/generate/migration_spec.rb +72 -0
  77. data/spec/integration/cli/generate/model_spec.rb +290 -0
  78. data/spec/integration/cli/generate/secret_spec.rb +56 -0
  79. data/spec/integration/cli/generate_spec.rb +19 -0
  80. data/spec/integration/cli/new/database_spec.rb +235 -0
  81. data/spec/integration/cli/new/hanami_head_spec.rb +27 -0
  82. data/spec/integration/cli/new/template_spec.rb +118 -0
  83. data/spec/integration/cli/new/test_spec.rb +274 -0
  84. data/spec/integration/cli/new_spec.rb +970 -0
  85. data/spec/integration/cli/plugins_spec.rb +39 -0
  86. data/spec/integration/cli/routes_spec.rb +49 -0
  87. data/spec/integration/cli/server_spec.rb +626 -0
  88. data/spec/integration/cli/version_spec.rb +85 -0
  89. data/spec/integration/early_hints_spec.rb +35 -0
  90. data/spec/integration/handle_exceptions_spec.rb +244 -0
  91. data/spec/integration/head_spec.rb +89 -0
  92. data/spec/integration/http_headers_spec.rb +29 -0
  93. data/spec/integration/mailer_spec.rb +32 -0
  94. data/spec/integration/middleware_spec.rb +81 -0
  95. data/spec/integration/mount_applications_spec.rb +88 -0
  96. data/spec/integration/project_initializers_spec.rb +40 -0
  97. data/spec/integration/rackup_spec.rb +35 -0
  98. data/spec/integration/rake/with_minitest_spec.rb +67 -0
  99. data/spec/integration/rake/with_rspec_spec.rb +69 -0
  100. data/spec/integration/routing_helpers_spec.rb +61 -0
  101. data/spec/integration/security/content_security_policy_spec.rb +46 -0
  102. data/spec/integration/security/csrf_protection_spec.rb +42 -0
  103. data/spec/integration/security/force_ssl_spec.rb +29 -0
  104. data/spec/integration/security/x_content_type_options_spec.rb +46 -0
  105. data/spec/integration/security/x_frame_options_spec.rb +46 -0
  106. data/spec/integration/security/x_xss_protection_spec.rb +46 -0
  107. data/spec/integration/send_file_spec.rb +51 -0
  108. data/spec/integration/sessions_spec.rb +247 -0
  109. data/spec/integration/static_middleware_spec.rb +21 -0
  110. data/spec/integration/streaming_spec.rb +41 -0
  111. data/spec/integration/unsafe_send_file_spec.rb +52 -0
  112. data/spec/isolation/hanami/application/already_configured_spec.rb +19 -0
  113. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +10 -0
  114. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +14 -0
  115. data/spec/isolation/hanami/application/not_configured_spec.rb +9 -0
  116. data/spec/isolation/hanami/application/routes/configured_spec.rb +44 -0
  117. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +16 -0
  118. data/spec/isolation/hanami/boot/success_spec.rb +50 -0
  119. data/spec/new_integration/action/configuration_spec.rb +26 -0
  120. data/spec/new_integration/action/cookies_spec.rb +58 -0
  121. data/spec/new_integration/action/csrf_protection_spec.rb +54 -0
  122. data/spec/new_integration/action/routes_spec.rb +73 -0
  123. data/spec/new_integration/action/sessions_spec.rb +50 -0
  124. data/spec/new_integration/action/view_integration_spec.rb +165 -0
  125. data/spec/new_integration/action/view_rendering/automatic_rendering_spec.rb +247 -0
  126. data/spec/new_integration/action/view_rendering/paired_view_inference_spec.rb +115 -0
  127. data/spec/new_integration/action/view_rendering_spec.rb +107 -0
  128. data/spec/new_integration/code_loading/loading_from_app_spec.rb +152 -0
  129. data/spec/new_integration/code_loading/loading_from_slice_spec.rb +165 -0
  130. data/spec/new_integration/container/application_routes_helper_spec.rb +48 -0
  131. data/spec/new_integration/container/auto_injection_spec.rb +53 -0
  132. data/spec/new_integration/container/auto_registration_spec.rb +86 -0
  133. data/spec/new_integration/container/autoloader_spec.rb +80 -0
  134. data/spec/new_integration/container/imports_spec.rb +253 -0
  135. data/spec/new_integration/container/prepare_container_spec.rb +123 -0
  136. data/spec/new_integration/container/shutdown_spec.rb +91 -0
  137. data/spec/new_integration/container/standard_bootable_components_spec.rb +124 -0
  138. data/spec/new_integration/rack_app/middleware_spec.rb +215 -0
  139. data/spec/new_integration/rack_app/non_booted_rack_app_spec.rb +105 -0
  140. data/spec/new_integration/rack_app/rack_app_spec.rb +524 -0
  141. data/spec/new_integration/settings_spec.rb +115 -0
  142. data/spec/new_integration/slices/external_slice_spec.rb +92 -0
  143. data/spec/new_integration/slices/slice_configuration_spec.rb +40 -0
  144. data/spec/new_integration/slices/slice_routing_spec.rb +226 -0
  145. data/spec/new_integration/slices/slice_settings_spec.rb +141 -0
  146. data/spec/new_integration/slices_spec.rb +101 -0
  147. data/spec/new_integration/view/configuration_spec.rb +49 -0
  148. data/spec/new_integration/view/context/assets_spec.rb +67 -0
  149. data/spec/new_integration/view/context/inflector_spec.rb +48 -0
  150. data/spec/new_integration/view/context/request_spec.rb +61 -0
  151. data/spec/new_integration/view/context/routes_spec.rb +86 -0
  152. data/spec/new_integration/view/context/settings_spec.rb +50 -0
  153. data/spec/new_integration/view/inflector_spec.rb +57 -0
  154. data/spec/new_integration/view/part_namespace_spec.rb +96 -0
  155. data/spec/new_integration/view/path_spec.rb +56 -0
  156. data/spec/new_integration/view/template_spec.rb +68 -0
  157. data/spec/new_integration/view/views_spec.rb +103 -0
  158. data/spec/spec_helper.rb +16 -0
  159. data/spec/support/app_integration.rb +91 -0
  160. data/spec/support/coverage.rb +1 -0
  161. data/spec/support/fixtures/hanami-plugin/Gemfile +8 -0
  162. data/spec/support/fixtures/hanami-plugin/README.md +35 -0
  163. data/spec/support/fixtures/hanami-plugin/Rakefile +4 -0
  164. data/spec/support/fixtures/hanami-plugin/bin/console +15 -0
  165. data/spec/support/fixtures/hanami-plugin/bin/setup +8 -0
  166. data/spec/support/fixtures/hanami-plugin/hanami-plugin.gemspec +28 -0
  167. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/cli.rb +19 -0
  168. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/version.rb +7 -0
  169. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin.rb +8 -0
  170. data/spec/support/rspec.rb +27 -0
  171. data/spec/support/shared_examples/cli/generate/app.rb +494 -0
  172. data/spec/support/shared_examples/cli/generate/migration.rb +32 -0
  173. data/spec/support/shared_examples/cli/generate/model.rb +81 -0
  174. data/spec/support/shared_examples/cli/new.rb +97 -0
  175. data/spec/unit/hanami/configuration/actions/content_security_policy_spec.rb +102 -0
  176. data/spec/unit/hanami/configuration/actions/cookies_spec.rb +46 -0
  177. data/spec/unit/hanami/configuration/actions/csrf_protection_spec.rb +57 -0
  178. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +52 -0
  179. data/spec/unit/hanami/configuration/actions/sessions_spec.rb +50 -0
  180. data/spec/unit/hanami/configuration/actions_spec.rb +78 -0
  181. data/spec/unit/hanami/configuration/base_url_spec.rb +25 -0
  182. data/spec/unit/hanami/configuration/inflector_spec.rb +35 -0
  183. data/spec/unit/hanami/configuration/logger_spec.rb +203 -0
  184. data/spec/unit/hanami/configuration/views_spec.rb +120 -0
  185. data/spec/unit/hanami/configuration_spec.rb +43 -0
  186. data/spec/unit/hanami/env_spec.rb +54 -0
  187. data/spec/unit/hanami/routes_spec.rb +25 -0
  188. data/spec/unit/hanami/settings/dotenv_store_spec.rb +119 -0
  189. data/spec/unit/hanami/settings_spec.rb +56 -0
  190. data/spec/unit/hanami/slice_configurable_spec.rb +104 -0
  191. data/spec/unit/hanami/slice_name_spec.rb +47 -0
  192. data/spec/unit/hanami/slice_spec.rb +17 -0
  193. data/spec/unit/hanami/version_spec.rb +7 -0
  194. data/spec/unit/hanami/web/rack_logger_spec.rb +78 -0
  195. metadata +353 -57
  196. data/lib/hanami/application/action.rb +0 -72
  197. data/lib/hanami/application/container/providers/inflector.rb +0 -7
  198. data/lib/hanami/application/container/providers/logger.rb +0 -7
  199. data/lib/hanami/application/container/providers/rack_logger.rb +0 -15
  200. data/lib/hanami/application/container/providers/rack_monitor.rb +0 -12
  201. data/lib/hanami/application/container/providers/routes_helper.rb +0 -9
  202. data/lib/hanami/application/container/providers/settings.rb +0 -7
  203. data/lib/hanami/application/routes.rb +0 -55
  204. data/lib/hanami/application/routes_helper.rb +0 -34
  205. data/lib/hanami/application/routing/resolver/node.rb +0 -50
  206. data/lib/hanami/application/routing/resolver/trie.rb +0 -59
  207. data/lib/hanami/application/routing/resolver.rb +0 -87
  208. data/lib/hanami/application/routing/router.rb +0 -36
  209. data/lib/hanami/application/settings/dotenv_store.rb +0 -60
  210. data/lib/hanami/application/settings.rb +0 -93
  211. data/lib/hanami/application/slice_registrar.rb +0 -106
  212. data/lib/hanami/application/view/context.rb +0 -95
  213. data/lib/hanami/application/view.rb +0 -24
  214. data/lib/hanami/application.rb +0 -273
  215. data/lib/hanami/cli/application/cli.rb +0 -40
  216. data/lib/hanami/cli/application/command.rb +0 -47
  217. data/lib/hanami/cli/application/commands/console.rb +0 -81
  218. data/lib/hanami/cli/application/commands.rb +0 -16
  219. data/lib/hanami/cli/base_command.rb +0 -48
  220. data/lib/hanami/cli/commands/command.rb +0 -171
  221. data/lib/hanami/cli/commands/server.rb +0 -88
  222. data/lib/hanami/cli/commands.rb +0 -65
  223. data/lib/hanami/configuration/middleware.rb +0 -20
  224. data/lib/hanami/configuration/source_dirs.rb +0 -42
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ if Hanami.bundled?("hanami-controller")
4
+ require_relative "./extensions/action"
5
+ end
6
+
7
+ if Hanami.bundled?("hanami-view")
8
+ require_relative "./extensions/view"
9
+ require_relative "./extensions/view/context"
10
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/provider/source"
4
+
5
+ module Hanami
6
+ module Providers
7
+ class Inflector < Dry::System::Provider::Source
8
+ def start
9
+ register :inflector, Hanami.app.inflector
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/provider/source"
4
+
5
+ module Hanami
6
+ module Providers
7
+ class Logger < Dry::System::Provider::Source
8
+ def start
9
+ register :logger, Hanami.app.configuration.logger_instance
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/provider/source"
4
+
5
+ module Hanami
6
+ module Providers
7
+ class Rack < Dry::System::Provider::Source
8
+ def prepare
9
+ require "dry/monitor"
10
+ require "hanami/web/rack_logger"
11
+ end
12
+
13
+ def start
14
+ target.start :logger
15
+
16
+ notifications = target[:notifications]
17
+
18
+ monitor_middleware = Dry::Monitor::Rack::Middleware.new(notifications)
19
+
20
+ rack_logger = Hanami::Web::RackLogger.new(target[:logger])
21
+ rack_logger.attach(monitor_middleware)
22
+
23
+ register "monitor", monitor_middleware
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/provider/source"
4
+
5
+ module Hanami
6
+ module Providers
7
+ class Routes < Dry::System::Provider::Source
8
+ def self.for_slice(slice)
9
+ Class.new(self) do |klass|
10
+ klass.instance_variable_set(:@slice, slice)
11
+ end
12
+ end
13
+
14
+ def self.slice
15
+ @slice || Hanami.app
16
+ end
17
+
18
+ def prepare
19
+ require "hanami/slice/routes_helper"
20
+ end
21
+
22
+ def start
23
+ # Register a lazy instance of RoutesHelper to ensure we don't load prematurely
24
+ # load the router during the process of booting. This ensures the router's
25
+ # resolver can run strict action key checks once when it runs on a fully booted
26
+ # slice.
27
+ register :routes do
28
+ Hanami::Slice::RoutesHelper.new(self.class.slice.router)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/provider/source"
4
+
5
+ module Hanami
6
+ module Providers
7
+ class Settings < Dry::System::Provider::Source
8
+ def self.for_slice(slice)
9
+ Class.new(self) do |klass|
10
+ klass.instance_variable_set(:@slice, slice)
11
+ end
12
+ end
13
+
14
+ def self.slice
15
+ @slice || Hanami.app
16
+ end
17
+
18
+ def start
19
+ register :settings, self.class.slice.settings
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/cli/rake_tasks"
4
+ require "hanami/cli/command_line"
5
+
6
+ Hanami::CLI::RakeTasks.register_tasks do
7
+ @_hanami_command_line = Hanami::CLI::CommandLine.new
8
+
9
+ desc "Load the app environment"
10
+ task :environment do
11
+ require "hanami/prepare"
12
+ end
13
+
14
+ # Ruby ecosystem compatibility
15
+ #
16
+ # Most of the SaaS automatic tasks are designed after Ruby on Rails.
17
+ # They expect the following Rake tasks to be present:
18
+ #
19
+ # * db:migrate
20
+ # * assets:precompile
21
+ #
22
+ # See https://github.com/heroku/heroku-buildpack-ruby/issues/442
23
+ #
24
+ # ===
25
+ #
26
+ # These Rake tasks aren't listed when someone runs `rake -T`, because we
27
+ # want to encourage developers to use `hanami` commands.
28
+ #
29
+ # In order to migrate the database or precompile assets a developer should
30
+ # use:
31
+ #
32
+ # * hanami db migrate
33
+ # * hanami assets precompile
34
+ #
35
+ # This is the preferred way to run Hanami command line tasks.
36
+ # Please use them when you're in control of your deployment environment.
37
+ #
38
+ # If you're not in control and your deployment requires these "standard"
39
+ # Rake tasks, they are here to solve this only specific problem.
40
+ namespace :db do
41
+ task :migrate do
42
+ # TODO(@jodosha): Enable when we'll integrate with ROM
43
+ # run_hanami_command("db migrate")
44
+ end
45
+ end
46
+
47
+ namespace :assets do
48
+ task :precompile do
49
+ # TODO(@jodosha): Enable when we'll integrate with hanami-assets
50
+ # run_hanami_command("assets precompile")
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def run_hanami_command(command)
57
+ @_hanami_command_line.call(command)
58
+ end
59
+ end
60
+
61
+ Hanami::CLI::RakeTasks.install_tasks
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ # App routes
5
+ #
6
+ # Users are expected to inherit from this class to define their app
7
+ # routes.
8
+ #
9
+ # @example
10
+ # # config/routes.rb
11
+ # # frozen_string_literal: true
12
+ #
13
+ # require "hanami/routes"
14
+ #
15
+ # module MyApp
16
+ # class Routes < Hanami::Routes
17
+ # define do
18
+ # root to: "home.show"
19
+ # end
20
+ # end
21
+ # end
22
+ #
23
+ # See {Hanami::Slice::Router} for the syntax allowed within the
24
+ # `define` block.
25
+ #
26
+ # @see Hanami::Slice::Router
27
+ # @since 2.0.0
28
+ class Routes
29
+ # Defines app routes
30
+ #
31
+ # @yield DSL syntax to define app routes executed in the context
32
+ # of {Hanami::Slice::Router}
33
+ #
34
+ # @return [Proc]
35
+ def self.define(&block)
36
+ @_routes = block
37
+ end
38
+
39
+ # @api private
40
+ def self.routes
41
+ @_routes || raise(<<~MSG)
42
+ Routes need to be defined before being able to fetch them. E.g.,
43
+ define do
44
+ slice :main, at: "/" do
45
+ root to: "home.show"
46
+ end
47
+ end
48
+ MSG
49
+ end
50
+ end
51
+ end
data/lib/hanami/server.rb CHANGED
@@ -10,7 +10,7 @@ module Hanami
10
10
  #
11
11
  # `bundle exec hanami server`
12
12
  #
13
- # It runs the application, by using the server specified in your `Gemfile`
13
+ # It runs the app, by using the server specified in your `Gemfile`
14
14
  # (eg. Puma or Unicorn).
15
15
  #
16
16
  # @since 0.8.0
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/constants"
4
+
5
+ module Hanami
6
+ class Settings
7
+ # Default app settings store.
8
+ #
9
+ # Uses [dotenv](https://github.com/bkeepers/dotenv) (if available) to load
10
+ # .env files and then loads settings from ENV. For a given `HANAMI_ENV`
11
+ # environment, the following `.env` files are looked up in the following order:
12
+ #
13
+ # - .env.{environment}.local
14
+ # - .env.local (except if the environment is `test`)
15
+ # - .env.{environment}
16
+ # - .env
17
+ #
18
+ # @since 2.0.0
19
+ # @api private
20
+ class DotenvStore
21
+ Undefined = Dry::Core::Constants::Undefined
22
+
23
+ attr_reader :store,
24
+ :hanami_env
25
+
26
+ def initialize(store: ENV, hanami_env: Hanami.env)
27
+ @store = store
28
+ @hanami_env = hanami_env
29
+ end
30
+
31
+ def fetch(name, default_value = Undefined, &block)
32
+ name = name.to_s.upcase
33
+ args = (default_value == Undefined) ? [name] : [name, default_value]
34
+
35
+ store.fetch(*args, &block)
36
+ end
37
+
38
+ def with_dotenv_loaded
39
+ require "dotenv"
40
+ Dotenv.load(*dotenv_files) if defined?(Dotenv)
41
+ self
42
+ rescue LoadError
43
+ self
44
+ end
45
+
46
+ private
47
+
48
+ def dotenv_files
49
+ [
50
+ ".env.#{hanami_env}.local",
51
+ (".env.local" unless hanami_env == :test),
52
+ ".env.#{hanami_env}",
53
+ ".env"
54
+ ].compact
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/constants"
5
+
6
+ module Hanami
7
+ # App settings
8
+ #
9
+ # Users are expected to inherit from this class to define their app settings.
10
+ #
11
+ # @example
12
+ # # config/settings.rb
13
+ # # frozen_string_literal: true
14
+ #
15
+ # require "hanami/settings"
16
+ # require "my_app/types"
17
+ #
18
+ # module MyApp
19
+ # class Settings < Hanami::Settings
20
+ # setting :database_url
21
+ # setting :feature_flag, default: false, constructor: Types::Params::Bool
22
+ # end
23
+ # end
24
+ #
25
+ # Settings are defined with
26
+ # [dry-configurable](https://dry-rb.org/gems/dry-configurable/), so you can
27
+ # take a look there to see the supported syntax.
28
+ #
29
+ # Users work with an instance of this class made available within the
30
+ # `settings` key in the container. The instance gets its settings populated
31
+ # from a configurable store, which defaults to
32
+ # {Hanami::Settings::DotenvStore}.
33
+ #
34
+ # A different store can be set through the `settings_store` Hanami
35
+ # configuration option. All it needs to do is implementing a `#fetch` method
36
+ # with the same signature as `Hash#fetch`.
37
+ #
38
+ # @see Hanami::Settings::DotenvStore
39
+ # @since 2.0.0
40
+ class Settings
41
+ # Exception for errors in the definition of settings.
42
+ #
43
+ # Its message collects all the individual errors that can be raised for
44
+ # each setting.
45
+ InvalidSettingsError = Class.new(StandardError) do
46
+ def initialize(errors)
47
+ @errors = errors
48
+ end
49
+
50
+ def to_s
51
+ <<~STR.strip
52
+ Could not initialize settings. The following settings were invalid:
53
+
54
+ #{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
55
+ STR
56
+ end
57
+ end
58
+
59
+ # @api private
60
+ EMPTY_STORE = Dry::Core::Constants::EMPTY_HASH
61
+
62
+ include Dry::Configurable
63
+
64
+ # @api private
65
+ def initialize(store = EMPTY_STORE)
66
+ errors = config._settings.map(&:name).reduce({}) do |errs, name|
67
+ public_send("#{name}=", store.fetch(name) { Dry::Core::Constants::Undefined })
68
+ errs
69
+ rescue => e # rubocop:disable Style/RescueStandardError
70
+ errs.merge(name => e)
71
+ end
72
+
73
+ raise InvalidSettingsError, errors if errors.any?
74
+ end
75
+
76
+ private
77
+
78
+ def method_missing(name, *args, &block)
79
+ if config.respond_to?(name)
80
+ config.send(name, *args, &block)
81
+ else
82
+ super
83
+ end
84
+ end
85
+
86
+ def respond_to_missing?(name, _include_all = false)
87
+ config.respond_to?(name) || super
88
+ end
89
+ end
90
+ end
data/lib/hanami/setup.rb CHANGED
@@ -4,6 +4,8 @@ require "bundler/setup"
4
4
  require "hanami"
5
5
 
6
6
  begin
7
- require File.join(Dir.pwd, "config/application")
8
- rescue LoadError # rubocop:disable Lint/SuppressedException
7
+ app_require_path = File.join(Dir.pwd, "config/app")
8
+ require app_require_path
9
+ rescue LoadError => e
10
+ raise e unless e.path == app_require_path
9
11
  end
@@ -1,13 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami/router"
4
- require "hanami/application/routing/middleware/stack"
4
+ require_relative "routing/middleware/stack"
5
5
 
6
6
  module Hanami
7
- class Application
8
- # Hanami application router
7
+ class Slice
8
+ # Hanami app router
9
9
  # @since 2.0.0
10
10
  class Router < ::Hanami::Router
11
+ # @api private
12
+ attr_reader :middleware_stack
13
+
11
14
  # @since 2.0.0
12
15
  # @api private
13
16
  def initialize(routes:, middleware_stack: Routing::Middleware::Stack.new, **kwargs, &blk)
@@ -27,32 +30,34 @@ module Hanami
27
30
 
28
31
  # @since 2.0.0
29
32
  # @api private
30
- def use(middleware, *args, &blk)
31
- @middleware_stack.use(middleware, *args, &blk)
33
+ def use(...)
34
+ middleware_stack.use(...)
32
35
  end
33
36
 
34
37
  # @since 2.0.0
35
38
  # @api private
36
39
  def scope(*args, &blk)
37
- @middleware_stack.with(args.first) do
40
+ middleware_stack.with(args.first) do
38
41
  super
39
42
  end
40
43
  end
41
44
 
42
45
  # @since 2.0.0
43
- def slice(name, at:, &blk)
44
- path = prefixed_path(at)
45
- @resolver.register_slice_at_path(name, path)
46
+ def slice(slice_name, at:, &blk)
47
+ blk ||= @resolver.find_slice(slice_name).routes
46
48
 
47
- scope(path, &blk)
49
+ prev_resolver = @resolver
50
+ @resolver = @resolver.to_slice(slice_name)
51
+
52
+ scope(prefixed_path(at), &blk)
53
+ ensure
54
+ @resolver = prev_resolver
48
55
  end
49
56
 
50
57
  # @since 2.0.0
51
58
  # @api private
52
59
  def to_rack_app
53
- return self if @middleware_stack.empty?
54
-
55
- @middleware_stack.to_rack_app(self)
60
+ middleware_stack.to_rack_app(self)
56
61
  end
57
62
  end
58
63
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Slice
5
+ # Hanami app routes helpers
6
+ #
7
+ # An instance of this class will be registered with slice (at the "routes" key). You
8
+ # can use it to access the route helpers for your app.
9
+ #
10
+ # @example
11
+ # MyApp::App["routes"].path(:root) # => "/"
12
+ #
13
+ # @see Hanami::Router::UrlHelpers
14
+ # @since 2.0.0
15
+ class RoutesHelper
16
+ # @since 2.0.0
17
+ # @api private
18
+ def initialize(router)
19
+ @router = router
20
+ end
21
+
22
+ # @see Hanami::Router::UrlHelpers#path
23
+ def path(...)
24
+ router.path(...)
25
+ end
26
+
27
+ # @see Hanami::Router::UrlHelpers#url
28
+ def url(...)
29
+ router.url(...)
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :router
35
+ end
36
+ end
37
+ end
@@ -3,7 +3,7 @@
3
3
  require "rack/builder"
4
4
 
5
5
  module Hanami
6
- class Application
6
+ class Slice
7
7
  module Routing
8
8
  # Hanami::Applicatione::Router middleware stack
9
9
  #
@@ -20,6 +20,10 @@ module Hanami
20
20
  ROOT_PREFIX = "/"
21
21
  private_constant :ROOT_PREFIX
22
22
 
23
+ # @since 2.0.0
24
+ # @api private
25
+ attr_reader :stack
26
+
23
27
  # @since 2.0.0
24
28
  # @api private
25
29
  def initialize
@@ -29,8 +33,35 @@ module Hanami
29
33
 
30
34
  # @since 2.0.0
31
35
  # @api private
32
- def use(middleware, *args, &blk)
33
- @stack[@prefix].push([middleware, args, blk])
36
+ def initialize_copy(source)
37
+ super
38
+ @prefix = source.instance_variable_get(:@prefix).dup
39
+ @stack = stack.dup
40
+ end
41
+
42
+ # @since 2.0.0
43
+ # @api private
44
+ def use(middleware, *args, before: nil, after: nil, &blk)
45
+ item = [middleware, args, blk]
46
+
47
+ if before
48
+ @stack[@prefix].insert((idx = index_of(before)).zero? ? 0 : idx - 1, item)
49
+ elsif after
50
+ @stack[@prefix].insert(index_of(after) + 1, item)
51
+ else
52
+ @stack[@prefix].push([middleware, args, blk])
53
+ end
54
+
55
+ self
56
+ end
57
+
58
+ # @since 2.0.0
59
+ # @api private
60
+ def update(other)
61
+ other.stack.each do |prefix, items|
62
+ stack[prefix].concat(items)
63
+ end
64
+ self
34
65
  end
35
66
 
36
67
  # @since 2.0.0
@@ -45,7 +76,7 @@ module Hanami
45
76
 
46
77
  # @since 2.0.0
47
78
  # @api private
48
- def to_rack_app(app) # rubocop:disable Metrics/MethodLength
79
+ def to_rack_app(app)
49
80
  s = self
50
81
 
51
82
  Rack::Builder.new do
@@ -58,7 +89,7 @@ module Hanami
58
89
 
59
90
  run app
60
91
  end
61
- end.to_app
92
+ end
62
93
  end
63
94
 
64
95
  # @since 2.0.0
@@ -82,6 +113,13 @@ module Hanami
82
113
  builder.map(prefix, &blk)
83
114
  end
84
115
  end
116
+
117
+ private
118
+
119
+ # @since 2.0.0
120
+ def index_of(middleware)
121
+ @stack[@prefix].index { |(m, *)| m.equal?(middleware) }
122
+ end
85
123
  end
86
124
  end
87
125
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Slice
5
+ module Routing
6
+ # @since 2.0.0
7
+ class UnknownActionError < Hanami::Error
8
+ def initialize(identifier)
9
+ super("unknown action referenced in router: `#{identifier.inspect}'")
10
+ end
11
+ end
12
+
13
+ # @since 2.0.0
14
+ class NotCallableEndpointError < StandardError
15
+ def initialize(endpoint)
16
+ super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
17
+ end
18
+ end
19
+
20
+ # Hanami app router endpoint resolver
21
+ #
22
+ # @since 2.0.0
23
+ class Resolver
24
+ SLICE_ACTIONS_KEY_NAMESPACE = "actions"
25
+
26
+ # @api private
27
+ # @since 2.0.0
28
+ def initialize(slice:)
29
+ @slice = slice
30
+ end
31
+
32
+ # @api private
33
+ # @since 2.0.0
34
+ def find_slice(slice_name)
35
+ slice.slices[slice_name]
36
+ end
37
+
38
+ # @api private
39
+ # @since 2.0.0
40
+ def to_slice(slice_name)
41
+ self.class.new(slice: find_slice(slice_name))
42
+ end
43
+
44
+ # @api private
45
+ # @since 2.0.0
46
+ def call(_path, endpoint)
47
+ endpoint =
48
+ case endpoint
49
+ when String
50
+ resolve_slice_action(endpoint)
51
+ when Class
52
+ endpoint.respond_to?(:call) ? endpoint : endpoint.new
53
+ else
54
+ endpoint
55
+ end
56
+
57
+ unless endpoint.respond_to?(:call)
58
+ raise NotCallableEndpointError.new(endpoint)
59
+ end
60
+
61
+ endpoint
62
+ end
63
+
64
+ private
65
+
66
+ # @api private
67
+ # @since 2.0.0
68
+ attr_reader :slice
69
+
70
+ # @api private
71
+ # @since 2.0.0
72
+ def resolve_slice_action(key)
73
+ action_key = "#{SLICE_ACTIONS_KEY_NAMESPACE}.#{key}"
74
+
75
+ ensure_action_in_slice(action_key)
76
+
77
+ # Lazily resolve action from the slice to reduce router initialization time, and
78
+ # circumvent endless loops from the action requiring access to router-related
79
+ # concerns (which may not be fully loaded at the time of reading the routes)
80
+ -> (*args) {
81
+ action = slice.resolve(action_key) do
82
+ raise UnknownActionError.new(key)
83
+ end
84
+
85
+ action.call(*args)
86
+ }
87
+ end
88
+
89
+ def ensure_action_in_slice(key)
90
+ return unless slice.booted?
91
+
92
+ raise UnknownActionError.new(key) unless slice.key?(key)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end