diff --git a/.rubocop.yml b/.rubocop.yml index bded7aed3..9ea8ca106 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -30,6 +30,7 @@ Style/GlobalVars: Exclude: - 'config/initializers/redis.rb' - 'lib/redis/alfred.rb' + - 'lib/global_config.rb' Metrics/BlockLength: Exclude: - spec/**/* diff --git a/lib/global_config.rb b/lib/global_config.rb new file mode 100644 index 000000000..8c1da224d --- /dev/null +++ b/lib/global_config.rb @@ -0,0 +1,44 @@ +class GlobalConfig + VERSION = 'V1'.freeze + KEY_PREFIX = 'GLOBAL_CONFIG'.freeze + DEFAULT_EXPIRY = 1.day + + class << self + def get(*args) + config_keys = *args + config = {} + + config_keys.each do |config_key| + config[config_key] = load_from_cache(config_key) + end + + config.with_indifferent_access + end + + def clear_cache + cached_keys = $alfred.keys("#{VERSION}:#{KEY_PREFIX}:*") + (cached_keys || []).each do |cached_key| + $alfred.expire(cached_key, 0) + end + end + + private + + def load_from_cache(config_key) + cache_key = "#{VERSION}:#{KEY_PREFIX}:#{config_key}" + cached_value = $alfred.get(cache_key) + + if cached_value.blank? + value_from_db = db_fallback(config_key) + cached_value = { value: value_from_db }.to_json + $alfred.set(cache_key, cached_value, { expiry: DEFAULT_EXPIRY }) + end + + JSON.parse(cached_value)['value'] + end + + def db_fallback(config_key) + InstallationConfig.find_by(name: config_key)&.value + end + end +end diff --git a/spec/lib/global_config_spec.rb b/spec/lib/global_config_spec.rb new file mode 100644 index 000000000..ac9a94368 --- /dev/null +++ b/spec/lib/global_config_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +describe GlobalConfig do + subject(:trigger) { described_class } + + describe 'execute' do + context 'when called with default options' do + before do + described_class.clear_cache + end + + it 'hit DB for the first call' do + expect(InstallationConfig).to receive(:find_by) + described_class.get('test') + end + + it 'get from cache for subsequent calls' do + # this loads from DB + described_class.get('test') + + # subsequent calls should not hit DB + expect(InstallationConfig).not_to receive(:find_by) + described_class.get('test') + end + + it 'clears cache and fetch from DB next time, when clear_cache is called' do + # this loads from DB and is cached + described_class.get('test') + + # clears the cache + described_class.clear_cache + + # should be loaded from DB + expect(InstallationConfig).to receive(:find_by).with({ name: 'test' }).and_return(nil) + described_class.get('test') + end + end + end +end