Prepare for v2.0.0.beta1
This commit is contained in:
parent
7ef19f3ad5
commit
6d9c7bf986
148
CHANGELOG.md
148
CHANGELOG.md
|
@ -1,30 +1,48 @@
|
|||
# Hanami::Utils
|
||||
Ruby core extentions and class utilities for Hanami
|
||||
|
||||
Ruby core extensions and class utilities for Hanami
|
||||
|
||||
## v2.0.0.beta1 - 2022-07-13
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Removed `Hanami::Utils::BasicObject` (moved to `dry-core` as `Dry::Core::BasicObject`)
|
||||
- [Luca Guidi] Removed `Hanami::Interactor`
|
||||
|
||||
## v2.0.0.alpha6 - 2022-02-10
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for Ruby: MRI 3.0 and 3.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Rob Jacoby] Allow `Hanami::Logger#initialize` to accept `File::NULL` as `stream:` argument
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Drop support for Ruby: MRI 2.6 and 2.7.
|
||||
|
||||
## v2.0.0.alpha3 - 2021-11-09
|
||||
|
||||
No changes.
|
||||
|
||||
## v2.0.0.alpha2 - 2021-05-04
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Drop support for Ruby: MRI 2.5.
|
||||
- [Luca Guidi] Transform `Utils::String` from class to module
|
||||
|
||||
## v2.0.0.alpha1 - 2019-01-30
|
||||
|
||||
### Added
|
||||
|
||||
- [Gustavo Caso] Introduce `Hanami::Middleware` namespace
|
||||
- [Luca Guidi] Introduce `Callbacks::Chain#dup`
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Drop support for Ruby: MRI 2.3, and 2.4.
|
||||
- [Luca Guidi] Remove `Utils::Duplicable`
|
||||
- [Luca Guidi] Remove `Utils::Inflector`
|
||||
|
@ -37,62 +55,85 @@ No changes.
|
|||
- [Vladimir Suvorov] Remove `Utils::Class.load_from_pattern!`
|
||||
|
||||
## v1.3.8 - 2021-05-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Hiếu Nguyễn] Ensure `Hanami::Interactor#initialize` to accept keyword arguments while working with Ruby 3
|
||||
|
||||
## v1.3.7 - 2021-01-04
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for Ruby: MRI 3.0
|
||||
- [Khai Le] Allow `Hanami::Logger` to filter sensitive data for an array of hashes
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Hiếu Nguyễn] Ensure `Hanami::Logger` to not mutate `Hash` input when filtering sensitive data
|
||||
|
||||
## v1.3.6 - 2020-01-07
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for Ruby: MRI 2.7
|
||||
|
||||
### Fixed
|
||||
|
||||
- [ippachi] `Utils::Files.append`: don't check breakline if file is empty
|
||||
|
||||
## v1.3.5 - 2019-10-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Ivan Kabluchkov] Ensure `Hanami::Logger` filters to not crash when logger stream is a closed tempfile
|
||||
- [Luca Guidi] Ensure `Utils::Files.append` to append contents properly when existing file doesn't end with a newline
|
||||
|
||||
## v1.3.4 - 2019-09-27
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Let `Utils::BasicObject` to lookup constants at the top-level namespace
|
||||
|
||||
## v1.3.3 - 2019-09-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Mauro Morales] Ensure `Utils::Inflector.pluralize` and `.singularize` to work with words that contain an underscore (`_`)
|
||||
|
||||
## v1.3.2 - 2019-06-21
|
||||
|
||||
### Added
|
||||
|
||||
- [Vladislav Yashin & Luca Guidi] Added `Utils::BasicObject#instance_of?`, `#is_a?`, and `#kind_of`
|
||||
|
||||
## v1.3.1 - 2019-01-18
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for Ruby: MRI 2.6
|
||||
- [Luca Guidi] Support `bundler` 2.0+
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Alfonso Uceda] Fix `Hash` serialization for `Utils::Logger`
|
||||
- [Jeff Dickey] Add missing `pathname` require in `lib/hanami/utils.rb`
|
||||
|
||||
## v1.3.0 - 2018-10-24
|
||||
|
||||
## v1.3.0.beta1 - 2018-08-08
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for JRuby 9.2.0.0
|
||||
- [graywolf] Add `Utils::Files.inject_line_before_last` and `.inject_line_after_last`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [graywolf] Don't show `Fixnum` Ruby warning for 2.4+
|
||||
- [Luca Guidi] Fix pluralization of `"fee"`
|
||||
|
||||
### Deprecated
|
||||
|
||||
- [Luca Guidi & Marion Schleifer] Deprecate `Utils::String` as Ruby type. Please use `Utils::String` class methods instead of `Utils::String.new("")`.
|
||||
- [Luca Guidi & Marion Schleifer] Deprecate `Utils::Hash` as Ruby type. Please use `Utils::Hash` class methods instead of `Utils::Hash.new({})`.
|
||||
- [Luca Guidi & Marion Schleifer] Deprecate `Utils::String.pluralize` and `.singularize`.
|
||||
|
@ -101,14 +142,19 @@ No changes.
|
|||
## v1.2.0 - 2018-04-11
|
||||
|
||||
## v1.2.0.rc2 - 2018-04-06
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Use different colors for each `Hanami::Logger` level
|
||||
|
||||
## v1.2.0.rc1 - 2018-03-30
|
||||
|
||||
### Added
|
||||
|
||||
- [Oana Sipos & Sean Collins & Luca Guidi] Colored logging
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Make `Hanami::Logger` to properly log hash messages
|
||||
|
||||
## v1.2.0.beta2 - 2018-03-23
|
||||
|
@ -116,25 +162,34 @@ No changes.
|
|||
## v1.2.0.beta1 - 2018-02-28
|
||||
|
||||
## v1.1.2 - 2018-02-02
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for Ruby: MRI 2.5
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Sean Collins & Luca Guidi] Make `Utils::Files.write` idempotent: ensure to truncate the file before to write
|
||||
- [Sean Collins & Luca Guidi] Don't erase file contents when invoking `Utils::Files.touch`
|
||||
|
||||
### Changed
|
||||
|
||||
- [Sean Collins & Luca Guidi] Deprecate `Utils::Files.rewrite` in favor of `.write`
|
||||
|
||||
## v1.1.1 - 2017-11-22
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Introduce `Utils::Hash.deep_stringify` to recursively stringify a hash
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Yuta Tokitake] Ensure `Interactor#call` to accept non-keyword arguments
|
||||
|
||||
## v1.1.0 - 2017-10-25
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Introduce `Utils::Hash.deep_serialize` to recursively serialize input into `::Hash`
|
||||
|
||||
## v1.1.0.rc1 - 2017-10-16
|
||||
|
@ -142,11 +197,15 @@ No changes.
|
|||
## v1.1.0.beta3 - 2017-10-04
|
||||
|
||||
## v1.1.0.beta2 - 2017-10-03
|
||||
|
||||
### Added
|
||||
|
||||
- [Alfonso Uceda] Auto create directory for `Hanami::Logger`
|
||||
|
||||
## v1.1.0.beta1 - 2017-08-11
|
||||
|
||||
### Added
|
||||
|
||||
- [Marion Duprey] Allow `Hanami::Interactor#call` to accept arguments. `#initialize` should be used for Dependency Injection, while `#call` should be used for input
|
||||
- [Marion Schleifer] Introduce `Utils::Hash.stringify`
|
||||
- [Marion Schleifer] Introduce `Utils::String.titleize`, `.capitalize`, `.classify`, `.underscore`, `.dasherize`, `.demodulize`, `.namespace`, `.pluralize`, `.singularize`, and `.rsub`
|
||||
|
@ -155,101 +214,136 @@ No changes.
|
|||
- [Marion Duprey & Gabriel Gizotti] Filter sensitive informations for `Hanami::Logger`
|
||||
|
||||
## v1.0.4 - 2017-10-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Make `Hanami::Utils::BasicObject` to be fully compatible with Ruby's `pp` and to be inspected by Pry.
|
||||
- [Thiago Kenji Okada] Fix pluralization/singularization for `"release" => "releases"`
|
||||
|
||||
## v1.0.3 - 2017-09-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Malina Sulca] Fix pluralization/singularization for `"exercise" => "exercises"`
|
||||
- [Xavier Barbosa] Fix pluralization/singularization for `"area" => "areas"`
|
||||
|
||||
## v1.0.2 - 2017-07-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Anton Davydov] Fix pluralization/singularization for `"phrase" => "phrases"`
|
||||
|
||||
## v1.0.1 - 2017-06-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Introduced `Utils::Hash.symbolize` and `.deep_symbolize`
|
||||
- [Luca Guidi] Introduced `Utils::Hash.deep_dup`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [choallin] Ensure `Utils::String#classify` to return output identical to the input for already classified strings.
|
||||
- [Marion Duprey & Jonas Amundsen] Ensure `Utils::Hash#initialize` to accept frozen `Hash` as argument.
|
||||
|
||||
## v1.0.0 - 2017-04-06
|
||||
|
||||
## v1.0.0.rc1 - 2017-03-31
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Allow `Hanami::Logger#initialize` to accept arguments to be compliant with Ruby's `Logger`
|
||||
|
||||
## v1.0.0.beta3 - 2017-03-17
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Use `$stdout` instead of `STDOUT` as default stream for `Hanami::Logger`
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Removed `Utils::Attributes`
|
||||
- [Luca Guidi] Removed deprecated `Hanami::Interactor::Result#failing?`
|
||||
- [Luca Guidi] Removed deprecated `Utils::Json.load` and `.dump`
|
||||
|
||||
## v1.0.0.beta2 - 2017-03-02
|
||||
|
||||
### Changed
|
||||
|
||||
- [Anton Davydov] Made `Utils::Blank` private API
|
||||
|
||||
## v1.0.0.beta1 - 2017-02-14
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for Ruby: MRI 2.4
|
||||
- [alexd16] Introduced `Utils::Hash#deep_symbolize!` for deep symbolization
|
||||
- [Luca Guidi] Introduced `Hanami::Utils.reload!` as a mechanism to force code reloading in development
|
||||
|
||||
### Fixed
|
||||
|
||||
- [alexd16 & Alfonso Uceda & Luca Guidi] Don't deeply symbolize `Hanami::Interactor::Result` payload
|
||||
- [Alfonso Uceda] `Hanami::Interactor::Result`: Don't transform objects that respond to `#to_hash` (like entities)
|
||||
- [Bhanu Prakash] Use `Utils::Json.generate` instead of the deprecated `.dump` for `Hanami::Logger` JSON formatter
|
||||
- [Luca Guidi] `Hanami::Logger`: when a `Hash` message is passed, don't nest it under `:message` key, but unwrap at the top level
|
||||
|
||||
### Changed
|
||||
|
||||
- [alexd16] `Utils::Hash#symbolize!` no longer symbolizes deep structures
|
||||
- [Luca Guidi & Alfonso Uceda] Improve readability for default logger formatter
|
||||
- [Luca Guidi] Use ISO-8601 time format for JSON logger formatter
|
||||
|
||||
## v0.9.2 - 2016-12-19
|
||||
|
||||
### Added
|
||||
|
||||
- [Grachev Mikhail] Introduced `Hanami::Interactor::Result#failure?`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Paweł Świątkowski] `Utils::Inflector.pluralize` Pluralize -en to -ens instead of -ina
|
||||
|
||||
### Changed
|
||||
|
||||
- [Grachev Mikhail] Deprecate `Hanami::Interactor::Result#failing?` in favor of `#failure?`
|
||||
|
||||
## v0.9.1 - 2016-11-18
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Introduced `Utils::Json.parse` and `.generate`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Ensure `Utils::Json` parsing to not eval untrusted input
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Deprecated `Utils::Json.load` in favor of `.parse`
|
||||
- [Luca Guidi] Deprecated `Utils::Json.dump` in favor of `.generate`
|
||||
|
||||
## v0.9.0 - 2016-11-15
|
||||
|
||||
### Added
|
||||
|
||||
– [Luca Guidi] Introduced `Utils.require!` to recursively require Ruby files with an order that is consistent across platforms
|
||||
– [Luca Guidi] Introduced `Utils::FileList` as cross-platform ordered list of files, alternative to `Dir.glob`
|
||||
|
||||
- [Luca Guidi] Make `Utils::BasicObject` pretty printable
|
||||
- [Grachev Mikhail] Added `Interactor::Result#successful?` and `#failing?`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Pascal Betz] Ensure `Utils::Class.load!` to lookup constant only within the given namespace
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Make `Utils::Hash` only compatible with objects that respond to `#to_hash`
|
||||
- [Luca Guidi] Official support for Ruby: MRI 2.3+ and JRuby 9.1.5.0+
|
||||
|
||||
## v0.8.0 - 2016-07-22
|
||||
|
||||
### Added
|
||||
|
||||
- [Andrey Morskov] Introduced `Hanami::Utils::Blank`
|
||||
- [Anton Davydov] Allow to specify a default log level for `Hanami::Logger`
|
||||
- [Anton Davydov] Introduced default and JSON formatters for `Hanami::Logger`
|
||||
|
@ -263,101 +357,139 @@ No changes.
|
|||
- [Rogério Ramos] Fix English pluralization for words ending with `"ice"`
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Drop support for Ruby 2.0, 2.1 and Rubinius. Official support for JRuby 9.0.5.0+.
|
||||
|
||||
## v0.7.1 - 2016-02-05
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Yuuji Yaginuma] `Hanami::Utils::Escape`: fixed Ruby warning for `String#chars` with a block, which is deprecated. Using `String#each_char` now.
|
||||
- [Sean Collins] Allow non string objects to be escaped by `Hanami::Utils::Escape`.
|
||||
|
||||
## v0.7.0 - 2016-01-22
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Renamed the project
|
||||
|
||||
## v0.6.1 - 2016-01-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Anton Davydov] Ensure `Lotus::Utils::String#classify` to work properly with dashes (eg. `"app-store" => "App::Store"`)
|
||||
|
||||
## v0.6.0 - 2016-01-12
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for Ruby 2.3
|
||||
- [Luca Guidi] Custom inflections
|
||||
- [Luca Guidi] Introduced `Lotus::Utils::Duplicable` as a safe dup logic for Ruby types
|
||||
- [Luca Guidi] Added `Lotus::Utils::String#rsub` replace rightmost occurrence
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Fix `Lotus::Utils::PathPrefix#join` and `#relative_join` by rejecting arguments that are equal to the separator
|
||||
- [Karim Kiatlottiavi] Fix `Encoding::UndefinedConversionError` in `Lotus::Utils::Escape.encode`
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Deprecate Ruby 2.0 and 2.1
|
||||
- [Luca Guidi] Removed `Lotus::Utils::Callbacks#add` in favor of `#append`
|
||||
- [Luca Guidi] Removed pattern support for `Utils::Class.load!` (eg. `Articles(Controller|::Controller)`)
|
||||
|
||||
## v0.5.2 - 2015-09-30
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Added `Lotus::Utils::String#capitalize`
|
||||
- [Trung Lê] Official support for JRuby 9k+
|
||||
|
||||
## v0.5.1 - 2015-07-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Thiago Felippe] Ensure `Lotus::Utils::PathPrefix#join` won't remote duplicate entries (eg `/admin/dashboard/admin`)
|
||||
|
||||
## v0.5.0 - 2015-06-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Extracted `Lotus::Logger` from `hanamirb`
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] `Lotus::Interactor::Result` contains only objects explicitly exposed via `Lotus::Interactor.expose`.
|
||||
|
||||
## v0.4.3 - 2015-05-22
|
||||
|
||||
### Added
|
||||
|
||||
- [François Beausoleil] Improved `Lotus::Utils::Kernel` messages for `TypeError`.
|
||||
|
||||
## v0.4.2 - 2015-05-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Ensure `Lotus::Utils::Attributes#to_h` to return `::Hash`
|
||||
|
||||
## v0.4.1 - 2015-05-15
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi & Alfonso Uceda Pompa] Introduced `Lotus::Utils::Inflector`, `Lotus::Utils::String#pluralize` and `#singularize`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Ensure `Lotus::Utils::Attributes#to_h` to safely return nested `::Hash` instances for complex data structures.
|
||||
- [Luca Guidi] Let `Lotus::Interactor#error` to return a falsey value for control flow. (eg. `check_permissions or error "You can't access"`)
|
||||
|
||||
## v0.4.0 - 2015-03-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Introduced `Lotus::Utils::Escape`. It implements OWASP/ESAPI suggestions for HTML, HTML attribute and URL escape utilities.
|
||||
- [Luca Guidi] Introduced `Lotus::Utils::String#dasherize`
|
||||
- [Luca Guidi] Introduced `Lotus::Utils::String#titleize`
|
||||
|
||||
## v0.3.5 - 2015-03-12
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Introduced `Lotus::Interactor`
|
||||
- [Luca Guidi] Introduced `Lotus::Utils::BasicObject`
|
||||
|
||||
## v0.3.4 - 2015-01-30
|
||||
|
||||
### Added
|
||||
|
||||
- [Alfonso Uceda Pompa] Aliased `Lotus::Utils::Attributes#get` with `#[]`
|
||||
- [Simone Carletti] Introduced `Lotus::Utils::Callbacks::Chain#prepend` and `#append`
|
||||
|
||||
### Deprecated
|
||||
|
||||
- [Luca Guidi] Deprecated `Lotus::Utils::Callbacks::Chain#add` in favor of `#append`
|
||||
|
||||
## v0.3.3 - 2015-01-08
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Ensure to return the right offending object if a missing method is called with Utils::String and Hash (eg. `Utils::Hash.new(a: 1).all? {|_, v| v.foo }` blame `v` instead of `Hash`)
|
||||
- [Luca Guidi] Raise an error if try to coerce non numeric strings into Integer, Float & BigDecimal (eg. `Utils::Kernel.Integer("hello") # => raise TypeError`)
|
||||
|
||||
## v0.3.2 - 2014-12-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Official support for Ruby 2.2
|
||||
- [Luca Guidi] Introduced `Utils::Attributes`
|
||||
- [Luca Guidi] Added `Utils::Hash#stringify!`
|
||||
|
||||
## v0.3.1 - 2014-11-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Allow `Utils::Class.load!` to accept any object that implements `#to_s`
|
||||
- [Trung Lê] Allow `Utils::Class.load!` to accept a class
|
||||
- [Luca Guidi] Introduced `Utils::Class.load_from_pattern!`
|
||||
|
@ -371,14 +503,18 @@ No changes.
|
|||
- [Luca Guidi] Made `Utils::PathPrefix#join` to accept multiple argument
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Made `Utils::PathPrefix#join` remove trailing occurrences for `@separator` from the output
|
||||
- [Luca Guidi] Made `Utils::PathPrefix#relative_join` to correctly replace all the instances of `@separator` from the output
|
||||
|
||||
### Deprecated
|
||||
|
||||
- [Luca Guidi] Deprecated `Utils::Class.load!` with a pattern like `Articles(Controller|::Controller)`, use `Utils::Class.load_from_pattern!` instead
|
||||
|
||||
## v0.3.0 - 2014-10-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Celso Fernandes] Add BigDecimal coercion to Lotus::Utils::Kernel
|
||||
- [Luca Guidi] Define `Boolean` constant, if missing
|
||||
- [Luca Guidi] Use composition over inheritance for `Lotus::Utils::PathPrefix`
|
||||
|
@ -386,6 +522,7 @@ No changes.
|
|||
- [Luca Guidi] Use composition over inheritance for `Lotus::Utils::String`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Improved error message for `Utils::Class.load!`
|
||||
- [Tom Kadwill] Improved error `NameError` message by passing in the whole constant name to `Utils::Class.load!`
|
||||
- [Luca Guidi] `Utils::Hash#to_h` return instances of `::Hash` in case of nested symbolized data structure
|
||||
|
@ -395,7 +532,9 @@ No changes.
|
|||
- [Luca Guidi] Ensure `Utils::Hash#inspect` output to be the same of `::Hash#inspect`
|
||||
|
||||
## v0.2.0 - 2014-06-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Implemented `Lotus::Utils::Kernel.Symbol`
|
||||
- [Luca Guidi] Made `Kernel.Pathname` to raise an error when `nil` is passed as argument
|
||||
- [Luca Guidi] Implemented `Lotus::Utils::LoadPaths#freeze` in order to prevent modification after the object has been frozen
|
||||
|
@ -406,9 +545,11 @@ No changes.
|
|||
- [Luca Guidi] Implemented `Lotus::Utils::Kernel.Pathname`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Luca Guidi] Implemented `Lotus::Utils::LoadPaths#initialize_copy` in order to safely `#dup` and `#clone`
|
||||
|
||||
### Changed
|
||||
|
||||
- [Luca Guidi] Implemented `Lotus::Utils::Callbacks::Chain#freeze` in order to prevent modification after the object has been frozen
|
||||
- [Luca Guidi] All the `Utils::Kernel` methods will raise `TypeError` in case of failed coercion.
|
||||
- [Luca Guidi] Made `Kernel.Time` to raise an error when `nil` is passed as argument
|
||||
|
@ -424,7 +565,9 @@ No changes.
|
|||
- [Luca Guidi] Use composition over inheritance for `Lotus::Utils::Callbacks::Chain`
|
||||
|
||||
## v0.1.1 - 2014-04-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Implemented `Lotus::Utils::Kernel.Time`
|
||||
- [Luca Guidi] Implemented `Lotus::Utils::Kernel.DateTime`
|
||||
- [Luca Guidi] Implemented `Lotus::Utils::Kernel.Date`
|
||||
|
@ -437,10 +580,13 @@ No changes.
|
|||
- [Luca Guidi] Implemented `Lotus::Utils::Kernel.Array`
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Christopher Keele] Add missing stdlib `Set` require to `Utils::ClassAttribute`
|
||||
|
||||
## v0.1.0 - 2014-01-23
|
||||
|
||||
### Added
|
||||
|
||||
- [Luca Guidi] Introduced `Lotus::Utils::String#demodulize`
|
||||
- [Luca Guidi] Introduced `Lotus::Utils::IO.silence_warnings`
|
||||
- [Luca Guidi] Introduced class loading mechanism from a string: `Utils::Class.load!`
|
||||
|
|
27
README.md
27
README.md
|
@ -10,23 +10,22 @@ Ruby core extensions and class utilities for [Hanami](http://hanamirb.org)
|
|||
[![Depfu](https://badges.depfu.com/badges/a8545fb67cf32a2c75b6227bc0821027/overview.svg)](https://depfu.com/github/hanami/utils?project=Bundler)
|
||||
[![Inline Docs](http://inch-ci.org/github/hanami/utils.svg)](http://inch-ci.org/github/hanami/utils)
|
||||
|
||||
|
||||
## Version
|
||||
|
||||
**This branch contains the code for `hanami-utils` 2.x.**
|
||||
|
||||
## Contact
|
||||
|
||||
* Home page: http://hanamirb.org
|
||||
* Mailing List: http://hanamirb.org/mailing-list
|
||||
* API Doc: http://rdoc.info/gems/hanami-utils
|
||||
* Bugs/Issues: https://github.com/hanami/utils/issues
|
||||
* Support: http://stackoverflow.com/questions/tagged/hanami
|
||||
* Chat: http://chat.hanamirb.org
|
||||
- Home page: http://hanamirb.org
|
||||
- Mailing List: http://hanamirb.org/mailing-list
|
||||
- API Doc: http://rdoc.info/gems/hanami-utils
|
||||
- Bugs/Issues: https://github.com/hanami/utils/issues
|
||||
- Support: http://stackoverflow.com/questions/tagged/hanami
|
||||
- Chat: http://chat.hanamirb.org
|
||||
|
||||
## Rubies
|
||||
|
||||
__Hanami::Utils__ supports Ruby (MRI) 3.0+
|
||||
**Hanami::Utils** supports Ruby (MRI) 3.0+
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -46,23 +45,15 @@ Or install it yourself as:
|
|||
|
||||
## Usage
|
||||
|
||||
__Hanami::Utils__ is designed to enhance Ruby's code and stdlib.
|
||||
**Hanami::Utils** is designed to enhance Ruby's code and stdlib.
|
||||
**By default this gem doesn't load any code, you must require what you need.**
|
||||
|
||||
## Features
|
||||
|
||||
### Hanami::Interactor
|
||||
|
||||
Standardized Service Object with small interface and rich returning result. [[API doc](http://www.rubydoc.info/gems/hanami-utils/Hanami/Interactor)]
|
||||
|
||||
### Hanami::Logger
|
||||
|
||||
Enhanced version of Ruby's `Logger`. [[API doc](http://www.rubydoc.info/gems/hanami-utils/Hanami/Logger)]
|
||||
|
||||
### Hanami::Utils::BasicObject
|
||||
|
||||
Enhanced version of Ruby's `BasicObject`. [[API doc](http://www.rubydoc.info/gems/hanami-utils/Hanami/Utils/BasicObject)]
|
||||
|
||||
### Hanami::Utils::Blank
|
||||
|
||||
Checks for blank. [[API doc](http://www.rubydoc.info/gems/hanami-utils/Hanami/Utils/Blank)]
|
||||
|
@ -133,7 +124,7 @@ Enhanced version of Ruby's `String`. [[API doc](http://www.rubydoc.info/gems/han
|
|||
|
||||
## Versioning
|
||||
|
||||
__Hanami::Utils__ uses [Semantic Versioning 2.0.0](http://semver.org)
|
||||
**Hanami::Utils** uses [Semantic Versioning 2.0.0](http://semver.org)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ Gem::Specification.new do |spec|
|
|||
|
||||
spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-utils.gemspec`.split($/)
|
||||
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
||||
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
||||
spec.require_paths = ["lib"]
|
||||
spec.metadata["rubygems_mfa_required"] = "true"
|
||||
spec.required_ruby_version = ">= 3.0"
|
||||
|
|
|
@ -1,619 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "hanami/utils/basic_object"
|
||||
require "hanami/utils/class_attribute"
|
||||
require "hanami/utils/hash"
|
||||
|
||||
module Hanami
|
||||
# Hanami Interactor
|
||||
#
|
||||
# @since 0.3.5
|
||||
module Interactor
|
||||
# Result of an operation
|
||||
#
|
||||
# @since 0.3.5
|
||||
class Result < Utils::BasicObject
|
||||
# Concrete methods
|
||||
#
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
#
|
||||
# @see Hanami::Interactor::Result#respond_to_missing?
|
||||
METHODS = ::Hash[initialize: true,
|
||||
success?: true,
|
||||
successful?: true,
|
||||
failure?: true,
|
||||
fail!: true,
|
||||
prepare!: true,
|
||||
errors: true,
|
||||
error: true].freeze
|
||||
|
||||
# Initialize a new result
|
||||
#
|
||||
# @param payload [Hash] a payload to carry on
|
||||
#
|
||||
# @return [Hanami::Interactor::Result]
|
||||
#
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def initialize(payload = {})
|
||||
@payload = payload
|
||||
@errors = []
|
||||
@success = true
|
||||
end
|
||||
|
||||
# Checks if the current status is successful
|
||||
#
|
||||
# @return [TrueClass,FalseClass] the result of the check
|
||||
#
|
||||
# @since 0.8.1
|
||||
def successful?
|
||||
@success && errors.empty?
|
||||
end
|
||||
|
||||
# @since 0.3.5
|
||||
alias_method :success?, :successful?
|
||||
|
||||
# Checks if the current status is not successful
|
||||
#
|
||||
# @return [TrueClass,FalseClass] the result of the check
|
||||
#
|
||||
# @since 0.9.2
|
||||
def failure?
|
||||
!successful?
|
||||
end
|
||||
|
||||
# Forces the status to be a failure
|
||||
#
|
||||
# @since 0.3.5
|
||||
def fail!
|
||||
@success = false
|
||||
end
|
||||
|
||||
# Returns all the errors collected during an operation
|
||||
#
|
||||
# @return [Array] the errors
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @see Hanami::Interactor::Result#error
|
||||
# @see Hanami::Interactor#call
|
||||
# @see Hanami::Interactor#error
|
||||
# @see Hanami::Interactor#error!
|
||||
def errors
|
||||
@errors.dup
|
||||
end
|
||||
|
||||
# @since 0.5.0
|
||||
# @api private
|
||||
def add_error(*errors)
|
||||
@errors << errors
|
||||
@errors.flatten!
|
||||
nil
|
||||
end
|
||||
|
||||
# Returns the first errors collected during an operation
|
||||
#
|
||||
# @return [nil,String] the error, if present
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @see Hanami::Interactor::Result#errors
|
||||
# @see Hanami::Interactor#call
|
||||
# @see Hanami::Interactor#error
|
||||
# @see Hanami::Interactor#error!
|
||||
def error
|
||||
errors.first
|
||||
end
|
||||
|
||||
# Prepares the result before to be returned
|
||||
#
|
||||
# @param payload [Hash] an updated payload
|
||||
#
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def prepare!(payload)
|
||||
@payload.merge!(payload)
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def method_missing(method_name, *)
|
||||
@payload.fetch(method_name) { super }
|
||||
end
|
||||
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def respond_to_missing?(method_name, _include_all)
|
||||
method_name = method_name.to_sym
|
||||
METHODS[method_name] || @payload.key?(method_name)
|
||||
end
|
||||
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def __inspect
|
||||
" @success=#{@success} @payload=#{@payload.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Override for <tt>Module#included</tt>.
|
||||
#
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def self.included(base)
|
||||
super
|
||||
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
# Interactor legacy interface
|
||||
#
|
||||
# @since 0.3.5
|
||||
module LegacyInterface
|
||||
# Initialize an interactor
|
||||
#
|
||||
# It accepts arbitrary number of arguments.
|
||||
# Developers can override it.
|
||||
#
|
||||
# @param args [Array<Object>] arbitrary number of arguments
|
||||
#
|
||||
# @return [Hanami::Interactor] the interactor
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @example Override #initialize
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class UpdateProfile
|
||||
# include Hanami::Interactor
|
||||
#
|
||||
# def initialize(user, params)
|
||||
# @user = user
|
||||
# @params = params
|
||||
# end
|
||||
#
|
||||
# def call
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
ensure
|
||||
@__result = ::Hanami::Interactor::Result.new
|
||||
end
|
||||
|
||||
# Triggers the operation and return a result.
|
||||
#
|
||||
# All the instance variables will be available in the result.
|
||||
#
|
||||
# ATTENTION: This must be implemented by the including class.
|
||||
#
|
||||
# @return [Hanami::Interactor::Result] the result of the operation
|
||||
#
|
||||
# @raise [NoMethodError] if this isn't implemented by the including class.
|
||||
#
|
||||
# @example Expose instance variables in result payload
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class Signup
|
||||
# include Hanami::Interactor
|
||||
# expose :user, :params
|
||||
#
|
||||
# def initialize(params)
|
||||
# @params = params
|
||||
# @foo = 'bar'
|
||||
# end
|
||||
#
|
||||
# def call
|
||||
# @user = UserRepository.new.create(@params)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# result = Signup.new(name: 'Luca').call
|
||||
# result.failure? # => false
|
||||
# result.successful? # => true
|
||||
#
|
||||
# result.user # => #<User:0x007fa311105778 @id=1 @name="Luca">
|
||||
# result.params # => { :name=>"Luca" }
|
||||
# result.foo # => raises NoMethodError
|
||||
#
|
||||
# @example Failed precondition
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class Signup
|
||||
# include Hanami::Interactor
|
||||
# expose :user
|
||||
#
|
||||
# def initialize(params)
|
||||
# @params = params
|
||||
# end
|
||||
#
|
||||
# # THIS WON'T BE INVOKED BECAUSE #valid? WILL RETURN false
|
||||
# def call
|
||||
# @user = UserRepository.new.create(@params)
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def valid?
|
||||
# @params.valid?
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# result = Signup.new(name: nil).call
|
||||
# result.successful? # => false
|
||||
# result.failure? # => true
|
||||
#
|
||||
# result.user # => #<User:0x007fa311105778 @id=nil @name="Luca">
|
||||
#
|
||||
# @example Bad usage
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class Signup
|
||||
# include Hanami::Interactor
|
||||
#
|
||||
# # Method #call is not defined
|
||||
# end
|
||||
#
|
||||
# Signup.new.call # => NoMethodError
|
||||
def call
|
||||
_call { super }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def _call
|
||||
catch :fail do
|
||||
validate!
|
||||
yield
|
||||
end
|
||||
|
||||
_prepare!
|
||||
end
|
||||
|
||||
# @since 0.3.5
|
||||
def validate!
|
||||
fail! unless valid?
|
||||
end
|
||||
end
|
||||
|
||||
# Interactor interface
|
||||
# @since 1.1.0
|
||||
module Interface
|
||||
# Triggers the operation and return a result.
|
||||
#
|
||||
# All the exposed instance variables will be available in the result.
|
||||
#
|
||||
# ATTENTION: This must be implemented by the including class.
|
||||
#
|
||||
# @return [Hanami::Interactor::Result] the result of the operation
|
||||
#
|
||||
# @raise [NoMethodError] if this isn't implemented by the including class.
|
||||
#
|
||||
# @example Expose instance variables in result payload
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class Signup
|
||||
# include Hanami::Interactor
|
||||
# expose :user, :params
|
||||
#
|
||||
# def call(params)
|
||||
# @params = params
|
||||
# @foo = 'bar'
|
||||
# @user = UserRepository.new.persist(User.new(params))
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# result = Signup.new(name: 'Luca').call
|
||||
# result.failure? # => false
|
||||
# result.successful? # => true
|
||||
#
|
||||
# result.user # => #<User:0x007fa311105778 @id=1 @name="Luca">
|
||||
# result.params # => { :name=>"Luca" }
|
||||
# result.foo # => raises NoMethodError
|
||||
#
|
||||
# @example Failed precondition
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class Signup
|
||||
# include Hanami::Interactor
|
||||
# expose :user
|
||||
#
|
||||
# # THIS WON'T BE INVOKED BECAUSE #valid? WILL RETURN false
|
||||
# def call(params)
|
||||
# @user = User.new(params)
|
||||
# @user = UserRepository.new.persist(@user)
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def valid?(params)
|
||||
# params.valid?
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# result = Signup.new.call(name: nil)
|
||||
# result.successful? # => false
|
||||
# result.failure? # => true
|
||||
#
|
||||
# result.user # => nil
|
||||
#
|
||||
# @example Bad usage
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class Signup
|
||||
# include Hanami::Interactor
|
||||
#
|
||||
# # Method #call is not defined
|
||||
# end
|
||||
#
|
||||
# Signup.new.call # => NoMethodError
|
||||
def call(*args, **kwargs)
|
||||
@__result = ::Hanami::Interactor::Result.new
|
||||
_call(*args, **kwargs) { super }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @api private
|
||||
# @since 1.1.0
|
||||
def _call(*args, **kwargs)
|
||||
catch :fail do
|
||||
validate!(*args, **kwargs)
|
||||
yield
|
||||
end
|
||||
|
||||
_prepare!
|
||||
end
|
||||
|
||||
# @since 1.1.0
|
||||
def validate!(*args, **kwargs)
|
||||
fail! unless valid?(*args, **kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Checks if proceed with <tt>#call</tt> invocation.
|
||||
# By default it returns <tt>true</tt>.
|
||||
#
|
||||
# Developers can override it.
|
||||
#
|
||||
# @return [TrueClass,FalseClass] the result of the check
|
||||
#
|
||||
# @since 0.3.5
|
||||
def valid?(*)
|
||||
true
|
||||
end
|
||||
|
||||
# Fails and interrupts the current flow.
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @example
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class CreateEmailTest
|
||||
# include Hanami::Interactor
|
||||
#
|
||||
# def initialize(params)
|
||||
# @params = params
|
||||
# end
|
||||
#
|
||||
# def call
|
||||
# persist_email_test!
|
||||
# capture_screenshot!
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def persist_email_test!
|
||||
# @email_test = EmailTestRepository.new.create(@params)
|
||||
# end
|
||||
#
|
||||
# # IF THIS RAISES AN EXCEPTION WE FORCE A FAILURE
|
||||
# def capture_screenshot!
|
||||
# Screenshot.new(@email_test).capture!
|
||||
# rescue
|
||||
# fail!
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# result = CreateEmailTest.new(account_id: 1).call
|
||||
# result.successful? # => false
|
||||
def fail!
|
||||
@__result.fail!
|
||||
throw :fail
|
||||
end
|
||||
|
||||
# Logs an error without interrupting the flow.
|
||||
#
|
||||
# When used, the returned result won't be successful.
|
||||
#
|
||||
# @param message [String] the error message
|
||||
#
|
||||
# @return false
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @see Hanami::Interactor#error!
|
||||
#
|
||||
# @example
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class CreateRecord
|
||||
# include Hanami::Interactor
|
||||
# expose :logger
|
||||
#
|
||||
# def initialize
|
||||
# @logger = []
|
||||
# end
|
||||
#
|
||||
# def call
|
||||
# prepare_data!
|
||||
# persist!
|
||||
# sync!
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def prepare_data!
|
||||
# @logger << __method__
|
||||
# error "Prepare data error"
|
||||
# end
|
||||
#
|
||||
# def persist!
|
||||
# @logger << __method__
|
||||
# error "Persist error"
|
||||
# end
|
||||
#
|
||||
# def sync!
|
||||
# @logger << __method__
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# result = CreateRecord.new.call
|
||||
# result.successful? # => false
|
||||
#
|
||||
# result.errors # => ["Prepare data error", "Persist error"]
|
||||
# result.logger # => [:prepare_data!, :persist!, :sync!]
|
||||
def error(message)
|
||||
@__result.add_error message
|
||||
false
|
||||
end
|
||||
|
||||
# Logs an error and interrupts the flow.
|
||||
#
|
||||
# When used, the returned result won't be successful.
|
||||
#
|
||||
# @param message [String] the error message
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @see Hanami::Interactor#error
|
||||
#
|
||||
# @example
|
||||
# require 'hanami/interactor'
|
||||
#
|
||||
# class CreateRecord
|
||||
# include Hanami::Interactor
|
||||
# expose :logger
|
||||
#
|
||||
# def initialize
|
||||
# @logger = []
|
||||
# end
|
||||
#
|
||||
# def call
|
||||
# prepare_data!
|
||||
# persist!
|
||||
# sync!
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def prepare_data!
|
||||
# @logger << __method__
|
||||
# error "Prepare data error"
|
||||
# end
|
||||
#
|
||||
# def persist!
|
||||
# @logger << __method__
|
||||
# error! "Persist error"
|
||||
# end
|
||||
#
|
||||
# # THIS WILL NEVER BE INVOKED BECAUSE WE USE #error! IN #persist!
|
||||
# def sync!
|
||||
# @logger << __method__
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# result = CreateRecord.new.call
|
||||
# result.successful? # => false
|
||||
#
|
||||
# result.errors # => ["Prepare data error", "Persist error"]
|
||||
# result.logger # => [:prepare_data!, :persist!]
|
||||
def error!(message)
|
||||
error(message)
|
||||
fail!
|
||||
end
|
||||
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def _prepare!
|
||||
@__result.prepare!(_exposures)
|
||||
end
|
||||
|
||||
# @since 0.5.0
|
||||
# @api private
|
||||
def _exposures
|
||||
::Hash[].tap do |result|
|
||||
self.class.exposures.each do |name, ivar|
|
||||
result[name] = instance_variable_defined?(ivar) ? instance_variable_get(ivar) : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @since 0.5.0
|
||||
# @api private
|
||||
module ClassMethods
|
||||
# @since 0.5.0
|
||||
# @api private
|
||||
def self.extended(interactor)
|
||||
interactor.class_eval do
|
||||
include Utils::ClassAttribute
|
||||
|
||||
class_attribute :exposures
|
||||
self.exposures = {}
|
||||
end
|
||||
end
|
||||
|
||||
def method_added(method_name)
|
||||
super
|
||||
return unless method_name == :call
|
||||
|
||||
if instance_method(:call).arity.zero?
|
||||
prepend Hanami::Interactor::LegacyInterface
|
||||
else
|
||||
prepend Hanami::Interactor::Interface
|
||||
end
|
||||
end
|
||||
|
||||
# Exposes local instance variables into the returning value of <tt>#call</tt>
|
||||
#
|
||||
# @param instance_variable_names [Symbol,Array<Symbol>] one or more instance
|
||||
# variable names
|
||||
#
|
||||
# @since 0.5.0
|
||||
#
|
||||
# @see Hanami::Interactor::Result
|
||||
#
|
||||
# @example Exposes instance variable
|
||||
#
|
||||
# class Signup
|
||||
# include Hanami::Interactor
|
||||
# expose :user
|
||||
#
|
||||
# def initialize(params)
|
||||
# @params = params
|
||||
# @user = User.new(@params[:user])
|
||||
# end
|
||||
#
|
||||
# def call
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# result = Signup.new(user: { name: "Luca" }).call
|
||||
#
|
||||
# result.user # => #<User:0x007fa85c58ccd8 @name="Luca">
|
||||
# result.params # => NoMethodError
|
||||
def expose(*instance_variable_names)
|
||||
instance_variable_names.each do |name|
|
||||
exposures[name.to_sym] = "@#{name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,141 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Hanami
|
||||
module Utils
|
||||
# BasicObject
|
||||
#
|
||||
# @since 0.3.5
|
||||
class BasicObject < ::BasicObject
|
||||
# Lookups constants at the top-level namespace, if they are missing in the
|
||||
# current context.
|
||||
#
|
||||
# @param name [Symbol] the constant name
|
||||
#
|
||||
# @return [Object, Module] the constant
|
||||
#
|
||||
# @raise [NameError] if the constant cannot be found
|
||||
#
|
||||
# @since 1.3.4
|
||||
# @api private
|
||||
#
|
||||
# @see https://ruby-doc.org/core/Module.html#method-i-const_missing
|
||||
def self.const_missing(name)
|
||||
::Object.const_get(name)
|
||||
end
|
||||
|
||||
# Returns the class for debugging purposes.
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @see http://ruby-doc.org/core/Object.html#method-i-class
|
||||
def class
|
||||
(class << self; self; end).superclass
|
||||
end
|
||||
|
||||
# Bare minimum inspect for debugging purposes.
|
||||
#
|
||||
# @return [String] the inspect string
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @see http://ruby-doc.org/core/Object.html#method-i-inspect
|
||||
def inspect
|
||||
"#<#{self.class}:#{'0x0000%x' % (__id__ << 1)}#{__inspect}>"
|
||||
end
|
||||
|
||||
# @!macro [attach] instance_of?(class)
|
||||
#
|
||||
# Determines if self is an instance of given class or module
|
||||
#
|
||||
# @param class [Class,Module] the class of module to verify
|
||||
#
|
||||
# @return [TrueClass,FalseClass] the result of the check
|
||||
#
|
||||
# @raise [TypeError] if the given argument is not of the expected types
|
||||
#
|
||||
# @since 1.3.2
|
||||
#
|
||||
# @see http://ruby-doc.org/core/Object.html#method-i-instance_of-3F
|
||||
define_method :instance_of?, ::Object.instance_method(:instance_of?)
|
||||
|
||||
# @!macro [attach] is_a?(class)
|
||||
#
|
||||
# Determines if self is of the type of the object class or module
|
||||
#
|
||||
# @param class [Class,Module] the class of module to verify
|
||||
#
|
||||
# @return [TrueClass,FalseClass] the result of the check
|
||||
#
|
||||
# @raise [TypeError] if the given argument is not of the expected types
|
||||
#
|
||||
# @since 1.3.2
|
||||
#
|
||||
# @see http://ruby-doc.org/core/Object.html#method-i-is_a-3F
|
||||
define_method :is_a?, ::Object.instance_method(:is_a?)
|
||||
|
||||
# @!macro [attach] kind_of?(class)
|
||||
#
|
||||
# Determines if self is of the kind of the object class or module
|
||||
#
|
||||
# @param class [Class,Module] the class of module to verify
|
||||
#
|
||||
# @return [TrueClass,FalseClass] the result of the check
|
||||
#
|
||||
# @raise [TypeError] if the given argument is not of the expected types
|
||||
#
|
||||
# @since 1.3.2
|
||||
#
|
||||
# @see http://ruby-doc.org/core/Object.html#method-i-kind_of-3F
|
||||
define_method :kind_of?, ::Object.instance_method(:kind_of?)
|
||||
|
||||
# Alias for __id__
|
||||
#
|
||||
# @return [Fixnum] the object id
|
||||
#
|
||||
# @since 0.9.0
|
||||
#
|
||||
# @see http://ruby-doc.org/core/Object.html#method-i-object_id
|
||||
def object_id
|
||||
__id__
|
||||
end
|
||||
|
||||
# Interface for pp
|
||||
#
|
||||
# @param printer [PP] the Pretty Printable printer
|
||||
# @return [String] the pretty-printable inspection of the object
|
||||
#
|
||||
# @since 0.9.0
|
||||
#
|
||||
# @see https://ruby-doc.org/stdlib/libdoc/pp/rdoc/PP.html
|
||||
def pretty_print(printer)
|
||||
printer.text(inspect)
|
||||
end
|
||||
|
||||
# Returns true if responds to the given method.
|
||||
#
|
||||
# @return [TrueClass,FalseClass] the result of the check
|
||||
#
|
||||
# @since 0.3.5
|
||||
#
|
||||
# @see http://ruby-doc.org/core-2.2.1/Object.html#method-i-respond_to-3F
|
||||
def respond_to?(method_name, include_all = false)
|
||||
respond_to_missing?(method_name, include_all)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Must be overridden by descendants
|
||||
#
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def respond_to_missing?(_method_name, _include_all)
|
||||
::Kernel.raise ::NotImplementedError
|
||||
end
|
||||
|
||||
# @since 0.3.5
|
||||
# @api private
|
||||
def __inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,6 +5,6 @@ module Hanami
|
|||
# Defines the version
|
||||
#
|
||||
# @since 0.1.0
|
||||
VERSION = "2.0.0.alpha6"
|
||||
VERSION = "2.0.0.beta1"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,767 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "hanami/interactor"
|
||||
|
||||
class LegacyInteractorWithoutInitialize
|
||||
include Hanami::Interactor
|
||||
|
||||
def call
|
||||
end
|
||||
end
|
||||
|
||||
class InteractorWithoutInitialize
|
||||
include Hanami::Interactor
|
||||
|
||||
def call(*)
|
||||
end
|
||||
end
|
||||
|
||||
class InteractorWithoutCall
|
||||
include Hanami::Interactor
|
||||
end
|
||||
|
||||
class InteractorWithMethodAdded
|
||||
module MethodAdded; end
|
||||
|
||||
module WatchMethods
|
||||
def method_added(method_name)
|
||||
super
|
||||
include MethodAdded if method_name == :call
|
||||
end
|
||||
end
|
||||
|
||||
include Hanami::Interactor
|
||||
extend WatchMethods
|
||||
|
||||
def call(*)
|
||||
end
|
||||
end
|
||||
|
||||
class User
|
||||
def initialize(attributes = {})
|
||||
@attributes = attributes
|
||||
end
|
||||
|
||||
def name
|
||||
@attributes.fetch(:name, nil)
|
||||
end
|
||||
|
||||
def name=(value)
|
||||
@attributes[:name] = value
|
||||
end
|
||||
|
||||
def persist!
|
||||
raise if name.nil?
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{name: name}
|
||||
end
|
||||
end
|
||||
|
||||
class LegacySignup
|
||||
include Hanami::Interactor
|
||||
expose :user, :params
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
@user = User.new(params)
|
||||
@__foo = 23
|
||||
end
|
||||
|
||||
def call
|
||||
@user.persist!
|
||||
rescue StandardError
|
||||
fail!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid?
|
||||
!@params[:force_failure]
|
||||
end
|
||||
end
|
||||
|
||||
class Signup
|
||||
include Hanami::Interactor
|
||||
expose :user, :params
|
||||
|
||||
def initialize(force_failure: false)
|
||||
@force_failure = force_failure
|
||||
end
|
||||
|
||||
def call(**params)
|
||||
@params = params
|
||||
@user = User.new(params)
|
||||
@user.persist!
|
||||
rescue StandardError
|
||||
fail!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid?(*)
|
||||
!@force_failure
|
||||
end
|
||||
end
|
||||
|
||||
class ComplexCall
|
||||
include Hanami::Interactor
|
||||
expose :args, :kwargs
|
||||
|
||||
def call(*args, **kwargs)
|
||||
@args = args
|
||||
@kwargs = kwargs
|
||||
end
|
||||
end
|
||||
|
||||
class CallWithoutKwargs
|
||||
include Hanami::Interactor
|
||||
expose :args
|
||||
|
||||
def call(*args)
|
||||
@args = args
|
||||
end
|
||||
end
|
||||
|
||||
class LegacyErrorInteractor
|
||||
include Hanami::Interactor
|
||||
expose :operations
|
||||
|
||||
def initialize
|
||||
@operations = []
|
||||
end
|
||||
|
||||
def call
|
||||
prepare!
|
||||
persist!
|
||||
log!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare!
|
||||
@operations << __method__
|
||||
error "There was an error while preparing data."
|
||||
end
|
||||
|
||||
def persist!
|
||||
@operations << __method__
|
||||
error "There was an error while persisting data."
|
||||
end
|
||||
|
||||
def log!
|
||||
@operations << __method__
|
||||
end
|
||||
end
|
||||
|
||||
class ErrorInteractor
|
||||
include Hanami::Interactor
|
||||
expose :operations
|
||||
|
||||
def call(*)
|
||||
@operations = []
|
||||
prepare!
|
||||
persist!
|
||||
log!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare!
|
||||
@operations << __method__
|
||||
error "There was an error while preparing data."
|
||||
end
|
||||
|
||||
def persist!
|
||||
@operations << __method__
|
||||
error "There was an error while persisting data."
|
||||
end
|
||||
|
||||
def log!
|
||||
@operations << __method__
|
||||
end
|
||||
end
|
||||
|
||||
class LegacyErrorBangInteractor
|
||||
include Hanami::Interactor
|
||||
expose :operations
|
||||
|
||||
def initialize
|
||||
@operations = []
|
||||
end
|
||||
|
||||
def call
|
||||
persist!
|
||||
sync!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def persist!
|
||||
@operations << __method__
|
||||
error! "There was an error while persisting data."
|
||||
end
|
||||
|
||||
def sync!
|
||||
@operations << __method__
|
||||
error "There was an error while syncing data."
|
||||
end
|
||||
end
|
||||
|
||||
class ErrorBangInteractor
|
||||
include Hanami::Interactor
|
||||
expose :operations
|
||||
|
||||
def call(*)
|
||||
@operations = []
|
||||
persist!
|
||||
sync!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def persist!
|
||||
@operations << __method__
|
||||
error! "There was an error while persisting data."
|
||||
end
|
||||
|
||||
def sync!
|
||||
@operations << __method__
|
||||
error "There was an error while syncing data."
|
||||
end
|
||||
end
|
||||
|
||||
class LegacyPublishVideo
|
||||
include Hanami::Interactor
|
||||
|
||||
def call
|
||||
end
|
||||
|
||||
def valid?
|
||||
owns?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def owns?
|
||||
# fake failed ownership check
|
||||
error("You're not owner of this video")
|
||||
end
|
||||
end
|
||||
|
||||
class PublishVideo
|
||||
include Hanami::Interactor
|
||||
expose :video_name
|
||||
|
||||
def call(*)
|
||||
@video_name = "H2G2"
|
||||
end
|
||||
|
||||
def valid?(*)
|
||||
owns?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def owns?
|
||||
# fake failed ownership check
|
||||
error("You're not owner of this video")
|
||||
end
|
||||
end
|
||||
|
||||
class LegacyCreateUser
|
||||
include Hanami::Interactor
|
||||
expose :user
|
||||
|
||||
def initialize(**params)
|
||||
@user = User.new(**params)
|
||||
end
|
||||
|
||||
def call
|
||||
persist
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def persist
|
||||
@user.persist!
|
||||
end
|
||||
end
|
||||
|
||||
class CreateUser
|
||||
include Hanami::Interactor
|
||||
expose :user
|
||||
|
||||
def call(**params)
|
||||
build_user(**params)
|
||||
persist
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_user(params)
|
||||
@user = User.new(params)
|
||||
end
|
||||
|
||||
def persist
|
||||
@user.persist!
|
||||
end
|
||||
end
|
||||
|
||||
class LegacyUpdateUser < LegacyCreateUser
|
||||
def initialize(_user, **params)
|
||||
super(**params)
|
||||
@user.name = params.fetch(:name)
|
||||
end
|
||||
end
|
||||
|
||||
class UpdateUser < CreateUser
|
||||
def build_user(user:, **params)
|
||||
@user = user
|
||||
@user.name = params.fetch(:name)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Hanami::Interactor do
|
||||
describe "interactor interface" do
|
||||
it "includes the correct interface" do
|
||||
expect(LegacySignup.ancestors).to include(Hanami::Interactor::LegacyInterface)
|
||||
expect(Signup.ancestors).to include(Hanami::Interactor::Interface)
|
||||
end
|
||||
|
||||
it "does not include the other interface" do
|
||||
expect(LegacySignup.ancestors).not_to include(Hanami::Interactor::Interface)
|
||||
expect(Signup.ancestors).not_to include(Hanami::Interactor::LegacyInterface)
|
||||
end
|
||||
|
||||
it "raises error when #call isn't implemented" do
|
||||
expect { InteractorWithoutCall.new.call }.to raise_error NoMethodError
|
||||
end
|
||||
|
||||
it "lets .method_added open to overrides" do
|
||||
expect(InteractorWithMethodAdded.ancestors).to include(InteractorWithMethodAdded::MethodAdded)
|
||||
end
|
||||
end
|
||||
|
||||
describe "legacy interface" do
|
||||
describe "#initialize" do
|
||||
it "works when it isn't overridden" do
|
||||
LegacyInteractorWithoutInitialize.new
|
||||
end
|
||||
|
||||
it "allows to override it" do
|
||||
LegacySignup.new({})
|
||||
end
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
it "returns a result" do
|
||||
result = LegacySignup.new(name: "Luca").call
|
||||
expect(result.class).to eq Hanami::Interactor::Result
|
||||
end
|
||||
|
||||
it "is successful by default" do
|
||||
result = LegacySignup.new(name: "Luca").call
|
||||
expect(result).to be_successful
|
||||
end
|
||||
|
||||
it "returns the payload" do
|
||||
result = LegacySignup.new(name: "Luca").call
|
||||
|
||||
expect(result.user.name).to eq "Luca"
|
||||
expect(result.params).to eq(name: "Luca")
|
||||
end
|
||||
|
||||
it "doesn't include private ivars" do
|
||||
result = LegacySignup.new(name: "Luca").call
|
||||
|
||||
expect { result.__foo }.to raise_error NoMethodError
|
||||
end
|
||||
|
||||
it "exposes a convenient API for handling failures" do
|
||||
result = LegacySignup.new({}).call
|
||||
expect(result).to be_failure
|
||||
end
|
||||
|
||||
it "doesn't invoke it if the preconditions are failing" do
|
||||
result = LegacySignup.new(force_failure: true).call
|
||||
expect(result).to be_failure
|
||||
end
|
||||
|
||||
describe "inheritance" do
|
||||
it "is successful for super class" do
|
||||
result = LegacyCreateUser.new(name: "L").call
|
||||
|
||||
expect(result).to be_successful
|
||||
expect(result.user.name).to eq "L"
|
||||
end
|
||||
|
||||
it "is successful for sub class" do
|
||||
user = User.new(name: "L")
|
||||
result = LegacyUpdateUser.new(user, name: "MG").call
|
||||
|
||||
expect(result).to be_successful
|
||||
expect(result.user.name).to eq "MG"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#error" do
|
||||
it "isn't successful" do
|
||||
result = LegacyErrorInteractor.new.call
|
||||
expect(result).to be_failure
|
||||
end
|
||||
|
||||
it "accumulates errors" do
|
||||
result = LegacyErrorInteractor.new.call
|
||||
expect(result.errors).to eq [
|
||||
"There was an error while preparing data.",
|
||||
"There was an error while persisting data."
|
||||
]
|
||||
end
|
||||
|
||||
it "doesn't interrupt the flow" do
|
||||
result = LegacyErrorInteractor.new.call
|
||||
expect(result.operations).to eq %i[prepare! persist! log!]
|
||||
end
|
||||
|
||||
# See https://github.com/hanami/utils/issues/69
|
||||
it "returns false as control flow for caller" do
|
||||
interactor = LegacyPublishVideo.new
|
||||
expect(interactor).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "#error!" do
|
||||
it "isn't successful" do
|
||||
result = LegacyErrorBangInteractor.new.call
|
||||
expect(result).to be_failure
|
||||
end
|
||||
|
||||
it "stops at the first error" do
|
||||
result = LegacyErrorBangInteractor.new.call
|
||||
expect(result.errors).to eq [
|
||||
"There was an error while persisting data."
|
||||
]
|
||||
end
|
||||
|
||||
it "interrupts the flow" do
|
||||
result = LegacyErrorBangInteractor.new.call
|
||||
expect(result.operations).to eq [:persist!]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "new interface" do
|
||||
describe "#initialize" do
|
||||
it "works when it isn't overridden" do
|
||||
InteractorWithoutInitialize.new.call
|
||||
end
|
||||
|
||||
it "allows to override it" do
|
||||
Signup.new.call
|
||||
end
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
it "returns a result" do
|
||||
result = Signup.new.call(name: "Luca")
|
||||
expect(result.class).to eq Hanami::Interactor::Result
|
||||
end
|
||||
|
||||
it "is successful by default" do
|
||||
result = Signup.new.call(name: "Luca")
|
||||
expect(result).to be_successful
|
||||
end
|
||||
|
||||
it "returns the payload" do
|
||||
result = Signup.new.call(name: "Luca")
|
||||
|
||||
expect(result.user.name).to eq "Luca"
|
||||
expect(result.params).to eq(name: "Luca")
|
||||
end
|
||||
|
||||
it "doesn't include private ivars" do
|
||||
result = Signup.new.call(name: "Luca")
|
||||
|
||||
expect { result.force_failure }.to raise_error NoMethodError
|
||||
end
|
||||
|
||||
it "exposes a convenient API for handling failures" do
|
||||
result = Signup.new.call
|
||||
expect(result).to be_failure
|
||||
end
|
||||
|
||||
it "doesn't invoke it if the preconditions are failing" do
|
||||
result = Signup.new(force_failure: true).call
|
||||
expect(result).to be_failure
|
||||
end
|
||||
|
||||
it "raises error when #call isn't implemented" do
|
||||
expect { InteractorWithoutCall.new.call }.to raise_error NoMethodError
|
||||
end
|
||||
|
||||
it "handles args and kwargs" do
|
||||
result = ComplexCall.new.call("foo", "bar", baz: "baz", buzz: "buzz")
|
||||
expect(result.args).to eql(%w[foo bar])
|
||||
expect(result.kwargs).to eql(Hash[baz: "baz", buzz: "buzz"])
|
||||
end
|
||||
|
||||
it "handles args without kwargs" do
|
||||
result = CallWithoutKwargs.new.call("foo", "bar")
|
||||
expect(result.args).to eql(%w[foo bar])
|
||||
end
|
||||
|
||||
it "handles kwargs without args" do
|
||||
result = ComplexCall.new.call(baz: "baz", buzz: "buzz")
|
||||
expect(result.args).to eql(Array[])
|
||||
expect(result.kwargs).to eql(Hash[baz: "baz", buzz: "buzz"])
|
||||
end
|
||||
|
||||
it "handles args with to_hash method" do
|
||||
user = User.new(name: "Luca")
|
||||
result = CallWithoutKwargs.new.call(user)
|
||||
expect(result.args).to eql(Array[user])
|
||||
end
|
||||
|
||||
describe "inheritance" do
|
||||
it "is successful for super class" do
|
||||
result = CreateUser.new.call(name: "L")
|
||||
|
||||
expect(result).to be_successful
|
||||
expect(result.user.name).to eq "L"
|
||||
end
|
||||
|
||||
it "is successful for sub class" do
|
||||
user = User.new(name: "L")
|
||||
result = UpdateUser.new.call(user: user, name: "MG")
|
||||
|
||||
expect(result).to be_successful
|
||||
expect(result.user.name).to eq "MG"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#error" do
|
||||
it "isn't successful" do
|
||||
result = ErrorInteractor.new.call
|
||||
expect(result).to be_failure
|
||||
end
|
||||
|
||||
it "accumulates errors" do
|
||||
result = ErrorInteractor.new.call
|
||||
expect(result.errors).to eq [
|
||||
"There was an error while preparing data.",
|
||||
"There was an error while persisting data."
|
||||
]
|
||||
end
|
||||
|
||||
it "doesn't interrupt the flow" do
|
||||
result = ErrorInteractor.new.call
|
||||
expect(result.operations).to eq %i[prepare! persist! log!]
|
||||
end
|
||||
|
||||
# See https://github.com/hanami/utils/issues/69
|
||||
it "returns false as control flow for caller" do
|
||||
result = PublishVideo.new.call
|
||||
expect(result).not_to be_successful
|
||||
expect(result.video_name).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#error!" do
|
||||
it "isn't successful" do
|
||||
result = ErrorBangInteractor.new.call
|
||||
expect(result).to be_failure
|
||||
end
|
||||
|
||||
it "stops at the first error" do
|
||||
result = ErrorBangInteractor.new.call
|
||||
expect(result.errors).to eq [
|
||||
"There was an error while persisting data."
|
||||
]
|
||||
end
|
||||
|
||||
it "interrupts the flow" do
|
||||
result = ErrorBangInteractor.new.call
|
||||
expect(result.operations).to eq [:persist!]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Hanami::Interactor::Result do
|
||||
describe "#initialize" do
|
||||
it "allows to skip payload" do
|
||||
Hanami::Interactor::Result.new
|
||||
end
|
||||
|
||||
it "accepts a payload" do
|
||||
result = Hanami::Interactor::Result.new(foo: "bar")
|
||||
expect(result.foo).to eq "bar"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#successful?" do
|
||||
it "is successful by default" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
expect(result).to be_successful
|
||||
end
|
||||
|
||||
describe "when it has errors" do
|
||||
it "isn't successful" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
result.add_error "There was a problem"
|
||||
expect(result).to be_failure
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#fail!" do
|
||||
it "causes a failure" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
result.fail!
|
||||
|
||||
expect(result).to be_failure
|
||||
end
|
||||
end
|
||||
|
||||
describe "#prepare!" do
|
||||
it "merges the current payload" do
|
||||
result = Hanami::Interactor::Result.new(foo: "bar")
|
||||
result.prepare!(foo: 23)
|
||||
|
||||
expect(result.foo).to eq 23
|
||||
end
|
||||
|
||||
it "returns self" do
|
||||
result = Hanami::Interactor::Result.new(foo: "bar")
|
||||
returning = result.prepare!(foo: 23)
|
||||
|
||||
expect(returning).to eq result
|
||||
end
|
||||
end
|
||||
|
||||
describe "#errors" do
|
||||
it "empty by default" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
expect(result.errors).to be_empty
|
||||
end
|
||||
|
||||
it "returns all the errors" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
result.add_error ["Error 1", "Error 2"]
|
||||
|
||||
expect(result.errors).to eq ["Error 1", "Error 2"]
|
||||
end
|
||||
|
||||
it "prevents information escape" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
result.add_error ["Error 1", "Error 2"]
|
||||
|
||||
result.errors.clear
|
||||
|
||||
expect(result.errors).to eq ["Error 1", "Error 2"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "#error" do
|
||||
it "nil by default" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
expect(result.error).to be_nil
|
||||
end
|
||||
|
||||
it "returns only the first error" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
result.add_error ["Error 1", "Error 2"]
|
||||
|
||||
expect(result.error).to eq "Error 1"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#respond_to?" do
|
||||
it "returns true for concrete methods" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
|
||||
expect(result).to respond_to(:successful?)
|
||||
expect(result).to respond_to("successful?")
|
||||
|
||||
expect(result).to respond_to(:failure?)
|
||||
expect(result).to respond_to("failure?")
|
||||
end
|
||||
|
||||
it "returns true for methods derived from payload" do
|
||||
result = Hanami::Interactor::Result.new(foo: 1)
|
||||
|
||||
expect(result).to respond_to(:foo)
|
||||
expect(result).to respond_to("foo")
|
||||
end
|
||||
|
||||
it "returns true for methods derived from merged payload" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
result.prepare!(bar: 2)
|
||||
|
||||
expect(result).to respond_to(:bar)
|
||||
expect(result).to respond_to("bar")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#inspect" do
|
||||
let(:result) { Hanami::Interactor::Result.new(id: 23, user: User.new) }
|
||||
|
||||
it "reports the class name and the object_id" do
|
||||
expect(result.inspect).to match %(#<Hanami::Interactor::Result)
|
||||
end
|
||||
|
||||
it "reports the object_id" do
|
||||
object_id = format("%x", (result.__id__ << 1))
|
||||
expect(result.inspect).to match object_id
|
||||
end
|
||||
|
||||
it "reports @success" do
|
||||
expect(result.inspect).to match %(@success=true)
|
||||
end
|
||||
|
||||
it "reports @payload" do
|
||||
expect(result.inspect).to match %(@payload={:id=>23, :user=>#<User:)
|
||||
end
|
||||
end
|
||||
|
||||
describe "payload" do
|
||||
it "returns all the values passed in the payload" do
|
||||
result = Hanami::Interactor::Result.new(a: 1, b: 2)
|
||||
expect(result.a).to eq 1
|
||||
expect(result.b).to eq 2
|
||||
end
|
||||
|
||||
it "returns hash values passed in the payload" do
|
||||
result = Hanami::Interactor::Result.new(a: {100 => 3})
|
||||
expect(result.a).to eq(100 => 3)
|
||||
end
|
||||
|
||||
it "returns all the values after a merge" do
|
||||
result = Hanami::Interactor::Result.new(a: 1, b: 2)
|
||||
result.prepare!(a: 23, c: 3)
|
||||
|
||||
expect(result.a).to eq 23
|
||||
expect(result.b).to eq 2
|
||||
expect(result.c).to eq 3
|
||||
end
|
||||
|
||||
it "doesn't ignore forwarded messages" do
|
||||
result = Hanami::Interactor::Result.new(params: {name: "Luca"})
|
||||
expect(result.params[:name]).to eq "Luca"
|
||||
end
|
||||
|
||||
it "raises an error when unknown message is passed" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
expect { result.unknown }.to raise_error NoMethodError
|
||||
end
|
||||
|
||||
it "raises an error when unknown message is passed with args" do
|
||||
result = Hanami::Interactor::Result.new
|
||||
expect { result.unknown(:foo) }.to raise_error NoMethodError
|
||||
end
|
||||
end
|
||||
end
|
|
@ -93,7 +93,7 @@ RSpec.describe Hanami::Logger do
|
|||
|
||||
describe "and it does not exist" do
|
||||
before do
|
||||
File.delete(stream) if File.exist?(stream)
|
||||
FileUtils.rm_rf(stream)
|
||||
end
|
||||
|
||||
it "writes to file" do
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "hanami/utils/basic_object"
|
||||
require "pp"
|
||||
|
||||
class ExternalTestClass
|
||||
end
|
||||
|
||||
class TestClass < Hanami::Utils::BasicObject
|
||||
class InternalTestClass
|
||||
end
|
||||
|
||||
def internal
|
||||
InternalTestClass
|
||||
end
|
||||
|
||||
def external
|
||||
ExternalTestClass
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Hanami::Utils::BasicObject do
|
||||
describe ".const_missing" do
|
||||
subject { TestClass.new }
|
||||
|
||||
it "lookups constants at the top-level namespace" do
|
||||
expect(subject.internal).to eq(TestClass::InternalTestClass)
|
||||
expect(subject.external).to eq(ExternalTestClass)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#respond_to_missing?" do
|
||||
it "raises an exception if respond_to? method is not implemented" do
|
||||
expect { TestClass.new.respond_to?(:no_existing_method) }
|
||||
.to raise_error(NotImplementedError)
|
||||
end
|
||||
|
||||
it "returns true given respond_to? method was implemented" do
|
||||
TestCase = Class.new(TestClass) do
|
||||
def respond_to?(_method_name, _include_all = false)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
expect(TestCase.new).to respond_to(:no_existing_method)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#class" do
|
||||
it "returns TestClass" do
|
||||
expect(TestClass.new.class).to eq TestClass
|
||||
end
|
||||
end
|
||||
|
||||
describe "#inspect" do
|
||||
it "returns the inspect message" do
|
||||
inspect_msg = TestClass.new.inspect
|
||||
expect(inspect_msg).to match(/\A#<TestClass:\w+>\z/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pretty_print" do
|
||||
# See https://github.com/hanami/hanami/issues/629
|
||||
it "is pretty printable" do
|
||||
expect { pp TestClass.new }.to output(/TestClass/).to_stdout
|
||||
end
|
||||
|
||||
# See https://github.com/hanami/utils/issues/234
|
||||
it "outputs the inspection to the given printer" do
|
||||
printer = PP.new
|
||||
subject = TestClass.new
|
||||
subject.pretty_print(printer)
|
||||
|
||||
expect(printer.output).to match(/\A#<TestClass:\w+>\z/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#instance_of?" do
|
||||
subject { TestClass.new }
|
||||
|
||||
context "when object is instance of the given class" do
|
||||
it "returns true" do
|
||||
expect(subject.instance_of?(TestClass)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when object is not instance of the given class" do
|
||||
it "returns false" do
|
||||
expect(subject.instance_of?(::String)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when given argument is not a class or module" do
|
||||
it "raises error" do
|
||||
expect { subject.instance_of?("foo") }.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#is_a?" do
|
||||
subject { TestClass.new }
|
||||
let(:test_class) { TestClass }
|
||||
|
||||
context "when object is instance of the given class" do
|
||||
it "returns true" do
|
||||
expect(subject.is_a?(TestClass)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when object is not instance of the given class" do
|
||||
it "returns false" do
|
||||
expect(subject.is_a?(::String)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when given argument is not a class or module" do
|
||||
it "raises error" do
|
||||
expect { subject.is_a?("foo") }.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when object is instance of the subclass" do
|
||||
subject { Class.new(TestClass).new }
|
||||
|
||||
it "returns true" do
|
||||
expect(subject.is_a?(TestClass)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when object has given module included" do
|
||||
subject do
|
||||
m = mod
|
||||
Class.new { include m }.new
|
||||
end
|
||||
|
||||
let(:mod) { Module.new }
|
||||
|
||||
it "returns true" do
|
||||
expect(subject.is_a?(mod)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Style/ClassCheck
|
||||
describe "#kind_of?" do
|
||||
context "when object is instance of the given class" do
|
||||
subject { TestClass.new }
|
||||
|
||||
it "returns true" do
|
||||
expect(subject.kind_of?(TestClass)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when object is instance of the subclass" do
|
||||
subject { Class.new(TestClass).new }
|
||||
|
||||
it "returns true" do
|
||||
expect(subject.kind_of?(TestClass)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when object is not instance of the given class" do
|
||||
it "returns false" do
|
||||
expect(subject.kind_of?(::String)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when given argument is not a class or module" do
|
||||
it "raises error" do
|
||||
expect { subject.kind_of?("foo") }.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when object has given module included" do
|
||||
subject do
|
||||
m = mod
|
||||
Class.new { include m }.new
|
||||
end
|
||||
|
||||
let(:mod) { Module.new }
|
||||
|
||||
it "returns true" do
|
||||
expect(subject.kind_of?(mod)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Style/ClassCheck
|
||||
end
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
RSpec.describe "Hanami::Utils::VERSION" do
|
||||
it "exposes version" do
|
||||
expect(Hanami::Utils::VERSION).to eq("2.0.0.alpha6")
|
||||
expect(Hanami::Utils::VERSION).to eq("2.0.0.beta1")
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue