Module: API::Helpers::Caching

Includes:
Gitlab::Cache::Helpers
Included in:
API::Helpers
Defined in:
lib/api/helpers/caching.rb

Constant Summary collapse

DEFAULT_CACHE_OPTIONS =

Returns:

  • (Hash)
{
  race_condition_ttl: 5.seconds,
  version: 1
}.freeze
PAGINATION_HEADERS =

Returns:

  • (Array)
%w[X-Per-Page X-Page X-Next-Page X-Prev-Page Link X-Total X-Total-Pages].freeze

Constants included from Gitlab::Cache::Helpers

Gitlab::Cache::Helpers::DEFAULT_EXPIRY

Instance Method Summary collapse

Methods included from Gitlab::Cache::Helpers

#cache, #render_cached

Instance Method Details

#cache_action(key, **custom_cache_opts) ⇒ Gitlab::Json::PrecompiledJson

Action caching implementation

This allows you to wrap an entire API endpoint call in a cache, useful for short TTL caches to effectively rate-limit an endpoint. The block will be converted to JSON and cached, and returns a `Gitlab::Json::PrecompiledJson` object which will be exported without secondary conversion.

Parameters:

  • key (Object)

    any object that can be converted into a cache key

  • expires_in (ActiveSupport::Duration, Integer)

    an expiry time for the cache entry

Returns:


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/api/helpers/caching.rb', line 79

def cache_action(key, **custom_cache_opts)
  cache_opts = apply_default_cache_options(custom_cache_opts)

  json, cached_headers = cache.fetch(key, **cache_opts) do
    response = yield

    cached_body = response.is_a?(Gitlab::Json::PrecompiledJson) ? response.to_s : Gitlab::Json.dump(response.as_json)
    cached_headers = header.slice(*PAGINATION_HEADERS)

    [cached_body, cached_headers]
  end

  cached_headers.each do |key, value|
    next if header.key?(key)

    header key, value
  end

  body Gitlab::Json::PrecompiledJson.new(json)
end

#cache_action_if(conditional, *opts, **kwargs) ⇒ Object

Conditionally cache an action

Perform a `cache_action` only if the conditional passes


103
104
105
106
107
108
109
110
111
# File 'lib/api/helpers/caching.rb', line 103

def cache_action_if(conditional, *opts, **kwargs)
  if conditional
    cache_action(*opts, **kwargs) do
      yield
    end
  else
    yield
  end
end

#cache_action_unless(conditional, *opts, **kwargs) ⇒ Object

Conditionally cache an action

Perform a `cache_action` unless the conditional passes


116
117
118
119
120
# File 'lib/api/helpers/caching.rb', line 116

def cache_action_unless(conditional, *opts, **kwargs)
  cache_action_if(!conditional, *opts, **kwargs) do
    yield
  end
end

#present_cached(obj_or_collection, with:, cache_context: -> (_) { current_user&.cache_key }, expires_in: Gitlab::Cache::Helpers::DEFAULT_EXPIRY, **presenter_args) ⇒ Gitlab::Json::PrecompiledJson

This is functionally equivalent to the standard `#present` used in Grape endpoints, but the JSON for the object, or for each object of a collection, will be cached.

With a collection all the keys will be fetched in a single call and the Entity rendered for those missing from the cache, which are then written back into it.

Both the single object, and all objects inside a collection, must respond to `#cache_key`.

To override the Grape formatter we return a custom wrapper in `Gitlab::Json::PrecompiledJson` which tells the `Gitlab::Json::GrapeFormatter` to export the string without conversion.

A cache context can be supplied to add more context to the cache key. This defaults to including the `current_user` in every key for safety, unless overridden.

Parameters:

  • obj_or_collection (Object, Enumerable<Object>)

    the object or objects to render

  • with (Grape::Entity)

    the entity to use for rendering

  • cache_context (Proc) (defaults to: -> (_) { current_user&.cache_key })

    a proc to call for each object to provide more context to the cache key

  • expires_in (ActiveSupport::Duration, Integer) (defaults to: Gitlab::Cache::Helpers::DEFAULT_EXPIRY)

    an expiry time for the cache entry

  • presenter_args (Hash)

    keyword arguments to be passed to the entity

Returns:


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/api/helpers/caching.rb', line 45

def present_cached(obj_or_collection, with:, cache_context: -> (_) { current_user&.cache_key }, expires_in: Gitlab::Cache::Helpers::DEFAULT_EXPIRY, **presenter_args)
  json =
    if obj_or_collection.is_a?(Enumerable)
      cached_collection(
        obj_or_collection,
        presenter: with,
        presenter_args: presenter_args,
        context: cache_context,
        expires_in: expires_in
      )
    else
      cached_object(
        obj_or_collection,
        presenter: with,
        presenter_args: presenter_args,
        context: cache_context,
        expires_in: expires_in
      )
    end

  body Gitlab::Json::PrecompiledJson.new(json)
end