ruby-grape/grape

View on GitHub
lib/grape/dsl/settings.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Grape
  module DSL
    # Keeps track of settings (implemented as key-value pairs, grouped by
    # types), in two contexts: top-level settings which apply globally no
    # matter where they're defined, and inheritable settings which apply only
    # in the current scope and scopes nested under it.
    module Settings
      extend ActiveSupport::Concern

      attr_writer :inheritable_setting, :top_level_setting

      # Fetch our top-level settings, which apply to all endpoints in the API.
      def top_level_setting
        @top_level_setting ||= build_top_level_setting
      end

      # Fetch our current inheritable settings, which are inherited by
      # nested scopes but not shared across siblings.
      def inheritable_setting
        @inheritable_setting ||= Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from top_level_setting }
      end

      # @param type [Symbol]
      # @param key [Symbol]
      def unset(type, key)
        setting = inheritable_setting.send(type)
        setting.delete key
      end

      # @param type [Symbol]
      # @param key [Symbol]
      # @param value [Object] will be stored if the value is currently empty
      # @return either the old value, if it wasn't nil, or the given value
      def get_or_set(type, key, value)
        setting = inheritable_setting.send(type)
        if value.nil?
          setting[key]
        else
          setting[key] = value
        end
      end

      # @param key [Symbol]
      # @param value [Object]
      # @return (see #get_or_set)
      def global_setting(key, value = nil)
        get_or_set :global, key, value
      end

      # @param key [Symbol]
      def unset_global_setting(key)
        unset :global, key
      end

      # (see #global_setting)
      def route_setting(key, value = nil)
        get_or_set :route, key, value
      end

      # (see #unset_global_setting)
      def unset_route_setting(key)
        unset :route, key
      end

      # (see #global_setting)
      def namespace_setting(key, value = nil)
        get_or_set :namespace, key, value
      end

      # (see #unset_global_setting)
      def unset_namespace_setting(key)
        unset :namespace, key
      end

      # (see #global_setting)
      def namespace_inheritable(key, value = nil)
        get_or_set :namespace_inheritable, key, value
      end

      # (see #unset_global_setting)
      def unset_namespace_inheritable(key)
        unset :namespace_inheritable, key
      end

      # @param key [Symbol]
      def namespace_inheritable_to_nil(key)
        inheritable_setting.namespace_inheritable[key] = nil
      end

      # (see #global_setting)
      def namespace_stackable(key, value = nil)
        get_or_set :namespace_stackable, key, value
      end

      def namespace_reverse_stackable(key, value = nil)
        get_or_set :namespace_reverse_stackable, key, value
      end

      def namespace_stackable_with_hash(key)
        settings = get_or_set :namespace_stackable, key, nil
        return if settings.blank?

        settings.each_with_object({}) { |value, result| result.deep_merge!(value) }
      end

      def namespace_reverse_stackable_with_hash(key)
        settings = get_or_set :namespace_reverse_stackable, key, nil
        return if settings.blank?

        settings.each_with_object({}) do |setting, result|
          result.merge!(setting) { |_k, s1, _s2| s1 }
        end
      end

      # (see #unset_global_setting)
      def unset_namespace_stackable(key)
        unset :namespace_stackable, key
      end

      # (see #global_setting)
      def api_class_setting(key, value = nil)
        get_or_set :api_class, key, value
      end

      # (see #unset_global_setting)
      def unset_api_class_setting(key)
        unset :api_class, key
      end

      # Fork our inheritable settings to a new instance, copied from our
      # parent's, but separate so we won't modify it. Every call to this
      # method should have an answering call to #namespace_end.
      def namespace_start
        @inheritable_setting = Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from inheritable_setting }
      end

      # Set the inheritable settings pointer back up by one level.
      def namespace_end
        route_end
        @inheritable_setting = inheritable_setting.parent
      end

      # Stop defining settings for the current route and clear them for the
      # next, within a namespace.
      def route_end
        inheritable_setting.route_end
      end

      # Execute the block within a context where our inheritable settings are forked
      # to a new copy (see #namespace_start).
      def within_namespace(&block)
        namespace_start

        result = yield if block

        namespace_end
        reset_validations!

        result
      end

      private

      # Builds the current class :inheritable_setting. If available, it inherits from
      # the superclass's :inheritable_setting.
      def build_top_level_setting
        Grape::Util::InheritableSetting.new.tap do |setting|
          # Doesn't try to inherit settings from +Grape::API::Instance+ which also responds to
          # +inheritable_setting+, however, it doesn't contain any user-defined settings.
          # Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+
          # in the chain for every endpoint.
          setting.inherit_from superclass.inheritable_setting if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance
        end
      end
    end
  end
end