Skip to content

Instantly share code, notes, and snippets.

@wrightling
Created August 18, 2014 21:03
Show Gist options
  • Save wrightling/59761c0eb2c7f544eba6 to your computer and use it in GitHub Desktop.
Save wrightling/59761c0eb2c7f544eba6 to your computer and use it in GitHub Desktop.
class CreateKeyVals < ActiveRecord::Migration
def change
create_table :key_vals do |t|
t.string :key
t.text :value
t.string :value_type
t.timestamps
end
add_index :key_vals, :key, unique: true
end
end
class KeyVal < ActiveRecord::Base
def self.[] key
Rails.cache.fetch(key, expires_in: 5.minutes) do
KeyVal.find_by!(key: key).convert
end
end
def self.[]= key, value
keyval = KeyVal.find_or_initialize_by key: key
keyval.update value: value, value_type: discern_type(value)
Rails.cache.clear key if Rails.cache.exist? key
end
def convert
case value_type
when 'integer'
value.to_i
when 'float'
value.to_f
when 'boolean'
value == 'true'
else
value
end
end
private
def self.discern_type value
case value
when Integer
'integer'
when Float
'float'
when TrueClass, FalseClass
'boolean'
else
'string'
end
end
end
require 'rails_helper'
describe KeyVal, focus: true do
before :each do
Rails.cache.clear
end
context "storing values" do
it "is stored with value_type=string for strings" do
KeyVal[:key] = 'some string'
expect(KeyVal.first.value_type).to eql 'string'
end
it "is stored with value_type=integer for integers" do
KeyVal[:key] = 42
expect(KeyVal.first.value_type).to eql 'integer'
end
it "is stored with value_type=float for floats" do
KeyVal[:key] = 4.2
expect(KeyVal.first.value_type).to eql 'float'
end
it "is stored with value_type=boolean for true or false" do
KeyVal[:key] = true
expect(KeyVal.first.value_type).to eql 'boolean'
end
it "overwrites the value if a key is used twice" do
KeyVal[:key] = 42
KeyVal[:key] = 'forty-two'
expect(KeyVal.first.value).to eql 'forty-two'
end
end
describe "retrieving stored values" do
it "comes back as an instance of class String for strings" do
KeyVal[:key] = 'some string'
expect(KeyVal[:key]).to be_a(String)
end
it "comes back as an instance of class Integer for integers" do
KeyVal[:key] = 42
expect(KeyVal[:key]).to be_a(Integer)
end
it "comes back as an instance of class Float for floats" do
KeyVal[:key] = 4.2
expect(KeyVal[:key]).to be_a(Float)
end
it "comes back as an instance of class FalseClass for a false boolean" do
KeyVal[:key] = false
expect(KeyVal[:key]).to be_a(false.class)
end
it "raises an error if the key is not found" do
expect { KeyVal[:key] }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
@wrightling
Copy link
Author

A few thoughts:

I spent an hour or so putting this together. I wanted a very simple key-value store that would work with any relational database. The values are stored as text fields to avoid size limitations.

Intended Use:

KeyVal[:meaning_of_life] = 42
meaning = KeyVal[:meaning_of_life]  # => 42 (Fixnum)
KeyVal['pi'] = 3.14
pi = KeyVal['pi']  # 3.14 (Float)
KeyVal['da_bool'] = true
res = KeyVal['da_bool']  # true (TrueClass)
KeyVal['some string'] = 'abcdefghijklmnop'
some_string = KeyVal['some string']  # 'abcdefghijklmnop' (String)

As you can see, some rudimentary type checking (something we almost always want to avoid in a dynamic, duck-typing-prone language like Ruby) lets us determine whether incoming values are integers, floats, booleans, or default them to string, and lets us return values having already been cast to their appropriate type.

The caching is super basic low-level rails caching.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment