Prepare for v2.0.0.beta1

This commit is contained in:
Luca Guidi 2022-07-10 14:47:43 +02:00
parent 7ef19f3ad5
commit 6d9c7bf986
No known key found for this signature in database
GPG Key ID: 319B8D04DABE74E0
10 changed files with 159 additions and 1738 deletions

View File

@ -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!`

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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