sanger/sequencescape

View on GitHub
app/api/core/endpoint/basic_handler/actions.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
95%
# frozen_string_literal: true
# rubocop:todo Metrics/ModuleLength
module Core::Endpoint::BasicHandler::Actions
  class UnsupportedAction < StandardError
    def initialize(action, _request)
      super(action.to_s)
    end
  end

  def self.included(base)
    base.class_eval do
      include Core::Endpoint::BasicHandler::Actions::Bound
      include Core::Endpoint::BasicHandler::Actions::Factory
      include Core::Endpoint::BasicHandler::Actions::Guards
      include Core::Endpoint::BasicHandler::EndpointLookup
      include Core::Abilities::ActionBehaviour
    end
  end

  ACTIONS_WITH_SUCCESS_CODES = {
    create: 201,
    read: 200,
    update: 200,
    delete: 200,
    create_from_file: 201,
    update_from_file: 200
  }.freeze

  ACTIONS_WITH_SUCCESS_CODES.each do |action, status_code|
    line = __LINE__ + 1
    class_eval(
      "
      def #{action}(request, path, &block)
        current, *rest = path
        handler = handler_for(current)
        return handler.#{action}(request, rest, &block) unless self == handler

        check_request_io_class!(request)
        check_authorisation!(self, #{action.inspect}, request, request.target)
        request.response do |response|
          response.status(#{status_code})
          _#{action}(request, response) do |handler, object|
            response.handled_by ||= handler
            response.object     = object
          end
        end
      end

      def _#{action}(request, response)
        raise ::Core::Service::UnsupportedAction
      end
    ",
      __FILE__,
      line
    )
  end

  def check_request_io_class!(request)
    raise StandardError, 'Need an I/O class for this request' if request.io.nil?
  end

  def does_not_require_an_io_class
    singleton_class.class_eval('def check_request_io_class!(_) ; end', __FILE__, __LINE__)
  end

  def disable(*actions) # rubocop:todo Metrics/MethodLength
    actions.each do |action|
      line = __LINE__ + 1
      singleton_class.class_eval(
        "
        def _#{action}(request, response)
          raise ::Core::Service::UnsupportedAction
        end
      ",
        __FILE__,
        line
      )
      @actions.delete(action.to_sym)
    end
  end

  def deprecate(*actions) # rubocop:todo Metrics/MethodLength
    actions.each do |action|
      line = __LINE__ + 1
      singleton_class.class_eval(
        "
        def _#{action}(request, response)
          raise ::Core::Service::DeprecatedAction
        end
      ",
        __FILE__,
        line
      )
      @actions.delete(action.to_sym)
    end
  end

  def action(name, options = {}, &block)
    declare_action(name, options, &block)
    attach_action(options[:as] || name, name)
    action_guard(name, options[:if]) if options.key?(:if)
  end

  def declare_action(name, options, &block) # rubocop:todo Metrics/MethodLength
    action_implementation_method =
      case
      when block
        singleton_class.class_eval { define_method(:"_#{name}_internal", &block) }
        :"_#{name}_internal"
      when options[:to]
        options[:to]
      else
        raise StandardError, 'Block or :to option needed to declare action'
      end

    line = __LINE__ + 1
    singleton_class.class_eval(
      "
      def _#{name}(request, response)
        object = #{action_implementation_method}(request, response)
        yield(endpoint_for_object(object).instance_handler, object)
      end
    ",
      __FILE__,
      line
    )
  end
  private :declare_action

  def generate_json_actions(object, options)
    options[:stream].block('actions') do |result|
      actions(object, options).each { |name, url| result.attribute(name, url) }
    end
  end
end
# rubocop:enable Metrics/ModuleLength