Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial easycache #1

Merged
merged 14 commits into from
Feb 27, 2024
24 changes: 24 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Build gem

on:
push:
pull_request:
branches:
- main

jobs:
build:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]')
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['3.3', '3.2', '3.1', '3.0', '2.7']
name: Build gem with Ruby ${{ matrix.ruby-version }}
steps:
- uses: actions/checkout@v2
- name: Set up Ruby ${{ matrix.ruby-version }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
- name: Gem build with Ruby ${{ matrix.ruby-version }}
run: gem build easycache.gemspec
21 changes: 21 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: RuboCop

on:
push:
pull_request:
branches:
- main

jobs:
rubocop:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]')
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Install RuboCop
run: sudo gem install rubocop -v 1.59.0

- name: Run RuboCop
run: rubocop ./lib
24 changes: 24 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Tests

on:
push:
pull_request:
branches:
- main

jobs:
test:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]')
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['3.3', '3.2', '3.1', '3.0', '2.7']
name: Test with Ruby ${{ matrix.ruby-version }}
steps:
- uses: actions/checkout@v2
- name: Set up Ruby ${{ matrix.ruby-version }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
- name: Run tests with Ruby ${{ matrix.ruby-version }}
run: rake test
61 changes: 59 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,59 @@
# easy-cache
Easy to use in-mem cache system for ruby
# EasyCache
<p align="center">

[![Gem Version](https://badge.fury.io/rb/easy-cache.svg)](https://badge.fury.io/rb/easy-cache)
![License](https://img.shields.io/badge/license-AGPL%203.0-blue.svg)
[![Lint](https://github.com/malvads/easy-cache/actions/workflows/lint.yml/badge.svg)](https://github.com/malvads/easy-cache/actions/workflows/lint.yml)
[![Tests](https://github.com/malvads/easy-cache/actions/workflows/tests.yml/badge.svg)](https://github.com/malvads/easy-cache/actions/workflows/tests.yml)
[![Build](https://github.com/malvads/easy-cache/actions/workflows/build.yml/badge.svg)](https://github.com/malvads/easy-cache/actions/workflows/build.yml)
</p>
EasyCache is an in-memory cache system for Ruby designed for situations where you don't want to set up Redis or Memcached but still need a simple solution for caching key-value data.

## Install

```
gem install easy-cache
```

## Usage

To use EasyCache in your Ruby project, require the library and include it in your code:

```ruby
require 'easycache'

cache = EasyCache.new
```

## Storing data

```ruby
key = "my_key"
cache_ttl = 3600
store_in_mem = true
data = cache.fetch(cache_key, cache_ttl, store_in_mem) do
my_http_get
end
```

Now data is in-mem for the next 3600 second (store_in_mem variable is important for storing data first time).

## Getting data

If i want to get the data stored in mem i do

```ruby
data = cache.fetch("my_key")
```

or i can also re-call the same function

```ruby
data = cache.fetch("my_key", cache_ttl, store_in_mem) do
my_http_get
end
```

because the data is already cached, so it will not call the block, it will return the cached data instead.

This will output the cached data, remember that cached data is stored in mem for only 3600 seconds
9 changes: 9 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'rake/testtask'

Rake::TestTask.new(:test) do |test|
test.libs << '.'
test.pattern = 'test/test_*.rb'
test.verbose = true
end

task default: :test
11 changes: 11 additions & 0 deletions easycache.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Gem::Specification.new do |s|
s.name = "easy-cache"
s.version = "0.0.1"
s.summary = "Easy to use in-mem cache system for ruby"
s.description = "A simple gem for store and manage data in mem"
s.authors = ["Miguel Álvarez"]
s.email = "thegexi@gmail.com"
s.files = Dir['lib/**/*', 'LICENSE', 'README.md']
s.homepage = "https://rubygems.org/gems/easycache"
s.license = "AGPL-3.0"
end
71 changes: 71 additions & 0 deletions lib/easycache.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

#
# This is the core of EasyCache.
# EasyCache provides an easy-to-use, in-memory cache system for Ruby.
# It is designed for situations where you don't want to set up Redis or Memcached but still want
# a simple solution for caching key-value data.
#
class EasyCache
def initialize
@cache = {}
@mutex = Mutex.new
end

def fetch(key, expiration = 3600, store_in_cache = false) # rubocop:disable Style/OptionalBooleanParameter
@mutex.synchronize do
return cached_value(key, store_in_cache) if cache_contains_valid_data?(key) && !block_given?

if should_fetch_from_block?(key, store_in_cache)
value = block_given? ? yield : nil
cache_value(key, value, expiration) if value && store_in_cache
value
else
cached_value(key, store_in_cache)
end
end
end

private

def cache_contains_valid_data?(key)
@cache.key?(key) && !expired?(key)
end

def should_fetch_from_block?(key, store_in_cache)
!store_in_cache || (!@cache.key?(key) || expired?(key))
end

def cached_value(key, _store_in_cache)
@cache[key][:value]
end

def cache_value(key, value, expiration)
@cache[key] = { value: value, timestamp: Time.now, expiration: expiration }
end

def expired?(key)
return false unless @cache.key?(key) && @cache[key].key?(:timestamp) && @cache[key].key?(:expiration)

expiration_date = @cache[key][:timestamp] + @cache[key][:expiration]
if Time.now > expiration_date
@cache.delete(key)
true
else
false
end
end
end
31 changes: 31 additions & 0 deletions test/test_easycache.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require_relative '../lib/easycache'
require 'test/unit'

# Test EasyCache class
class EasyCacheTest < Test::Unit::TestCase
def setup
@cache = EasyCache.new
end

def test_fetch_caches_value_for_given_key
@cache.fetch('test_key', 3600, true) { 'test_value' }
assert_instance_of(Time, @cache.instance_variable_get(:@cache)['test_key'][:timestamp])
end

def test_fetch_retrieves_cached_value_for_given_key_if_not_expired
@cache.fetch('test_key', 3600, true) { 'test_value' }
assert_equal 'test_value', @cache.fetch('test_key')
end

def test_fetch_expires_cached_value_after_specified_expiration_time
@cache.fetch('test_key', 1, true) { 'test_value' }
sleep(2)
assert_nil @cache.fetch('test_key')
end

def test_fetch_recomputes_expired_value_if_fetched_again
@cache.fetch('test_key', 1, true) { 'test_value' }
sleep(2)
assert_equal 'new_test_value', @cache.fetch('test_key') { 'new_test_value' }
end
end
Loading