pragmarb/pragma-rails

View on GitHub
lib/pragma/rails/resource_controller.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Pragma
  module Rails
    # Exposes CRUD operations on a resource through a Rails controller.
    module ResourceController
      def self.included(klass)
        klass.include Controller
        klass.extend ClassMethods
        klass.include InstanceMethods
      end

      module ClassMethods # :nodoc:
        # Returns the name of the resource this controller refers to.
        #
        # @return [String]
        #
        # @example
        #   API::V1::PostsController.resource_name => 'Post'
        def resource_name
          name.demodulize.chomp('Controller').singularize
        end

        # Returns the expected class of the provided operation on this resource.
        #
        # Note that this does not mean the operation is actually supported. Use {#operation?} for
        # that.
        #
        # @param operation_name [Symbol] name of the operation
        #
        # @return [String]
        #
        # @see #operation?
        #
        # @example
        #   API::V1::PostsController.operation_klass(:create) => 'API::V1::Post::Operation::Create'
        def operation_klass(operation_name)
          [name.deconstantize].tap do |klass|
            klass << "#{resource_name}::Operation::#{operation_name.to_s.camelize}"
          end.join('::')
        end

        # Returns whether the provided operation is supported on this resource.
        #
        # @param operation_name [Symbol] name of the operation
        #
        # @return [Boolean]
        def operation?(operation_name)
          class_exists? operation_klass(operation_name)
        end

        private

        def class_exists?(klass)
          begin
            Object.const_get(klass)
          rescue NameError => e
            raise e unless e.message.end_with?("uninitialized constant #{klass}")
          end

          Object.const_defined?(klass)
        end
      end

      module InstanceMethods # :nodoc:
        # If an action that does not exist is called on this controller, tries to find and run
        # an operation on the current resource with the same name.
        #
        # For instance, +API::V1::PostsController#create+ would try to find and run
        # +API::V1::Post::Operation::Create+.
        #
        # @param action_name [String] name of the missing action
        def action_missing(action_name)
          send(action_name)
        end

        # Supports running missing actions.
        #
        # @see #action_missing
        def method_missing(method_name, *arguments, &block)
          return super unless self.class.operation?(method_name)
          run self.class.operation_klass(method_name)
        end

        # Supports running missing actions.
        #
        # @see #action_missing
        def respond_to_missing?(method_name, include_private = false)
          self.class.operation?(method_name) || super
        end
      end
    end
  end
end