okuramasafumi/neco

View on GitHub
lib/neco/command.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

require 'neco/container'
require 'neco/result'

module Neco
  # Command module is a basic module of Neco.
  # It provides many DSLs for validating input, executing business logic,
  # handling errors and much more.
  module Command
    def self.included(base)
      base.class_eval do
        extend ClassMethods
        include InstanceMethods
      end
    end

    # DSLs
    module ClassMethods
      def main(&blk)
        @main = blk
      end

      def validates(&blk)
        @validation = blk
      end

      def rollback(&blk)
        @rollback = blk
      end

      def call(*args, **params)
        instance = new(*args, **params)
        instance.call
      end
    end

    # When command object is instantiated, either by a class-level call,
    # a container or a user, these methods will be called.
    module InstanceMethods
      def initialize(*args, container: FakeContainer.new(command: self), **params)
        @args = args
        @container = container
        @params = params
      end

      def set(key, value)
        @container.set(key, value)
      end

      def call(*args, **params)
        @args += args
        @params.merge!(params)

        return false unless validate

        main = self.class.instance_variable_get(:@main)
        begin
          instance_exec(*@args, **@params, &main)
          Success.new
        rescue StandardError => e
          Failure.new(exception: e)
        end
      end

      def validate
        validation = self.class.instance_variable_get(:@validation)
        validation ? validation.call(@args, @params) : true
      end

      def revert
        rollback = self.class.instance_variable_get(:@rollback)
        instance_exec(*@args, **@params, &rollback)
      end

      def to_s
        main = self.class.instance_variable_get(:@main)
        main.inspect
      end

      def inspect
        to_s
      end
    end

    # @private
    class FakeContainer
      def initialize(command:)
        @command = command
        @environment = {}
      end

      def set(key, value)
        @environment[key] = value
      end
    end
    private_constant :FakeContainer
  end
end