Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiple success failures types #41

Merged
merged 20 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---

### Added

- Rspec Helper mock_service;
- Rspec Matcher have_succeed_with and have_failed_with
- Add Rspec Helper mock_service;
- Add Rspec Matcher have_succeed_with and have_failed_with;
- Add `Success()`, `Failure()`, `Check()`, `Try()` now can be multipe types;

## 0.2.0
### Added
Expand Down
8 changes: 4 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ GEM
jaro_winkler (1.5.4)
maruku (0.7.3)
method_source (1.0.0)
mini_portile2 (2.8.0)
nokogiri (1.13.10)
mini_portile2 (2.8.1)
nokogiri (1.14.1)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
parallel (1.19.1)
Expand All @@ -28,7 +28,7 @@ GEM
method_source (~> 1.0)
pry-nav (1.0.0)
pry (>= 0.9.10, < 0.15)
racc (1.6.1)
racc (1.6.2)
rainbow (3.0.0)
rake (13.0.1)
reverse_markdown (1.4.0)
Expand Down Expand Up @@ -100,4 +100,4 @@ DEPENDENCIES
yard

BUNDLED WITH
2.1.4
2.2.32
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,17 @@ end

The next step is writing the `#run` method, which is where the work should be done.
Use the methods `#Success` and `#Failure` to handle your return values.
You can optionally specify a type and a value for your result.
You can optionally specify a list of types which represents that result and a value for your result.

```ruby
class User::Create < FService::Base
# ...
def run
return Failure(:no_name) if @name.nil?
return Failure(:no_name, :invalid_attribute) if @name.nil?

user = UserRepository.create(name: @name)
if user.valid?
Success(:created, data: user)
if user.save
Success(:success, :created, data: user)
else
Failure(:creation_failed, data: user.errors)
end
Expand Down Expand Up @@ -229,10 +229,10 @@ You can use `Check` to converts a boolean to a Result, truthy values map to `Suc

```ruby
Check(:math_works) { 1 < 2 }
# => #<Success @value=true, @type=:math_works>
# => #<Success @value=true, @types=[:math_works]>

Check(:math_works) { 1 > 2 }
# => #<Failure @error=false, @type=:math_works>
# => #<Failure @error=false, @types=[:math_works]>
```

`Try` transforms an exception into a `Failure` if some exception is raised for the given block. You can specify which exception class to watch for
Expand All @@ -251,10 +251,10 @@ class IHateEvenNumbers < FService::Base
end

IHateEvenNumbers.call
# => #<Success @value=9, @type=:rand_int>
# => #<Success @value=9, @types=[:rand_int]>

IHateEvenNumbers.call
# => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @type=:rand_int>
# => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:rand_int]>
```

## Testing
Expand All @@ -278,19 +278,19 @@ mock_service(Uer::Create)
mock_service(Uer::Create, result: :success)
# => Mocks a successful result with all values nil

mock_service(Uer::Create, result: :success, type: :created)
mock_service(Uer::Create, result: :success, types: [:created, :success])
# => Mocks a successful result with type created

mock_service(Uer::Create, result: :success, type: :created, value: instance_spy(User))
mock_service(Uer::Create, result: :success, types: :created, value: instance_spy(User))
# => Mocks a successful result with type created and a value

mock_service(Uer::Create, result: :failure)
# => Mocs a failure with all nil values

mock_service(Uer::Create, result: :failure, type: :invalid_attributes)
mock_service(Uer::Create, result: :failure, type: [:unprocessable_entity, :client_error])
bvicenzo marked this conversation as resolved.
Show resolved Hide resolved
# => Mocs a failure with a failure type

mock_service(Uer::Create, result: :failure, type: :invalid_attributes, value: { name: ["can't be blank"] })
mock_service(Uer::Create, result: :failure, type: [:unprocessable_entity, :client_error], value: { name: ["can't be blank"] })
bvicenzo marked this conversation as resolved.
Show resolved Hide resolved
# => Mocs a failure with a failure type and an error value
```

Expand Down
12 changes: 12 additions & 0 deletions lib/f_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,16 @@ def self.deprecate!(name:, alternative:, from: nil)

warn warn_message.join("\n")
end

# Marks an argument as deprecated
#
# @api private
def self.deprecate_argument_name(name:, argument_name:, alternative:, from: nil)
warn_message = ["\n[DEPRECATED] #{name} passing #{argument_name.inspect} is deprecated; "]
warn_message << ["called from #{from}; "] unless from.nil?
warn_message << "use #{alternative} instead. "
warn_message << 'It will be removed on the next release.'

warn warn_message.join("\n")
end
end
62 changes: 31 additions & 31 deletions lib/f_service/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,97 +109,97 @@ def success(data = nil)
end

# Returns a successful result.
# You can optionally specify a type and a value for your result.
# You can optionally specify a list of types and a value for your result.
# You'll probably want to return this inside {#run}.
#
#
# @example
# def run
# Success()
# # => #<Success @value=nil, @type=nil>
# # => #<Success @value=nil, @types=[]>
#
# Success(:ok)
# # => #<Success @value=nil, @type=:ok>
# # => #<Success @value=nil, @types=[:ok]>
#
# Success(data: 10)
# # => #<Success @value=10, @type=nil>
# # => #<Success @value=10, @types=[]>
#
# Success(:ok, data: 10)
# # => #<Success @value=10, @type=:ok>
# # => #<Success @value=10, @types=[:ok]>
# end
#
# @param type the Result type
# @param types the Result types
# @param data the result value
# @return [Result::Success] a successful result
def Success(type = nil, data: nil)
Result::Success.new(data, type)
def Success(*types, data: nil)
Result::Success.new(data, types)
end

# Returns a failed result.
# You can optionally specify a type and a value for your result.
# You can optionally specify types and a value for your result.
# You'll probably want to return this inside {#run}.
#
#
# @example
# def run
# Failure()
# # => #<Failure @error=nil, @type=nil>
# # => #<Failure @error=nil, @types=[]>
#
# Failure(:not_a_number)
# # => #<Failure @error=nil, @type=:not_a_number>
# # => #<Failure @error=nil, @types=[:not_a_number]>
#
# Failure(data: "10")
# # => #<Failure @error="10", @type=nil>
# # => #<Failure @error="10", @types=[]>
#
# Failure(:not_a_number, data: "10")
# # => #<Failure @error="10", @type=:not_a_number>
# # => #<Failure @error="10", @types=[:not_a_number]>
# end
#
# @param type the Result type
# @param types the Result types
# @param data the result value
# @return [Result::Failure] a failed result
def Failure(type = nil, data: nil)
Result::Failure.new(data, type)
def Failure(*types, data: nil)
Result::Failure.new(data, types)
end

# Converts a boolean to a Result.
# Truthy values map to Success, and falsey values map to Failures.
# You can optionally provide a type for the result.
# You can optionally provide a types for the result.
# The result value defaults as the evaluated value of the given block.
# If you want another value you can pass it through the `data:` argument.
#
# @example
# class CheckMathWorks < FService::Base
# def run
# Check(:math_works) { 1 < 2 }
# # => #<Success @value=true, @type=:math_works>
# # => #<Success @value=true, @types=[:math_works]>
#
# Check(:math_works) { 1 > 2 }
# # => #<Failure @error=false, @type=:math_works>
# # => #<Failure @error=false, @types=[:math_works]>
#
# Check(:math_works, data: 1 + 2) { 1 > 2 }
# # => #<Failure @type=:math_works, @error=3>
# # => #<Failure @types=:math_works, @error=3>
# end
#
# Check(:math_works, data: 1 + 2) { 1 < 2 }
# # => #<Success @type=:math_works, @value=3>
# # => #<Success @types=[:math_works], @value=3>
# end
# end
#
# @param type the Result type
# @param types the Result types
# @return [Result::Success, Result::Failure] a Result from the boolean expression
def Check(type = nil, data: nil)
def Check(*types, data: nil)
res = yield

final_data = data || res

res ? Success(type, data: final_data) : Failure(type, data: final_data)
res ? Success(*types, data: final_data) : Failure(*types, data: final_data)
end

# If the given block raises an exception, it wraps it in a Failure.
# Otherwise, maps the block value in a Success object.
# You can specify which exceptions to watch for.
# It's possible to provide a type for the result too.
# It's possible to provide a types for the result too.
#
# @example
# class IHateEvenNumbers < FService::Base
Expand All @@ -214,20 +214,20 @@ def Check(type = nil, data: nil)
# end
#
# IHateEvenNumbers.call
# # => #<Success @value=9, @type=:rand_int>
# # => #<Success @value=9, @types=[:rand_int]>
#
# IHateEvenNumbers.call
# # => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @type=:rand_int>
# # => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:rand_int]>
#
# @param type the Result type
# @param types the Result types
# @param catch the exception list to catch
# @return [Result::Success, Result::Failure] a result from the boolean expression
def Try(type = nil, catch: StandardError)
def Try(*types, catch: StandardError)
res = yield

Success(type, data: res)
Success(*types, data: res)
rescue *catch => e
Failure(type, data: e)
Failure(*types, data: e)
end

# Returns a failed operation.
Expand Down
15 changes: 13 additions & 2 deletions lib/f_service/result/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Base
# You usually shouldn't call this directly. See {FService::Base#Failure} and {FService::Base#Success}.
def initialize
@handled = false
@matching_types = []
end

# This hook runs if the result is successful.
Expand Down Expand Up @@ -59,6 +60,7 @@ def initialize
# @api public
def on_success(*target_types, unhandled: false)
if successful? && unhandled? && expected_type?(target_types, unhandled: unhandled)
match_types(target_types)
yield(*to_ary)
@handled = true
freeze
Expand Down Expand Up @@ -108,6 +110,7 @@ def on_success(*target_types, unhandled: false)
# @api public
def on_failure(*target_types, unhandled: false)
if failed? && unhandled? && expected_type?(target_types, unhandled: unhandled)
match_types(target_types)
yield(*to_ary)
@handled = true
freeze
Expand All @@ -122,7 +125,7 @@ def on_failure(*target_types, unhandled: false)
def to_ary
data = successful? ? value : error

[data, type]
[data, @matching_types.first]
bvicenzo marked this conversation as resolved.
Show resolved Hide resolved
end

private
Expand All @@ -136,7 +139,15 @@ def unhandled?
end

def expected_type?(target_types, unhandled:)
target_types.empty? || unhandled || target_types.include?(type)
target_types.empty? || unhandled || target_types.any? { |target_type| types.include?(target_type) }
end

def match_types(target_types)
@matching_types = if target_types.empty?
[]
else
target_types & types
end
end
end
end
Expand Down
10 changes: 5 additions & 5 deletions lib/f_service/result/failure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ module Result
#
# @!attribute [r] error
# @return [Object] the provided error for the result
# @!attribute [r] type
# @return [Object] the provided type for the result. Defaults to nil.
# @!attribute [r] types
# @return [Object] the provided types for the result. Defaults to nil.
# @api public
class Failure < Result::Base
attr_reader :error, :type
attr_reader :error, :types

# Creates a failed operation.
# You usually shouldn't call this directly. See {FService::Base#Failure}.
#
# @param error [Object] failure value.
def initialize(error, type = nil)
def initialize(error, types = [])
super()
@error = error
@type = type
@types = types
end

# Returns false.
Expand Down
12 changes: 6 additions & 6 deletions lib/f_service/result/success.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ module Result
#
# @!attribute [r] value
# @return [Object] the provided value for the result
# @!attribute [r] type
# @return [Object] the provided type for the result. Defaults to nil.
# @!attribute [r] types
# @return [Object] the provided types for the result. Defaults to nil.
# @api public
class Success < Result::Base
attr_reader :value, :type
attr_reader :value, :types
bvicenzo marked this conversation as resolved.
Show resolved Hide resolved

# Creates a successful operation.
# You usually shouldn't call this directly. See {FService::Base#Success}.
#
# @param value [Object] success value.
def initialize(value, type = nil)
def initialize(value, types = [])
super()
@value = value
@type = type
@types = types
end

# Returns true.
Expand Down Expand Up @@ -79,7 +79,7 @@ def error
# end
#
# @yieldparam value pass {#value} to a block
# @yieldparam type pass {#type} to a block
# @yieldparam types pass {#types} to a block
def and_then
yield(*to_ary)
end
Expand Down
Loading