cachet 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +50 -0
- data/lib/cachet.rb +35 -0
- data/lib/cachet/cacheable.rb +31 -0
- data/lib/cachet/file_store.rb +59 -0
- data/test/test_file_store.rb +59 -0
- data/test/test_helper.rb +7 -0
- metadata +84 -0
data/README
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
Just initialize the cache, by configuring your store.
|
2
|
+
Just add the configuration to your config initializers
|
3
|
+
|
4
|
+
require 'cachet'
|
5
|
+
|
6
|
+
Cachet.setup do |config|
|
7
|
+
config.logger = Rails.logger
|
8
|
+
config.storage = Cachet::FileStore.new("/pal_storage/path")
|
9
|
+
|
10
|
+
# The followings are the configurations that you can do on file store
|
11
|
+
# The given values are the default ones, if you are OK with them you don't need
|
12
|
+
# to configure at all.
|
13
|
+
# You can set whether you want directory optimization or not
|
14
|
+
# file_store.optimize = TRUE
|
15
|
+
# The depth of directories you want
|
16
|
+
# file_store.dir_levels = 3
|
17
|
+
# Number of directories within a directory, it is better if you use a prime number
|
18
|
+
# but something other than 31, that is the one we use for hashing :)
|
19
|
+
# file_store.dir_count = 19
|
20
|
+
end
|
21
|
+
|
22
|
+
And from this point forward , if you want return values of your methods to be cached;
|
23
|
+
|
24
|
+
#include cacheable module , which will add two class macros to mark a method as cacheable and also as cache invalidator.
|
25
|
+
You should pass blocks to these marcos which will return cache keys. Use exact signature of the method that you are referring in the block that returns the key.
|
26
|
+
|
27
|
+
|
28
|
+
class Sample
|
29
|
+
include Cachet::Cacheable
|
30
|
+
|
31
|
+
def first (param1, param2)
|
32
|
+
puts "First is running"
|
33
|
+
return "First"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def second (param1, param2)
|
38
|
+
puts "Second is running and invalidating for #{param1}"
|
39
|
+
return "First"
|
40
|
+
end
|
41
|
+
|
42
|
+
cacheable :first, :car do |param1, param2|
|
43
|
+
param1
|
44
|
+
end
|
45
|
+
|
46
|
+
cache_invalidator :second, :car do |param1, param2|
|
47
|
+
param1
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/cachet.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
3
|
+
module Cachet
|
4
|
+
mattr_accessor :logger
|
5
|
+
mattr_accessor :storage
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def setup
|
9
|
+
yield self
|
10
|
+
end
|
11
|
+
|
12
|
+
def cache (entity, key)
|
13
|
+
cached_item = storage.read(entity, key)
|
14
|
+
if not cached_item
|
15
|
+
cached_item = yield
|
16
|
+
storage.write(entity, key, cached_item)
|
17
|
+
Cachet.logger.info "#{entity} with key:#{key} could not found in cache. Evaluated and written to cache"
|
18
|
+
else
|
19
|
+
Cachet.logger.info "#{entity} with key:#{key} found in cache."
|
20
|
+
end
|
21
|
+
cached_item
|
22
|
+
end
|
23
|
+
|
24
|
+
def invalidate (entity, key)
|
25
|
+
storage.purge(entity, key)
|
26
|
+
Cachet.logger.info "#{entity} with key:#{key} invalidated."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'cachet/file_store'
|
33
|
+
require 'cachet/cacheable'
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cachet
|
2
|
+
module Cacheable
|
3
|
+
def self.included(base)
|
4
|
+
Cachet.logger.debug "Including Cacheable in #{base}"
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def cacheable (cached_method, entity)
|
10
|
+
define_method("#{cached_method}_with_cache") { |*args|
|
11
|
+
cache_key = block_given? ? yield(*args) : cached_method.to_s
|
12
|
+
Cachet.cache(entity, cache_key) do
|
13
|
+
method("#{cached_method}_without_cache".to_sym).call(*args)
|
14
|
+
end
|
15
|
+
}
|
16
|
+
alias_method_chain "#{cached_method}".to_sym, :cache
|
17
|
+
Cachet.logger.debug "Aliasing method : #{cached_method} to cache the result as #{entity} entities"
|
18
|
+
end
|
19
|
+
|
20
|
+
def cache_invalidator (cached_method, entity)
|
21
|
+
define_method("#{cached_method}_with_cache_invalidation") { |*args|
|
22
|
+
cache_key = block_given? ? yield(*args) : cached_method.to_s
|
23
|
+
method("#{cached_method}_without_cache_invalidation".to_sym).call(*args)
|
24
|
+
Cachet.invalidate(entity, cache_key)
|
25
|
+
}
|
26
|
+
alias_method_chain "#{cached_method}".to_sym, :cache_invalidation
|
27
|
+
Cachet.logger.debug "Aliasing method : #{cached_method} to invalidate the #{entity} entities"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Cachet
|
4
|
+
class FileStore
|
5
|
+
attr_reader :root
|
6
|
+
attr_accessor :optimize
|
7
|
+
attr_accessor :dir_level
|
8
|
+
attr_accessor :dir_count
|
9
|
+
|
10
|
+
def initialize(root)
|
11
|
+
@root = root
|
12
|
+
FileUtils.mkdir_p root, :mode=>0755
|
13
|
+
@optimize = TRUE
|
14
|
+
@dir_level = 3
|
15
|
+
@dir_count = 19
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(entity, key)
|
19
|
+
file_name = storage_path(entity, key)
|
20
|
+
File.open(file_name, 'rb') { |f| Marshal.load f.read } if File.exist?(file_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def write(entity, key, data_to_be_stored)
|
24
|
+
file_name = storage_path(entity, key)
|
25
|
+
temp_file_name = file_name + '.' + Thread.current.object_id.to_s
|
26
|
+
FileUtils.mkdir_p File.dirname(file_name), :mode => 755
|
27
|
+
File.open(temp_file_name, 'wb') { |f| f.write(Marshal.dump(data_to_be_stored)) }
|
28
|
+
if File.exist?(file_name)
|
29
|
+
File.unlink temp_file_name
|
30
|
+
else
|
31
|
+
FileUtils.mv temp_file_name, file_name
|
32
|
+
end
|
33
|
+
Cachet.logger.info "A new #{entity} entity stored in the cache with key:#{key} to path #{file_name}."
|
34
|
+
end
|
35
|
+
|
36
|
+
def purge(entity, key)
|
37
|
+
file_name = storage_path(entity, key)
|
38
|
+
begin
|
39
|
+
File.unlink file_name if File.exist?(file_name)
|
40
|
+
rescue
|
41
|
+
Cachet.logger.warn "An element of #{entity} in the cache with key:#{key} to path #{file_name}entity has could not be deleted."
|
42
|
+
end
|
43
|
+
Cachet.logger.info "An element of #{entity} in the cache with key:#{key} to path #{file_name}entity has been removed from the cache."
|
44
|
+
end
|
45
|
+
|
46
|
+
def storage_path(entity, key)
|
47
|
+
file_name = Digest::MD5.hexdigest(key)
|
48
|
+
if optimize
|
49
|
+
dirs = (1..dir_level).inject({:hash=>key.sum, :dirs=>[]}) do |result, level|
|
50
|
+
result[:dirs] << (result[:hash] % dir_count).abs.to_s
|
51
|
+
result[:hash] += (result[:hash] * 31)
|
52
|
+
result
|
53
|
+
end
|
54
|
+
file_name = File.join dirs[:dirs], file_name
|
55
|
+
end
|
56
|
+
File.join root, entity.to_s, file_name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FileStoreTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
FakeFS.activate!
|
7
|
+
FakeFS::FileSystem.clear
|
8
|
+
@test_store = Cachet::FileStore.new("~/testroot/")
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
FakeFS.deactivate!
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_storage_path_creation
|
16
|
+
assert_equal "~/testroot/test/2/7/15/25f9e794323b453885f5181f1b624d0b", @test_store.storage_path(:test, "123456789"), "Check storage path, hashing seems to be not working."
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_idempotent_storage_paths
|
20
|
+
assert_equal @test_store.storage_path(:test, "123456789"), @test_store.storage_path(:test, "123456789"), "Should always create same path for same entry."
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_storage_options
|
24
|
+
@test_store.dir_level = 10
|
25
|
+
assert_equal "~/testroot/test/2/7/15/5/8/9/3/1/13/17/25f9e794323b453885f5181f1b624d0b", @test_store.storage_path(:test, "123456789"), "Should nest resourced under 10 recursive directories."
|
26
|
+
@test_store.dir_count = 100
|
27
|
+
assert_equal "~/testroot/test/77/64/48/36/52/64/48/36/52/64/25f9e794323b453885f5181f1b624d0b", @test_store.storage_path(:test, "123456789"), "Should allow 100 hundred directories under any directory."
|
28
|
+
@test_store.optimize = FALSE
|
29
|
+
assert_equal "~/testroot/test/25f9e794323b453885f5181f1b624d0b", @test_store.storage_path(:test, "123456789"), "Should not optimize paths since optimize is disabled."
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_purging_entries
|
33
|
+
file_name = @test_store.storage_path(:test, 'test_item_1')
|
34
|
+
FileUtils.mkdir_p File.dirname(file_name), :mode => 755
|
35
|
+
File.open(file_name, 'wb') { |f| f.write("Data") }
|
36
|
+
assert_equal(true, File.exist?(file_name))
|
37
|
+
@test_store.purge(:test, 'test_item_1')
|
38
|
+
assert_equal(false, File.exist?(file_name))
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_purging_nonexisting_entries
|
42
|
+
file_name = @test_store.storage_path(:test, 'test_item_2')
|
43
|
+
assert_equal(false, File.exist?(file_name))
|
44
|
+
assert_nothing_thrown("FileStore should be slince while purging nonexisting entities") {
|
45
|
+
@test_store.purge(:test, 'test_item_2')
|
46
|
+
}
|
47
|
+
assert_equal(false, File.exist?(file_name))
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_reading_existing_entries
|
51
|
+
@test_store.write(:test, 'test_item_3', "Some test data")
|
52
|
+
assert_equal("Some test data", @test_store.read(:test, 'test_item_3'))
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_reading_nonexisting_entries
|
56
|
+
@test_store.purge(:test, 'test_item_4')
|
57
|
+
assert_nil @test_store.read(:test, 'test_item_4')
|
58
|
+
end
|
59
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cachet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Meltem Atesalp
|
9
|
+
- Umut Utkan
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2012-02-07 00:00:00 +02:00
|
15
|
+
default_executable:
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: rails
|
19
|
+
prerelease: false
|
20
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 3.0.0
|
26
|
+
type: :runtime
|
27
|
+
version_requirements: *id001
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: fakefs
|
30
|
+
prerelease: false
|
31
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: "0"
|
37
|
+
type: :development
|
38
|
+
version_requirements: *id002
|
39
|
+
description: Provides a way to cache and invalidate return values of your time consuming methods.
|
40
|
+
email: meltem.atesalp@gmail.com
|
41
|
+
executables: []
|
42
|
+
|
43
|
+
extensions: []
|
44
|
+
|
45
|
+
extra_rdoc_files: []
|
46
|
+
|
47
|
+
files:
|
48
|
+
- lib/cachet.rb
|
49
|
+
- lib/cachet/cacheable.rb
|
50
|
+
- lib/cachet/file_store.rb
|
51
|
+
- README
|
52
|
+
- test/test_file_store.rb
|
53
|
+
- test/test_helper.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: https://github.com/meltem/cachet
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.6.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Caches method responses to have a better performance!
|
82
|
+
test_files:
|
83
|
+
- test/test_file_store.rb
|
84
|
+
- test/test_helper.rb
|