lib/ballast/service.rb
#
# This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
# Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
#
module Ballast
# A class which implements a common abstraction for services.
#
# @attribute [r] owner
# @return [Object|NilClass] The owner of this service.
class Service
# A response to a service invocation.
#
# @attribute [r] success
# @return [Boolean] Whether the invocation was successful or not.
# @attribute [r] data
# @return [Object] The data returned by the operation.
# @attribute [r] errors
# @return [Array] The errors returned by the operation.
class Response
attr_reader :success, :data, :errors
# Creates a new service response.
#
# @param success [Boolean] Whether the invocation was successful or not.
# @param data [Object|NilClass] The data returned by the operation.
# @param errors [Array|NilClass] The errors returned by the operation.
# @param error [Object|NilClass] Alias for errors. *Ignored if `errors` is present.*
def initialize(success = true, data: nil, errors: nil, error: nil)
errors ||= error.ensure_array
@success = success.to_boolean
@data = data
@errors = errors.ensure_array(no_duplicates: true, compact: true)
end
# Returns whether the invocation was successful or not.
#
# @return [Boolean] `true` if the service invocation was successful, `false` otherwise.
def success?
# TODO@PI: Ignore rubocop on this
@success
end
alias_method :successful?, :success?
alias_method :succeeded?, :success?
# Returns whether the invocation failed or not.
#
# @return [Boolean] `true` if the service invocation failed, `false` otherwise.
def fail?
!@success
end
alias_method :failed?, :fail?
# Returns the first error returned by the operation.
#
# @return [Object] The first error returned by the service.
def error
@errors.first
end
# Converts this response to a AJAX response.
#
# @param transport [Object|NilClass] The transport to use for sending. Must respond to `render`, `params`, `request.format` and `performed?`.
# @return [AjaxResponse] The AJAX response, which will include only the first error.
def as_ajax_response(transport = nil)
status, error_message =
if successful?
[:ok, nil]
elsif error.is_a?(Hash)
[error[:status], error[:error]]
else
[:unknown, error]
end
AjaxResponse.new(status: status, data: data, error: error_message, transport: transport)
end
end
attr_reader :owner
# Invokes one of the operations exposed by the service.
#
# @param operation [String] The operation to invoke.
# @param owner [Object|NilClass] The owner of the service.
# @param raise_errors [Boolean] Whether to raise errors instead of returning a failure.
# @param params [Hash] The parameters to pass to the service.
# @param kwargs [Hash] Other modifiers to pass to the service.
# @param block [Proc] A lambda to pass to the service.
# @return [Response] The response of the service.
def self.call(operation = :perform, owner: nil, raise_errors: false, params: {}, **kwargs, &block)
fail!(status: 501, error: "Unsupported operation #{self}.#{operation}.") unless respond_to?(operation)
Response.new(true, data: send(operation, owner: owner, params: params, **kwargs, &block))
rescue Errors::Failure => failure
handle_failure(failure, raise_errors)
end
# Marks the failure of the operation.
#
# @param details [Object] The error(s) occurred.
def self.fail!(details)
raise(Errors::Failure, details)
end
# Marks the failure of the validation of the operation.
#
# @param details [Object] The error(s) occurred.
def self.fail_validation!(details)
raise(Errors::ValidationFailure, details)
end
# Creates a service object.
#
# @param owner [Object|NilClass] The owner of the service.
def initialize(owner = nil)
@owner = owner
end
# Invokes one of the operations exposed by the service.
#
# @param operation [String] The operation to invoke.
# @param owner [Object|NilClass] The owner of the service.
# @param raise_errors [Boolean] Whether to raise errors instead of returning a failure.
# @param params [Hash] The parameters to pass to the service.
# @param kwargs [Hash] Other modifiers to pass to the service.
# @param block [Proc] A lambda to pass to the service.
# @return [Response] The response of the service.
def call(operation = :perform, owner: nil, raise_errors: false, params: {}, **kwargs, &block)
# PI: Ignore Roodi on this method
@owner = owner if owner
fail!(status: 501, error: "Unsupported operation #{self.class}##{operation}.") unless respond_to?(operation)
Response.new(true, data: send(operation, params: params, **kwargs, &block))
rescue Errors::Failure => failure
self.class.send(:handle_failure, failure, raise_errors)
end
# Marks the failure of the operation.
#
# @param details [Object] The error(s) occurred.
def fail!(details)
raise(Errors::Failure, details)
end
# Marks the failure of the validation of the operation.
#
# @param details [Object] The error(s) occurred.
def fail_validation!(details)
raise(Errors::ValidationFailure, details)
end
# Handles a failure.
#
# @param failure [Failure] The failure to handle.
# @param raise_errors [Boolean] If `true` it will simply raise the error, otherwise it will return a failure as as Service::Response.
# @return [Response] A failure response.
def self.handle_failure(failure, raise_errors)
raise_errors ? raise(failure) : Response.new(false, error: failure.details)
end
end
end