debian-mirror-gitlab/lib/gitlab/json_cache.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

121 lines
3.3 KiB
Ruby
Raw Normal View History

2019-02-15 15:39:39 +05:30
# frozen_string_literal: true
module Gitlab
class JsonCache
2022-05-07 20:08:51 +05:30
attr_reader :backend, :namespace
STRATEGY_KEY_COMPONENTS = {
revision: Gitlab.revision,
version: [Gitlab::VERSION, Rails.version]
}.freeze
2019-02-15 15:39:39 +05:30
def initialize(options = {})
@backend = options.fetch(:backend, Rails.cache)
@namespace = options.fetch(:namespace, nil)
2022-05-07 20:08:51 +05:30
@cache_key_strategy = options.fetch(:cache_key_strategy, :revision)
2019-02-15 15:39:39 +05:30
end
def active?
if backend.respond_to?(:active?)
backend.active?
else
true
end
end
def cache_key(key)
2022-05-07 20:08:51 +05:30
expanded_cache_key = [namespace, key, *strategy_key_component].compact
expanded_cache_key.join(':').freeze
end
2019-02-15 15:39:39 +05:30
2022-05-07 20:08:51 +05:30
def strategy_key_component
STRATEGY_KEY_COMPONENTS.fetch(@cache_key_strategy)
2019-02-15 15:39:39 +05:30
end
def expire(key)
backend.delete(cache_key(key))
end
def read(key, klass = nil)
value = backend.read(cache_key(key))
2019-09-30 21:07:59 +05:30
value = parse_value(value, klass) unless value.nil?
2019-02-15 15:39:39 +05:30
value
end
def write(key, value, options = nil)
2022-05-07 20:08:51 +05:30
# As we use json as the serialization format, return everything from
# ActiveModel objects included encrypted values.
backend.write(cache_key(key), value.to_json(unsafe_serialization_hash: true), options)
2019-02-15 15:39:39 +05:30
end
def fetch(key, options = {}, &block)
klass = options.delete(:as)
value = read(key, klass)
return value unless value.nil?
value = yield
write(key, value, options)
value
end
private
def parse_value(raw, klass)
2021-10-27 15:23:28 +05:30
value = Gitlab::Json.parse(raw.to_s)
2019-02-15 15:39:39 +05:30
case value
when Hash then parse_entry(value, klass)
when Array then parse_entries(value, klass)
else
value
end
2021-10-27 15:23:28 +05:30
rescue JSON::ParserError
2019-02-15 15:39:39 +05:30
nil
end
def parse_entry(raw, klass)
2019-07-07 11:18:12 +05:30
return unless valid_entry?(raw, klass)
return klass.new(raw) unless klass.ancestors.include?(ActiveRecord::Base)
# When the cached value is a persisted instance of ActiveRecord::Base in
# some cases a relation can return an empty collection becauses scope.none!
# is being applied on ActiveRecord::Associations::CollectionAssociation#scope
# when the new_record? method incorrectly returns false.
#
2019-12-04 20:38:33 +05:30
# See https://gitlab.com/gitlab-org/gitlab/issues/9903#note_145329964
2020-01-01 13:55:28 +05:30
klass.allocate.init_with(encode_for(klass, raw))
2019-07-07 11:18:12 +05:30
end
2020-01-01 13:55:28 +05:30
def encode_for(klass, raw)
2019-07-07 11:18:12 +05:30
# We have models that leave out some fields from the JSON export for
# security reasons, e.g. models that include the CacheMarkdownField.
# The ActiveRecord::AttributeSet we build from raw does know about
# these columns so we need manually set them.
missing_attributes = (klass.columns.map(&:name) - raw.keys)
missing_attributes.each { |column| raw[column] = nil }
2020-01-01 13:55:28 +05:30
coder = {}
klass.new(raw).encode_with(coder)
coder["new_record"] = new_record?(raw, klass)
coder
2019-07-07 11:18:12 +05:30
end
def new_record?(raw, klass)
raw.fetch(klass.primary_key, nil).blank?
2019-02-15 15:39:39 +05:30
end
def valid_entry?(raw, klass)
return false unless klass && raw.is_a?(Hash)
(raw.keys - klass.attribute_names).empty?
end
def parse_entries(values, klass)
values.map { |value| parse_entry(value, klass) }.compact
end
end
end