Skip to content

Instantly share code, notes, and snippets.

@amkisko
Last active March 10, 2022 11:30
Show Gist options
  • Select an option

  • Save amkisko/a5633ed6e0864e2e566f8644c3095e16 to your computer and use it in GitHub Desktop.

Select an option

Save amkisko/a5633ed6e0864e2e566f8644c3095e16 to your computer and use it in GitHub Desktop.
Rails tuples

Rails tuples

Simple key-value storage with data types and expiration control.

class CreateTuples < ActiveRecord::Migration[7.0]
def change
create_table :tuples do |t|
t.string :key, index: { unique: true }
t.jsonb :data, default: {}
t.integer :datatype
t.datetime :expires_at
t.timestamps
end
end
end
class Tuple < ApplicationRecord
validates :key, presence: true, uniqueness: true
validates :datatype, presence: true
enum datatype: {
Integer: 10,
String: 20,
Hash: 30
}, _prefix: true
def self.get(key)
record = find_by(key:)
return if record.blank?
return if record.expired?
record.value
end
def self.set(key, value = nil, expires_in: nil, &block)
upsert_all([{
key:,
value: value || block&.call,
expires_at: (expires_in.since if expires_in.present?)
}], unique_by: "key")
end
def self.reset(key)
where(key:).delete_all
end
def self.fetch(key, value = nil, expires_in: nil, &block)
record = where(key:).first_or_initialize
if !record.persisted? || record.expired?
record.update!({
value: value || block&.call,
expires_at: (expires_in.since if expires_in.present?)
})
end
record.value
end
def self.increment(key, amount = 1, expires_in: nil)
where(key:, datatype: :Integer).update_all("data = to_jsonb((data::integer) + #{amount.to_i})")
end
def expired?
expires_at.present? && expires_at.past?
end
def value
case datatype
when "Integer"
data.to_i
when "String"
data.to_s
when "Hash"
data
else
raise "Invalid data type"
end
end
def value=(new_value)
self.data = new_value
self.datatype = data.class.name
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment