cachet 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|