# typed: false class Keystore < ApplicationRecord MAX_KEY_LENGTH = 50 self.primary_key = "key" validates :key, presence: true, length: {maximum: MAX_KEY_LENGTH} def self.get(key) find_by(key: key) end def self.value_for(key) where(key: key).limit(1).pluck(:value).first end def self.put(key, value) validate_input_key(key) Keystore.upsert({key: key, value: value}, returning: false) true end def self.increment_value_for(key, amount = 1) incremented_value_for(key, amount) end def self.incremented_value_for(key, amount = 1) validate_input_key(key) Keystore.transaction do Keystore.upsert({key: key, value: amount}, on_duplicate: Arel.sql("value = value + 1")) value_for(key) end end def self.find_or_create_key_for_update(key, init = nil) loop do found = lock(true).find_by(key: key) return found if found begin create! do |kv| kv.key = key kv.value = init kv.save! end rescue ActiveRecord::RecordNotUnique nil end end end def self.decrement_value_for(key, amount = -1) increment_value_for(key, amount) end def self.decremented_value_for(key, amount = -1) incremented_value_for(key, amount) end # deliberately no lock/transaction as TrafficHelper is on the hot path of every request def self.readthrough_cache(key, &blk) if (found = value_for(key)) found else value = yield blk put(key, value) value end end def self.validate_input_key(key) exception = ActiveRecord::ValueTooLong.new("#{MAX_KEY_LENGTH}" \ " characters is the maximum allowed for key") raise exception if key.length > MAX_KEY_LENGTH end end