Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam Eaton committed Aug 22, 2017
0 parents commit 56eea21
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .editorconfig
@@ -0,0 +1,7 @@
[*.cr]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
8 changes: 8 additions & 0 deletions .gitignore
@@ -0,0 +1,8 @@
/doc/
/lib/
/bin/
/.shards/

# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
/shard.lock
1 change: 1 addition & 0 deletions .travis.yml
@@ -0,0 +1 @@
language: crystal
21 changes: 21 additions & 0 deletions LICENSE
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2017 Sam Eaton

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
43 changes: 43 additions & 0 deletions README.md
@@ -0,0 +1,43 @@
# Cache Hash

A simple key value store where pairs can expire after a set interval

## Installation

Add this to your application's `shard.yml`:

```yaml
dependencies:
cache_hash:
github: samueleaton/cache_hash
```

## Usage

```ruby
require "cache_hash"

# (Hours, Minutes, Seconds), caching 2 seconds
cache_interval = Time::Span.new(0, 0, 2)

cache_hash = CacheHash(String, String).new(cache_interval)
cache_hash["key1"] = "Value 1"
sleep 1 # one second elapsed
cache_hash["key2"] = "Value 2"
sleep 1 # two seconds elapsed

cache_hash["key1"] #=> nil
cache_hash["key2"] #=> "Value 2"
```

## Contributing

1. Fork it ( https://github.com/[your-github-name]/cache_hash/fork )
2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am 'Add some feature')
4. Push to the branch (git push origin my-new-feature)
5. Create a new Pull Request

## Contributors

- [Sam Eaton](https://github.com/samueleaton) - creator, maintainer
9 changes: 9 additions & 0 deletions shard.yml
@@ -0,0 +1,9 @@
name: cache_hash
version: 0.1.0

authors:
- Sam Eaton <same@qualtrics.com>

crystal: 0.23.1

license: MIT
183 changes: 183 additions & 0 deletions spec/cache_hash_spec.cr
@@ -0,0 +1,183 @@
require "./spec_helper"

describe CacheHash do
it "saves key value pairs" do
hash = CacheHash(String, String).new(Time::Span.new(0,0,4))
hash["city_1"] = "Seattle"
hash["city_2"] = "Honk Kong"
hash["city_3"] = "Sacramento"
hash["city_1"].should eq("Seattle")
hash["city_2"].should eq("Honk Kong")
hash["city_3"].should eq("Sacramento")
end

it "removes stale kv pairs on lookup" do
hash = CacheHash(String, String).new(Time::Span.new(0,0,3))
hash["city_1"] = "Seattle"
sleep 1
hash["city_2"] = "Honk Kong"
sleep 1
hash["city_3"] = "Sacramento"
sleep 1
hash["city_4"] = "Salt Lake City"
sleep 1
hash["city_5"] = "Denver"
sleep 1

hash.raw["city_1"].should eq("Seattle")
hash.raw["city_2"].should eq("Honk Kong")
hash.raw["city_3"].should eq("Sacramento")
hash.raw["city_4"].should eq("Salt Lake City")
hash.raw["city_5"].should eq("Denver")

hash["city_1"].should be_nil
hash["city_2"].should be_nil
hash["city_3"].should be_nil
hash["city_4"].should eq("Salt Lake City")
hash["city_5"].should eq("Denver")

hash.raw["city_1"]?.should be_nil
hash.raw["city_2"]?.should be_nil
hash.raw["city_3"]?.should be_nil
hash.raw["city_4"]?.should eq("Salt Lake City")
hash.raw["city_5"]?.should eq("Denver")
end

describe "#purge_stale" do
it "removes all stale, expired values from the hash" do
hash = CacheHash(String, String).new(Time::Span.new(0,0,3))
hash["city_1"] = "Seattle"
sleep 1
hash["city_2"] = "Honk Kong"
sleep 1
hash["city_3"] = "Sacramento"
sleep 1
hash["city_4"] = "Salt Lake City"
sleep 1
hash["city_5"] = "Denver"
sleep 1
hash.raw["city_1"].should eq("Seattle")
hash.raw["city_2"].should eq("Honk Kong")
hash.raw["city_3"].should eq("Sacramento")
hash.raw["city_4"].should eq("Salt Lake City")
hash.raw["city_5"].should eq("Denver")
hash.purge_stale
hash.raw["city_1"]?.should be_nil
hash.raw["city_2"]?.should be_nil
hash.raw["city_3"]?.should be_nil
hash.raw["city_4"]?.should eq("Salt Lake City")
hash.raw["city_5"]?.should eq("Denver")
end
end

describe "#fresh" do
it "purges all stale values and returns the IDs of non-stale kv pairs" do
hash = CacheHash(String, String).new(Time::Span.new(0,0,3))
hash["city_1"] = "Seattle"
sleep 1
hash["city_2"] = "Honk Kong"
sleep 1
hash["city_3"] = "Sacramento"
sleep 1
hash["city_4"] = "Salt Lake City"
sleep 1
hash["city_5"] = "Denver"
sleep 1

hash.raw["city_1"].should eq("Seattle")
hash.raw["city_2"].should eq("Honk Kong")
hash.raw["city_3"].should eq("Sacramento")
hash.raw["city_4"].should eq("Salt Lake City")
hash.raw["city_5"].should eq("Denver")

hash.fresh.should eq(["city_4", "city_5"])

hash.raw["city_1"]?.should be_nil
hash.raw["city_2"]?.should be_nil
hash.raw["city_3"]?.should be_nil
hash.raw["city_4"]?.should eq("Salt Lake City")
hash.raw["city_5"]?.should eq("Denver")
end
end

describe "#fresh?" do
it "returns a true if the kv pair is not stale" do
hash = CacheHash(String, String).new(Time::Span.new(0,0,3))
hash["city_1"] = "Seattle"
sleep 1
hash["city_2"] = "Honk Kong"
sleep 1
hash["city_3"] = "Sacramento"
sleep 1
hash["city_4"] = "Salt Lake City"
sleep 1
hash["city_5"] = "Denver"
sleep 1

hash.fresh?("city_1").should be_false
hash.fresh?("city_4").should be_true
hash.fresh?("xxxxx").should be_false
sleep 2
hash.fresh?("city_4").should be_false
end

it "removes deletes the kv pair if it is stale" do
hash = CacheHash(String, String).new(Time::Span.new(0,0,3))
hash["city_1"] = "Seattle"
sleep 1
hash["city_2"] = "Honk Kong"
sleep 1
hash["city_3"] = "Sacramento"
sleep 1
hash["city_4"] = "Salt Lake City"
sleep 1
hash["city_5"] = "Denver"
sleep 1

hash.raw["city_1"].should eq("Seattle")
hash.raw["city_2"].should eq("Honk Kong")
hash.raw["city_3"].should eq("Sacramento")
hash.raw["city_4"].should eq("Salt Lake City")
hash.raw["city_5"].should eq("Denver")

(hash.fresh?("city_1")).should be_false
hash.raw["city_1"]?.should be_nil
end
end

describe "#time" do
it "returns a time if the kv pair is not stale" do
hash = CacheHash(String, String).new(Time::Span.new(0,0,3))

t = Time.now
hash["city_1"] = "Seattle"
hash.time("city_1").class.should eq(Time)

city_1_time = hash.time("city_1").not_nil!
(city_1_time > t && city_1_time < Time.now).should be_true
end

it "delete the kv pair if it is stale" do
hash = CacheHash(String, String).new(Time::Span.new(0,0,3))
hash["city_1"] = "Seattle"
sleep 1
hash["city_2"] = "Honk Kong"
sleep 1
hash["city_3"] = "Sacramento"
sleep 1
hash["city_4"] = "Salt Lake City"
sleep 1
hash["city_5"] = "Denver"
sleep 1

hash.raw["city_1"].should eq("Seattle")
hash.raw["city_2"].should eq("Honk Kong")
hash.raw["city_3"].should eq("Sacramento")
hash.raw["city_4"].should eq("Salt Lake City")
hash.raw["city_5"].should eq("Denver")

hash.time("city_1").should be_nil
hash.raw["city_1"]?.should be_nil
end
end
end
8 changes: 8 additions & 0 deletions spec/spec_helper.cr
@@ -0,0 +1,8 @@
require "spec"
require "../src/cache_hash"

class CacheHash
def raw
@kv_hash
end
end
65 changes: 65 additions & 0 deletions src/cache_hash.cr
@@ -0,0 +1,65 @@
class CacheHash(K, T)
def initialize(@cache_time_span : Time::Span)
@kv_hash = {} of K => String
@time_hash = {} of K => Time
end

def [](key)
if cached_time = @time_hash[key]?
if cached_time > Time.now - @cache_time_span
@kv_hash[key]
else
delete key
nil
end
end
end

def []=(key, val : T)
@time_hash[key] = Time.now
@kv_hash[key] = val
end

private def delete(key) : String | Nil
@time_hash.delete key
@kv_hash.delete key
nil
end

def purge_stale()
@kv_hash.select! do |k, v|
if cached_time = @time_hash[k]?
cached_time > Time.now - @cache_time_span
end
end
nil
end

def fresh
purge_stale
@kv_hash.keys
end

def fresh?(k)
if cached_time = @time_hash[k]?
if cached_time > Time.now - @cache_time_span
true
else
delete k
false
end
else
false
end
end

def time(k)
if cached_time = @time_hash[k]?
if cached_time > Time.now - @cache_time_span
cached_time
else
delete k
end
end
end
end

0 comments on commit 56eea21

Please sign in to comment.