Skip to content
New issue

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

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

Already on GitHub? # to your account

Switch to keyword arguments #8

Merged
merged 2 commits into from
May 25, 2022
Merged
Changes from all commits
Commits
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
43 changes: 24 additions & 19 deletions lib/operatic.rb
Original file line number Diff line number Diff line change
@@ -11,15 +11,12 @@ def self.included(base)
module ClassMethods
# The main way of calling an operation.
#
# The class is instantiated with supplied +attrs+ and calls {Operatic#call}
# returning a frozen {Result} instance.
# The class is instantiated with the supplied +attrs+ keyword arguments and
# calls {Operatic#call} returning a frozen {Result} instance.
#
# @param attrs [Hash<Symbol, anything>] an optional hash of key/values to
# to the result. The class must have corresponding +attr_reader+s
#
# @return a [Result]
def call(attrs = nil)
new(attrs)
# @return [Result]
def call(**attrs)
new(**attrs)
.tap(&:call)
.result
.freeze
@@ -30,8 +27,8 @@ def call(attrs = nil)
# test setups, etc.
#
# @return [Result]
def call!(attrs = nil)
call(attrs).tap { |result|
def call!(**attrs)
call(**attrs).tap { |result|
raise FailureError if result.failure?
}
end
@@ -55,8 +52,11 @@ def call!(attrs = nil)
# result = SayHello.call(name: 'Dave')
# result.success? # => true
# result.message # => "Hello Dave"
def result(*args)
@result_class = Result.generate(*args)
#
# @param attrs [Array<Symbol>] a list of convenience data accessors to
# define on the {Result}.
def result(*attrs)
@result_class = Result.generate(*attrs)
end

def result_class
@@ -69,48 +69,53 @@ def result_class
# @return [Result]
attr_reader :result

def initialize(attrs = nil)
def initialize(**attrs)
@result = self.class.result_class.new

attrs.each do |key, value|
instance_variable_set("@#{key}", value)
end if attrs
end
end

# Override this method with your implementation. Use {#success!} or
# {#failure!} methods to communicate the {#result}'s status and to attach
# data it. Define convenience result accessors with {ClassMethods#result}.
# data to it. Define convenience result accessors with {ClassMethods#result}.
#
# @example
# class SayHello
# include Operatic
#
# attr_reader :name
#
# result :message
#
# def call
# return failure! unless name
# success!(message: "Hello #{name}")
# end
# end
#
# result = SayHello.call(name: 'Dave')
# result.failure? # => false
# result.success? # => true
# result.message # => "Hello Dave"
# result.to_h # => {:message=>"Hello Dave"}
#
# result = SayHello.call
# result.failure? # => true
# result.success? # => false
# result.message # => nil
# result.to_h # => {}
def call
end

# Convenience shortcut to the operation's {Result#failure!}.
def failure!(data = nil)
result.failure!(data)
def failure!(**data)
result.failure!(**data)
end

# Convenience shortcut to the operation's {Result#success!}.
def success!(data = nil)
result.success!(data)
def success!(**data)
result.success!(**data)
end
end
38 changes: 16 additions & 22 deletions lib/operatic/result.rb
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ class Result
# wouldn't normally be called directly, see {ClassMethods#result} for
# example usage.
#
# @param attrs [Array<Symbol>] a list of accessors to the result's data.
# @param attrs [Array<Symbol>] a list of convenience data accessors.
def self.generate(*attrs)
Class.new(self) do
attrs.each do |name|
@@ -24,16 +24,13 @@ def initialize
@success = true
end

# Mark the result as a failure, optionally attach data, and freeze the
# object so it cannot be modified further.
# Mark the result as a failure, optionally attach +data+ via kwargs, and
# freeze the object so it cannot be modified further.
#
# *Note*: After calling this method calling {#success!} or {#failure!}
# again will raise a +FrozenError+.
#
# @param data [Hash<Symbol, anything>] an optional hash of data to attach
# to the result.
def failure!(data = nil)
set_data(data) if data
# *Note*: Calling {#success!} or {#failure!} more than once will raise a
# +FrozenError+.
def failure!(**data)
set_data(**data)
@success = false
freeze
end
@@ -47,20 +44,17 @@ def freeze
super
end

# Mark the result as a success, optionally attach data, and freeze the
# object so it cannot be modified further.
# Mark the result as a success, optionally attach +data+ via kwargs, and
# freeze the object so it cannot be modified further.
#
# Calling this is not strictly necessary as a result defaults to being a
# Calling this is not strictly necessary as a +Result+ defaults to being a
# success, but it's a convenient means of attaching data and of indicating
# intent in the consuming code.
#
# *Note*: After calling this method calling {#success!} or {#failure!}
# again will raise a +FrozenError+.
#
# @param data [Hash<Symbol, anything>] an optional hash of data to attach
# to the result.
def success!(data = nil)
set_data(data) if data
# *Note*: Calling {#success!} or {#failure!} more than once will raise a
# +FrozenError+.
def success!(**data)
set_data(**data)
@success = true
freeze
end
@@ -72,13 +66,13 @@ def success?
# Returns the full (frozen) hash of data attached to the result via
# {#success!}, {#failure!}, or convenience accessors added with {.generate}.
#
# @return [Hash]
# @return [Hash<Symbol, anything>]
def to_h
@data
end

private
def set_data(data)
def set_data(**data)
data.each do |key, value|
@data[key] = value
end
11 changes: 11 additions & 0 deletions spec/lib/operatic_spec.rb
Original file line number Diff line number Diff line change
@@ -16,6 +16,17 @@
expect(result.to_h).to be_frozen
end
end

context 'when called with something other than kwargs' do
it 'raises ArgumentError', :aggregate_failures do
expect { klass.call('Dave') }.to raise_error(ArgumentError)
expect { klass.call(['Dave']) }.to raise_error(ArgumentError)

if RUBY_VERSION > '3.0'
expect { klass.call({ name: 'Dave' }) }.to raise_error(ArgumentError)
end
end
end
end

describe '.call!' do