lib/grape/dsl/settings.rb
# 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