phallguy/scorpion

View on GitHub
lib/scorpion/rails/nest.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
98%
require "scorpion/nest"
require "active_support/core_ext/class/attribute"

module Scorpion
  module Rails
    # Handles building a scorpion to handle a single request and populating
    # all the dependencies automatically.
    #
    # The host class must respond to #scorpion, #assign_scorpion(scorpion) and
    # #free_scorpion.
    module Nest
      include Scorpion::Object

      # ============================================================================
      # @!group Attributes
      #

      # @!attribute
      # @return [Scorpion::Nest] the nest used to conceive scorpions.
        def nest
          self.class.nest
        end
        private :nest

        def scorpion( scope = nil )
          # Make sure a scorpion is always available. Will be freed on the next
          # call to #with_scorpion
          ensure_scorpion( super ) unless scope
          super
        end

      #
      # @!endgroup Attributes

      def self.included( base ) # rubocop:disable Metrics/MethodLength
        # @!attribute [rw]
        # @return [Scorpion::Nest] the singleton nest used by controllers.
        base.class_attribute :nest_instance
        base.class_exec do

          # @!attribute
          # @return [Scorpion::Nest] the nest used to conceive scorpions to
          #   hunt for objects on each request.
          def self.nest
            nest_instance || ( self.nest = Scorpion.instance.build_nest )
          end

          def self.nest=( value )
            nest_instance&.destroy
            self.nest_instance = value
          end

          # Prepare the nest for conceiving scorpions.
          # @see DependencyMap#chart
          def self.scorpion_nest( &block )
            nest.prepare &block
          end

          # Define dependency resolution that isn't resolved until an instance
          # of a scorpion is conceived to handle an idividual request.
          # @param (see DependencyMap#hunt_for )
          def self.hunt_for( *args, &block )
            instance_hunts << [:hunt_for, args, block]
          end

          # Define dependency resolution that isn't resolved until an instance
          # of a scorpion is conceived to handle an idividual request.
          # @param (see DependencyMap#capture )
          def self.capture( *args, &block )
            instance_hunts << [:capture, args, block]
          end

          # Hunting dependencies that cannot be resolved until an instance
          # of the nest class has been created.
          def self.instance_hunts
            @instance_hunts ||= begin
              if superclass.respond_to?( :instance_hunts )
                superclass.instance_hunts.dup
              else
                []
              end
            end
          end
        end


        super
      end

      private

      # Fetch a scorpion and feed the controller it's dependencies, then yield
      # to perform the action within the context of that scorpion.
      def with_scorpion( &block )
        ensure_scorpion( scorpion )
        scorpion.inject self

        yield
      ensure
        free_scorpion
      end

      def conceive_scorpion
        @conceived_scorpion = true
        nest.conceive
      end

      def conceived_scorpion?
        !!@conceived_scorpion
      end

      def append_instance_hunts( scorpion )
        scorpion.prepare do |hunter|
          self.class.instance_hunts.each do |method, args, block|
            hunter.send method, *args do |*method_args|
              instance_exec *method_args, &block
            end
          end
        end
      end

      def ensure_scorpion( existing )
        scorpion = existing
        scorpion = assign_scorpion( conceive_scorpion ) unless existing

        prepare_scorpion( scorpion ) if respond_to?( :prepare_scorpion, true )
        append_instance_hunts( scorpion )
        scorpion
      end

    end
  end
end