joshmfrankel / joshmfrankel.github.io

Blog and Personal site
http://joshfrankel.me
MIT License
2 stars 1 forks source link

Patterns in Ruby #71

Open joshmfrankel opened 1 year ago

joshmfrankel commented 1 year ago

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

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

joshmfrankel commented 4 days 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