hanami 2.2.0 → 2.3.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -1
  3. data/README.md +20 -35
  4. data/hanami.gemspec +3 -2
  5. data/lib/hanami/app.rb +2 -0
  6. data/lib/hanami/config/actions/content_security_policy.rb +23 -0
  7. data/lib/hanami/config/actions.rb +21 -0
  8. data/lib/hanami/config/console.rb +79 -0
  9. data/lib/hanami/config/logger.rb +1 -1
  10. data/lib/hanami/config.rb +13 -0
  11. data/lib/hanami/constants.rb +3 -0
  12. data/lib/hanami/extensions/db/repo.rb +11 -6
  13. data/lib/hanami/extensions/operation.rb +1 -1
  14. data/lib/hanami/extensions/view/context.rb +10 -0
  15. data/lib/hanami/helpers/assets_helper.rb +92 -25
  16. data/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
  17. data/lib/hanami/slice.rb +22 -6
  18. data/lib/hanami/slice_registrar.rb +1 -1
  19. data/lib/hanami/version.rb +1 -1
  20. data/lib/hanami.rb +10 -2
  21. data/spec/integration/assets/cross_slice_assets_helpers_spec.rb +0 -1
  22. data/spec/integration/assets/serve_static_assets_spec.rb +1 -1
  23. data/spec/integration/container/autoloader_spec.rb +2 -0
  24. data/spec/integration/db/db_spec.rb +1 -1
  25. data/spec/integration/db/logging_spec.rb +63 -0
  26. data/spec/integration/db/repo_spec.rb +87 -2
  27. data/spec/integration/logging/exception_logging_spec.rb +6 -1
  28. data/spec/integration/rack_app/middleware_spec.rb +4 -11
  29. data/spec/integration/view/helpers/form_helper_spec.rb +1 -1
  30. data/spec/integration/web/content_security_policy_nonce_spec.rb +251 -0
  31. data/spec/support/app_integration.rb +2 -1
  32. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +7 -0
  33. data/spec/unit/hanami/config/console_spec.rb +22 -0
  34. data/spec/unit/hanami/env_spec.rb +10 -13
  35. data/spec/unit/hanami/slice_spec.rb +18 -0
  36. data/spec/unit/hanami/version_spec.rb +1 -1
  37. data/spec/unit/hanami/web/rack_logger_spec.rb +11 -4
  38. metadata +27 -18
  39. data/spec/support/shared_examples/cli/generate/app.rb +0 -494
  40. data/spec/support/shared_examples/cli/generate/migration.rb +0 -32
  41. data/spec/support/shared_examples/cli/generate/model.rb +0 -81
  42. data/spec/support/shared_examples/cli/new.rb +0 -97
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ebc4cbb51db37137a558abeeb3c4aa509d2a6d4db521458b465a427e503b098
4
- data.tar.gz: fdd74d2e280be11ba0db6852a0dcfcd15df7629a1edb6e6b7067f623103d9fdd
3
+ metadata.gz: a8faef478dec45e673c25c67704b0fd7a363e784061412c64e96735040f2d07f
4
+ data.tar.gz: b88eac54b8ba6241645b616ba9eafd20d54edb888e7fca8abc9e310f414dabe5
5
5
  SHA512:
6
- metadata.gz: 5c7fbba4c66f89f8426a675c88005b1c67a544f7051aec5835108a075631b253fde376b8a6b993a78257fb329e398dad45349f8380716ff0ca1d812b79624619
7
- data.tar.gz: c5e66a40feaee584b0d6cc9d22aa31b01abcc4d8ec06a3fe09a9aca294f2cdf5d41c3bfa92a6da291a5c9015fac96f4986ac9535f8b85b3681b81708dd69dcdf
6
+ metadata.gz: 8233b2e453669ae1528c3cadd8032cbff3a7337c590bb0962d307ddd71bb8b18e28f56cc19a5644225efaf0ad0bfa2f5a008dd8fbae2615a9bc819906e47e1cc
7
+ data.tar.gz: 07551dada781667f44be90593599a0f505e9580d82d98c70df4e18e4288651e68fb26130d3504bf228dd2b7d2237af7494d7cfac86cd06ece733920657adfa83
data/CHANGELOG.md CHANGED
@@ -1,6 +1,48 @@
1
1
  # Hanami
2
2
 
3
- The web, with simplicity.
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+
7
+ ### Changed
8
+
9
+ ### Deprecated
10
+
11
+ ### Removed
12
+
13
+ ### Fixed
14
+
15
+ ### Security
16
+
17
+ ## [v2.3.0.beta1] - 2025-10-03
18
+
19
+ ### Added
20
+
21
+ - Add `config.console` settings to app. Set an alternative engine with e.g. `config.console.engine = :pry` (`:irb` is default). Add your own methods to the console with `config.console.include MyModule, AnotherModule`. (@alassek in #1540)
22
+ - Support optional nonce in Rack requests, CSP header rules and view helpers (@svoop in #1500)
23
+ - Check `ENV["APP_ENV"]` for the Hanami env if `ENV["HANAMI_ENV"]` is not set. The order of environment variable checks is now `HANAMI_ENV`->`APP_ENV`->`RACK_ENV` (@svoop in #1487).
24
+
25
+ ### Changed
26
+
27
+ - Support both Rack v2 and v3. (@kyleplump in #1493)
28
+ - Support single-character slice names. (@aaronmallen in #1528)
29
+
30
+ ### Fixed
31
+
32
+ - Allow `include Deps` to be used in `Hanami::DB::Repo` subclasses. (@wuarmin in #1523)
33
+ - Properly infer root relations for deeper `Hanami::DB::Repo` subclasses, such as in slices. (@wuarmin in #1478)
34
+ - Delay loading `config/routes.rb` until after autoloading is setup, which means you can access your constants there. (@timriley in #1539)
35
+ - Avoid warning from referencing deprecated `URI::DEFAULT_PARSER`. (@wuarmin in #1518)
36
+
37
+ ## v2.2.1 - 2024-11-12
38
+
39
+ ### Changed
40
+
41
+ - [Tim Riley] Depend on matching minor version of hanami-cli (a version spec of `"~> 2.2.1"` instead of `"~> 2.2"`). This ensures that future bumps to the minor version of hanami-cli will not be inadvertently installed on user machines (#1471)
42
+
43
+ ### Fixed
44
+
45
+ - [Tim Riley] Allow base operation class to load when a Hanami app is generated with `--skip-db` (i.e. when the "rom-sql" gem is not in the bundle) (#1475)
4
46
 
5
47
  ## v2.2.0 - 2024-11-05
6
48
 
@@ -1468,3 +1510,7 @@ end
1468
1510
  - [Luca Guidi] Introduced `Lotus::Configuration`
1469
1511
  - [Luca Guidi] Introduced `Lotus::Application`
1470
1512
  - [Luca Guidi] Official support for MRI 2.0
1513
+
1514
+
1515
+ [unreleased]: https://github.com/hanami/hanami/compare/v2.3.0.beta1...HEAD
1516
+ [v2.3.0.beta1] https://github.com/hanami/hanami/compare/v2.2.1...v2.3.0.beta1
data/README.md CHANGED
@@ -1,12 +1,6 @@
1
1
  # Hanami :cherry_blossom:
2
2
 
3
- The web, with simplicity.
4
-
5
- ## Version
6
-
7
- **This branch contains the code for `hanami`: 2.2**
8
-
9
- ## Frameworks
3
+ **A flexible framework for maintainable Ruby apps.**
10
4
 
11
5
  Hanami is a **full-stack** Ruby web framework. It's made up of smaller, single-purpose libraries.
12
6
 
@@ -14,8 +8,8 @@ This repository is for the full-stack framework, which provides the glue that ti
14
8
 
15
9
  * [**Hanami::Router**](https://github.com/hanami/router) - Rack compatible HTTP router for Ruby
16
10
  * [**Hanami::Controller**](https://github.com/hanami/controller) - Full featured, fast and testable actions for Rack
17
- * [**Hanami::Validations**](https://github.com/hanami/validations) - Parameter validations & coercion for actions
18
11
  * [**Hanami::View**](https://github.com/hanami/view) - Presentation with a separation between views and templates
12
+ * [**Hanami::DB**](https://github.com/hanami/db) - Database integration, complete with migrations, repositories, relations, and structs
19
13
  * [**Hanami::Assets**](https://github.com/hanami/assets) - Assets management for Ruby
20
14
 
21
15
  These components are designed to be used independently or together in a Hanami application.
@@ -24,12 +18,10 @@ These components are designed to be used independently or together in a Hanami a
24
18
 
25
19
  [![Gem Version](https://badge.fury.io/rb/hanami.svg)](https://badge.fury.io/rb/hanami)
26
20
  [![CI](https://github.com/hanami/hanami/actions/workflows/ci.yml/badge.svg)](https://github.com/hanami/hanami/actions?query=workflow%3Aci+branch%3Amain)
27
- [![Test Coverage](https://codecov.io/gh/hanami/hanami/branch/main/graph/badge.svg)](https://codecov.io/gh/hanami/hanami)
28
- [![Depfu](https://badges.depfu.com/badges/ba000e0f69e6ef1c44cd3038caaa1841/overview.svg)](https://depfu.com/github/hanami/hanami?project=Bundler)
29
21
 
30
22
  ## Installation
31
23
 
32
- __Hanami__ supports Ruby (MRI) 3.1+.
24
+ Hanami supports Ruby (MRI) 3.1+.
33
25
 
34
26
  ```shell
35
27
  gem install hanami
@@ -40,7 +32,8 @@ gem install hanami
40
32
  ```shell
41
33
  hanami new bookshelf
42
34
  cd bookshelf && bundle
43
- bundle exec hanami server # visit http://localhost:2300
35
+ bundle exec hanami dev
36
+ # Now visit http://localhost:2300
44
37
  ```
45
38
 
46
39
  Please follow along with the [Getting Started guide](https://guides.hanamirb.org/getting-started/).
@@ -49,30 +42,22 @@ Please follow along with the [Getting Started guide](https://guides.hanamirb.org
49
42
 
50
43
  You can give back to Open Source, by supporting Hanami development via [GitHub Sponsors](https://github.com/sponsors/hanami).
51
44
 
52
- ### Supporters
53
-
54
- * [Trung Lê](https://github.com/runlevel5)
55
- * [James Carlson](https://github.com/jxxcarlson)
56
- * [Creditas](https://www.creditas.com.br/)
57
-
58
45
  ## Contact
59
46
 
60
- * Home page: http://hanamirb.org
61
- * Community: http://hanamirb.org/community
62
- * Guides: https://guides.hanamirb.org
63
- * Snippets: https://snippets.hanamirb.org
64
- * Mailing List: http://hanamirb.org/mailing-list
65
- * API Doc: https://gemdocs.org/gems/hanami/latest
66
- * Bugs/Issues: https://github.com/hanami/hanami/issues
67
- * Stack Overflow: http://stackoverflow.com/questions/tagged/hanami
68
- * Forum: https://discourse.hanamirb.org
69
- * **Chat**: http://chat.hanamirb.org
47
+ * [Home page](http://hanamirb.org)
48
+ * [Community](http://hanamirb.org/community)
49
+ * [Guides](https://guides.hanamirb.org)
50
+ * [Issues](https://github.com/hanami/hanami/issues)
51
+ * [Forum](https://discourse.hanamirb.org)
52
+ * [Chat](https://discord.gg/KFCxDmk3JQ)
70
53
 
71
54
  ## Community
72
55
 
73
- We strive for an inclusive and helpful community. We have a [Code of Conduct](http://hanamirb.org/community/#code-of-conduct) to handle controversial cases. In general, we expect **you** to be **nice** with other people. Our hope is for a great software and a great Community.
56
+ We care about building a friendly, inclusive and helpful community. We welcome people of all backgrounds, genders and experience levels, and respect you all equally.
57
+
58
+ We do not tolerate nazis, transphobes, racists, or any kind of bigotry. See our [code of conduct](http://hanamirb.org/community/#code-of-conduct) for more.
74
59
 
75
- ## Contributing [![Open Source Helpers](https://www.codetriage.com/hanami/hanami/badges/users.svg)](https://www.codetriage.com/hanami/hanami)
60
+ ## Contributing
76
61
 
77
62
  1. Fork it ( https://github.com/hanami/hanami/fork )
78
63
  2. Create your feature branch (`git checkout -b my-new-feature`)
@@ -110,14 +95,14 @@ $ bundle exec rspec path/to/spec.rb
110
95
 
111
96
  ### Development Requirements
112
97
 
113
- * Ruby >= 3.1
114
- * Bundler
115
- * Node.js (MacOS)
98
+ * Ruby >= 3.1
99
+ * Bundler
100
+ * Node.js
116
101
 
117
102
  ## Versioning
118
103
 
119
- __Hanami__ uses [Semantic Versioning 2.0.0](http://semver.org)
104
+ Hanami uses [Semantic Versioning 2.0.0](http://semver.org).
120
105
 
121
106
  ## Copyright
122
107
 
123
- Copyright © 2014–2024 Hanami Team – Released under MIT License.
108
+ Copyright © 2014–2025 Hanami Team – Released under MIT License.
data/hanami.gemspec CHANGED
@@ -38,12 +38,13 @@ Gem::Specification.new do |spec|
38
38
  spec.add_dependency "dry-monitor", "~> 1.0", ">= 1.0.1", "< 2"
39
39
  spec.add_dependency "dry-system", "~> 1.1"
40
40
  spec.add_dependency "dry-logger", "~> 1.0", "< 2"
41
- spec.add_dependency "hanami-cli", "~> 2.2"
41
+ spec.add_dependency "hanami-cli", "~> 2.3.0.beta1"
42
42
  spec.add_dependency "hanami-utils", "~> 2.2"
43
43
  spec.add_dependency "json", ">= 2.7.2"
44
44
  spec.add_dependency "zeitwerk", "~> 2.6"
45
+ spec.add_dependency "rack-session"
45
46
 
46
47
  spec.add_development_dependency "rspec", "~> 3.8"
47
- spec.add_development_dependency "rack-test", "~> 1.1"
48
+ spec.add_development_dependency "rack-test", "~> 2.0"
48
49
  spec.add_development_dependency "rake", "~> 13.0"
49
50
  end
data/lib/hanami/app.rb CHANGED
@@ -168,6 +168,8 @@ module Hanami
168
168
  end
169
169
 
170
170
  def prepare_autoloader
171
+ autoloader.tag = "hanami.app.#{slice_name.name}"
172
+
171
173
  # Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
172
174
  # This method adds other dirs that are not otherwise configured as component dirs.
173
175
 
@@ -96,6 +96,29 @@ module Hanami
96
96
  @policy.delete(key)
97
97
  end
98
98
 
99
+ # Returns true if 'nonce' is used in any of the policies.
100
+ #
101
+ # @return [Boolean]
102
+ #
103
+ # @api public
104
+ # @since x.x.x
105
+ def nonce?
106
+ @policy.any? { _2.match?(/'nonce'/) }
107
+ end
108
+
109
+ # Returns an array of middleware name to support 'nonce' in
110
+ # policies, or an empty array if 'nonce' is not used.
111
+ #
112
+ # @return [Array<(Symbol, Array)>]
113
+ #
114
+ # @api public
115
+ # @since x.x.x
116
+ def middleware
117
+ return [] unless nonce?
118
+
119
+ [Hanami::Middleware::ContentSecurityPolicyNonce]
120
+ end
121
+
99
122
  # @since 2.0.0
100
123
  # @api private
101
124
  def to_s
@@ -74,6 +74,23 @@ module Hanami
74
74
  # @since 2.0.0
75
75
  attr_accessor :content_security_policy
76
76
 
77
+ # Returns the proc to generate Content Security Policy nonce values.
78
+ #
79
+ # The current Rack request object is provided as an optional argument
80
+ # to the proc, enabling the generation of nonces based on session IDs.
81
+ #
82
+ # @example Independent random nonce (default)
83
+ # -> { SecureRandom.urlsafe_base64(16) }
84
+ #
85
+ # @example Session dependent nonce
86
+ # ->(request) { Digest::SHA256.base64digest(request.session[:uuid])[0, 16] }
87
+ #
88
+ # @return [Proc]
89
+ #
90
+ # @api public
91
+ # @since x.x.x
92
+ setting :content_security_policy_nonce_generator, default: -> { SecureRandom.urlsafe_base64(16) }
93
+
77
94
  # @!attribute [rw] method_override
78
95
  # Sets or returns whether HTTP method override should be enabled for action classes.
79
96
  #
@@ -138,6 +155,10 @@ module Hanami
138
155
  end
139
156
  end
140
157
 
158
+ # @api public
159
+ # @since x.x.x
160
+ def content_security_policy? = !!@content_security_policy
161
+
141
162
  private
142
163
 
143
164
  # Apply defaults for base config
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+
5
+ module Hanami
6
+ class Config
7
+ # Hanami console config
8
+ #
9
+ # @since 2.3.0
10
+ # @api public
11
+ class Console
12
+ include Dry::Configurable
13
+
14
+ # @!attribute [rw] engine
15
+ # Sets or returns the interactive console engine to be used by `hanami console`.
16
+ # Supported values are `:irb` (default) and `:pry`.
17
+ #
18
+ # @example
19
+ # config.console.engine = :pry
20
+ #
21
+ # @return [Symbol]
22
+ #
23
+ # @api public
24
+ # @since 2.3.0
25
+ setting :engine, default: :irb
26
+
27
+ # Returns the complete list of extensions to be used in the console
28
+ #
29
+ # @example
30
+ # config.console.include MyExtension, OtherExtension
31
+ # config.console.include ThirdExtension
32
+ #
33
+ # config.console.extensions
34
+ # # => [MyExtension, OtherExtension, ThirdExtension]
35
+ #
36
+ # @return [Array<Module>]
37
+ #
38
+ # @api public
39
+ # @since 2.3.0
40
+ def extensions = @extensions.dup.freeze
41
+
42
+ # Define a module extension to be included in the console
43
+ #
44
+ # @param mod [Module] one or more modules to be included in the console
45
+ # @return [void]
46
+ #
47
+ # @api public
48
+ # @since 2.3.0
49
+ def include(*mod)
50
+ @extensions.concat(mod).uniq!
51
+ end
52
+
53
+ # @api private
54
+ def initialize
55
+ @extensions = []
56
+ end
57
+
58
+ private
59
+
60
+ # @api private
61
+ def initialize_copy(source)
62
+ super
63
+ @extensions = [*source.extensions]
64
+ end
65
+
66
+ def method_missing(name, *args, &block)
67
+ if config.respond_to?(name)
68
+ config.public_send(name, *args, &block)
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ def respond_to_missing?(name, _include_all = false)
75
+ config.respond_to?(name) || super
76
+ end
77
+ end
78
+ end
79
+ end
@@ -101,7 +101,7 @@ module Hanami
101
101
  # Sets or returns a hash of options to pass to the {logger_constructor} when initializing
102
102
  # the logger.
103
103
  #
104
- # Defaults to `[]`
104
+ # Defaults to `{}`
105
105
  #
106
106
  # @return [Hash]
107
107
  #
data/lib/hanami/config.rb CHANGED
@@ -6,6 +6,7 @@ require "dry/configurable"
6
6
  require "dry/inflector"
7
7
 
8
8
  require_relative "constants"
9
+ require_relative "config/console"
9
10
 
10
11
  module Hanami
11
12
  # Hanami app config
@@ -184,6 +185,18 @@ module Hanami
184
185
  "Hanami::Router::NotFoundError" => :not_found,
185
186
  )
186
187
 
188
+ # @!attribute [rw] console
189
+ # Returns the app's console config
190
+ #
191
+ # @example
192
+ # config.console.engine # => :irb
193
+ #
194
+ # @return [Hanami::Config::Console]
195
+ #
196
+ # @api public
197
+ # @since 2.3.0
198
+ setting :console, default: Hanami::Config::Console.new
199
+
187
200
  # Returns the app or slice's {Hanami::SliceName slice_name}.
188
201
  #
189
202
  # This is useful for default config values that depend on this name.
@@ -56,4 +56,7 @@ module Hanami
56
56
  # @api private
57
57
  RB_EXT_REGEXP = %r{.rb$}
58
58
  private_constant :RB_EXT_REGEXP
59
+
60
+ # @api private
61
+ CONTENT_SECURITY_POLICY_NONCE_REQUEST_KEY = "hanami.content_security_policy_nonce"
59
62
  end
@@ -69,7 +69,8 @@ module Hanami
69
69
  resolve_rom = method(:resolve_rom)
70
70
 
71
71
  define_method(:new) do |**kwargs|
72
- super(container: kwargs.fetch(:container) { resolve_rom.() })
72
+ container = kwargs.delete(:container) || resolve_rom.()
73
+ super(container: container, **kwargs)
73
74
  end
74
75
  end
75
76
 
@@ -77,14 +78,18 @@ module Hanami
77
78
  slice["db.rom"]
78
79
  end
79
80
 
80
- def root_for_repo_class(repo_class)
81
- return unless repo_class.to_s.end_with?("Repo")
81
+ REPO_CLASS_NAME_REGEX = /^(?<name>.+)_(repo|repository)$/
82
82
 
83
- slice.inflector.demodulize(repo_class)
83
+ def root_for_repo_class(repo_class)
84
+ repo_class_name = slice.inflector.demodulize(repo_class)
84
85
  .then { slice.inflector.underscore(_1) }
85
- .then { _1.gsub(/_repo$/, "") }
86
+
87
+ repo_class_match = repo_class_name.match(REPO_CLASS_NAME_REGEX)
88
+ return unless repo_class_match
89
+
90
+ repo_class_match[:name]
86
91
  .then { slice.inflector.pluralize(_1) }
87
- .then { _1.to_sym }
92
+ .then(&:to_sym)
88
93
  end
89
94
 
90
95
  def struct_namespace
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/operation"
4
- require "dry/operation/extensions/rom"
5
4
 
6
5
  module Hanami
7
6
  module Extensions
@@ -38,6 +37,7 @@ module Hanami
38
37
  return unless subclass.superclass == self
39
38
  return unless Hanami.bundled?("hanami-db")
40
39
 
40
+ require "dry/operation/extensions/rom"
41
41
  subclass.include Dry::Operation::Extensions::ROM
42
42
  end
43
43
  end
@@ -172,6 +172,16 @@ module Hanami
172
172
  @request
173
173
  end
174
174
 
175
+ # Returns true if the view is rendered from within an action and a request is available.
176
+ #
177
+ # @return [Boolean]
178
+ #
179
+ # @api public
180
+ # @since x.x.x
181
+ def request?
182
+ !!@request
183
+ end
184
+
175
185
  # Returns the app's routes helper.
176
186
  #
177
187
  # @return [Hanami::Slice::RoutesHelper] the routes helper