Open joshmfrankel opened 1 year ago
module ResultHandler
private
PROJECT_ROOT = '/Bend-API/'.freeze
DEFAULT_SUCCESS_MESSAGE = 'Success'.freeze
DEFAULT_FAILURE_MESSAGE = 'There was an error'.freeze
def self.included(included_class)
included_class.extend(ClassMethods)
end
module ClassMethods
def handle_result(*method_names)
handler = Module.new do
method_names.each do |method_name|
define_method(method_name) do |*args, **kwargs|
handle_result { super(*args, **kwargs) }
end
end
end
prepend(handler)
end
end
def handle_result
result_object = yield
return result_object if valid_result_object?(result_object:)
Result.success(payload: result_object, message: DEFAULT_SUCCESS_MESSAGE)
rescue ResultFailureError => exception
exception.result
rescue StandardError => exception
relevant_backtrace = exception.backtrace.select { |trace| trace.include?(PROJECT_ROOT) }
message = Rails.env.development? ? exception.message : DEFAULT_FAILURE_MESSAGE
Result.failure(message:, payload: { exception:, relevant_backtrace: })
end
def valid_result_object?(result_object:)
result_object.respond_to?(:success) &&
result_object.respond_to?(:failure) &&
result_object.respond_to?(:failure!)
end
def Success(payload: {}, message: DEFAULT_SUCCESS_MESSAGE)
Result.success(payload:, message:)
end
def Failure(payload: {}, message: DEFAULT_FAILURE_MESSAGE)
Result.failure(payload:, message:)
end
def Failure!(payload: {}, message: DEFAULT_FAILURE_MESSAGE)
Result.failure!(payload:, message:)
end
end
# result.rb
# Provides common interface for Railway-oriented programming
class Result
def self.success(payload: {}, message: '')
result = new(status: :success, payload:, message:)
result.success
end
def self.failure(payload: {}, message: '')
result = new(payload:, message:)
result.failure
end
def self.failure!(payload: {}, message: '')
result = new(payload:, message:)
result.failure!
end
attr_accessor :status, :payload, :message
def initialize(status: :success, payload: {}, message: '')
@status = status
@payload = payload
@message = message
end
def success?
status == :success
end
def failure?
!success?
end
def failure(message: self.message)
tap do |result|
result.status = :failure
result.message = message if message.present?
end
end
def failure!(message: self.message)
failure(message:)
raise ResultFailureError.new(self)
end
def success(message: '')
tap do |result|
result.status = :success
result.message = message || result.message
end
end
end
class ResultFailureError < StandardError
attr_reader :result
def initialize(result)
@result = result
super(result.message)
end
end
Sections for each
Model
Responsibilities An interface between the database and ORM
How to use
How not to use
Controller
Anti-patterns
View
Anti-patterns
Presenter
Anti-patterns
Query
.all
,.find_by
Anti-patterns
Serializer
Service
Job
Transformers
Translate one data format to another
Strategies
Provide concrete swappable business logic for different use-cases
Adapter
Take incoming interface and translate to another
View Component
Reusable View layer functionality and design
Policy
Enforces authorization and answers the questions:
Api
Schemas
Value
Form