diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b118a..a2c60d6 100644 --- a/CHANGELOG.md +++ b/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!` diff --git a/README.md b/README.md index b187f77..275820c 100644 --- a/README.md +++ b/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 diff --git a/hanami-utils.gemspec b/hanami-utils.gemspec index 08b4417..0874b92 100644 --- a/hanami-utils.gemspec +++ b/hanami-utils.gemspec @@ -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" diff --git a/lib/hanami/interactor.rb b/lib/hanami/interactor.rb deleted file mode 100644 index 2686bf6..0000000 --- a/lib/hanami/interactor.rb +++ /dev/null @@ -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 Module#included. - # - # @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] 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 # => # - # 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 # => # - # - # @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 # => # - # 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 #call invocation. - # By default it returns true. - # - # 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 #call - # - # @param instance_variable_names [Symbol,Array] 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 # => # - # result.params # => NoMethodError - def expose(*instance_variable_names) - instance_variable_names.each do |name| - exposures[name.to_sym] = "@#{name}" - end - end - end -end diff --git a/lib/hanami/utils/basic_object.rb b/lib/hanami/utils/basic_object.rb deleted file mode 100644 index a764dc5..0000000 --- a/lib/hanami/utils/basic_object.rb +++ /dev/null @@ -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 diff --git a/lib/hanami/utils/version.rb b/lib/hanami/utils/version.rb index 0f7c69b..8ec0766 100644 --- a/lib/hanami/utils/version.rb +++ b/lib/hanami/utils/version.rb @@ -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 diff --git a/spec/unit/hanami/interactor_spec.rb b/spec/unit/hanami/interactor_spec.rb deleted file mode 100644 index c1a06fc..0000000 --- a/spec/unit/hanami/interactor_spec.rb +++ /dev/null @@ -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 %(#23, :user=># 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 diff --git a/spec/unit/hanami/logger_spec.rb b/spec/unit/hanami/logger_spec.rb index bc3ef32..37d7b96 100644 --- a/spec/unit/hanami/logger_spec.rb +++ b/spec/unit/hanami/logger_spec.rb @@ -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 diff --git a/spec/unit/hanami/utils/basic_object_spec.rb b/spec/unit/hanami/utils/basic_object_spec.rb deleted file mode 100644 index 0d9f2b2..0000000 --- a/spec/unit/hanami/utils/basic_object_spec.rb +++ /dev/null @@ -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#\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#\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 diff --git a/spec/unit/hanami/utils/version_spec.rb b/spec/unit/hanami/utils/version_spec.rb index b072b79..f0f9483 100644 --- a/spec/unit/hanami/utils/version_spec.rb +++ b/spec/unit/hanami/utils/version_spec.rb @@ -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