Simple key-value storage with data types and expiration control.
Last active
March 10, 2022 11:30
-
-
Save amkisko/a5633ed6e0864e2e566f8644c3095e16 to your computer and use it in GitHub Desktop.
Rails tuples
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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