coffee_table 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +38 -20
- data/README.textile +9 -5
- data/changelog.txt +7 -1
- data/coffee_table.gemspec +1 -0
- data/lib/coffee_table/block_missing_error.rb +4 -0
- data/lib/coffee_table/invalid_object_error.rb +5 -0
- data/lib/coffee_table/key.rb +16 -5
- data/lib/coffee_table/object_definition.rb +18 -0
- data/lib/coffee_table/version.rb +1 -1
- data/lib/coffee_table.rb +34 -18
- data/spec/lib/coffee_table_spec.rb +109 -19
- data/spec/lib/key_spec.rb +56 -9
- data/spec/spec_helper.rb +8 -6
- metadata +59 -16
- data/.rvmrc +0 -1
- data/lib/coffee_table/coffee_table_block_missing_error.rb +0 -3
- data/lib/coffee_table/coffee_table_invalid_object_error.rb +0 -3
data/.gitignore
CHANGED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
coffee_table
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-1.9.3-p392
|
data/Gemfile
CHANGED
@@ -3,14 +3,15 @@ source "http://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in coffee_table.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem "redis"
|
7
6
|
gem "awesome_print"
|
8
7
|
gem "rufus-scheduler"
|
9
8
|
gem "activesupport"
|
10
9
|
gem "sourcify", :git => "git@github.com:stewartmckee/sourcify.git"
|
10
|
+
gem "gzip"
|
11
11
|
|
12
12
|
group :test do
|
13
13
|
gem "rspec"
|
14
14
|
gem "mock_redis"
|
15
15
|
gem "spork"
|
16
|
-
|
16
|
+
gem 'coveralls', require: false
|
17
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -11,8 +11,9 @@ GIT
|
|
11
11
|
PATH
|
12
12
|
remote: .
|
13
13
|
specs:
|
14
|
-
coffee_table (0.1.
|
14
|
+
coffee_table (0.1.2)
|
15
15
|
activesupport
|
16
|
+
gzip
|
16
17
|
redis
|
17
18
|
rufus-scheduler
|
18
19
|
sourcify
|
@@ -20,36 +21,52 @@ PATH
|
|
20
21
|
GEM
|
21
22
|
remote: http://rubygems.org/
|
22
23
|
specs:
|
23
|
-
activesupport (3.2.
|
24
|
-
i18n (~> 0.6)
|
24
|
+
activesupport (3.2.14)
|
25
|
+
i18n (~> 0.6, >= 0.6.4)
|
25
26
|
multi_json (~> 1.0)
|
26
27
|
awesome_print (1.1.0)
|
27
|
-
|
28
|
+
colorize (0.5.8)
|
29
|
+
coveralls (0.6.7)
|
30
|
+
colorize
|
31
|
+
multi_json (~> 1.3)
|
32
|
+
rest-client
|
33
|
+
simplecov (>= 0.7)
|
34
|
+
thor
|
35
|
+
diff-lcs (1.2.4)
|
28
36
|
file-tail (1.0.12)
|
29
37
|
tins (~> 0.5)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
rspec
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
gzip (1.0)
|
39
|
+
i18n (0.6.4)
|
40
|
+
mime-types (1.23)
|
41
|
+
mock_redis (0.9.0)
|
42
|
+
multi_json (1.7.8)
|
43
|
+
redis (3.0.4)
|
44
|
+
rest-client (1.6.7)
|
45
|
+
mime-types (>= 1.16)
|
46
|
+
rspec (2.14.1)
|
47
|
+
rspec-core (~> 2.14.0)
|
48
|
+
rspec-expectations (~> 2.14.0)
|
49
|
+
rspec-mocks (~> 2.14.0)
|
50
|
+
rspec-core (2.14.4)
|
51
|
+
rspec-expectations (2.14.1)
|
52
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
53
|
+
rspec-mocks (2.14.3)
|
42
54
|
ruby2ruby (1.3.1)
|
43
55
|
ruby_parser (~> 2.0)
|
44
56
|
sexp_processor (~> 3.0)
|
45
57
|
ruby_parser (2.3.1)
|
46
58
|
sexp_processor (~> 3.0)
|
47
|
-
rufus-scheduler (2.0.
|
59
|
+
rufus-scheduler (2.0.23)
|
48
60
|
tzinfo (>= 0.3.23)
|
49
61
|
sexp_processor (3.2.0)
|
62
|
+
simplecov (0.7.1)
|
63
|
+
multi_json (~> 1.0)
|
64
|
+
simplecov-html (~> 0.7.1)
|
65
|
+
simplecov-html (0.7.1)
|
50
66
|
spork (0.9.2)
|
51
|
-
|
52
|
-
|
67
|
+
thor (0.18.1)
|
68
|
+
tins (0.8.3)
|
69
|
+
tzinfo (1.0.1)
|
53
70
|
|
54
71
|
PLATFORMS
|
55
72
|
ruby
|
@@ -58,8 +75,9 @@ DEPENDENCIES
|
|
58
75
|
activesupport
|
59
76
|
awesome_print
|
60
77
|
coffee_table!
|
78
|
+
coveralls
|
79
|
+
gzip
|
61
80
|
mock_redis
|
62
|
-
redis
|
63
81
|
rspec
|
64
82
|
rufus-scheduler
|
65
83
|
sourcify!
|
data/README.textile
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
|
2
2
|
h1. CoffeeTable v0.1.1
|
3
3
|
|
4
|
+
!https://badge.fury.io/rb/coffee_table.png!:http://badge.fury.io/rb/coffee_table
|
4
5
|
!https://gemnasium.com/stewartmckee/coffee_table.png!
|
6
|
+
!https://coveralls.io/repos/stewartmckee/coffee_table/badge.png?branch=master(Coverage Status)!:https://coveralls.io/r/stewartmckee/coffee_table
|
5
7
|
|
6
8
|
h2. Intro
|
7
9
|
|
8
|
-
CoffeeTable was born out of a frustration with the standard caching methods. Maintaining the cache keys constantly was a headache and 'bet its a caching issue' was a phrase uttered way too much. CoffeeTable was designed to take on the role of maintaining the cache keys for you, allowing you to concentrate on what is in the cache. It works by maintaining a list of its keys in a known format and when expiry is required for an object it knows which ones to expire. It also hopefully will be a perfromance boost for some cases where you are being overly cautious about clearing cache, a more targeted approach will improve performance.
|
10
|
+
CoffeeTable is a smart fragment caching gem that was born out of a frustration with the standard caching methods. Maintaining the cache keys constantly was a headache and 'bet its a caching issue' was a phrase uttered way too much. CoffeeTable was designed to take on the role of maintaining the cache keys for you, allowing you to concentrate on what is in the cache. It works by maintaining a list of its keys in a known format and when expiry is required for an object it knows which ones to expire. It also hopefully will be a perfromance boost for some cases where you are being overly cautious about clearing cache, a more targeted approach will improve performance.
|
9
11
|
|
10
12
|
h3. Installation
|
11
13
|
|
@@ -34,21 +36,23 @@ Creates a new cache object. You can pass options into this method to modify the
|
|
34
36
|
* :redis_server defaults to "127.0.0.1"
|
35
37
|
* :redis_port defaults to 6789
|
36
38
|
* :ignore_code_changes defaults to false. By default a md5 hash of the code in the block is included in the key, if you change the code, the key automatically invalidates. This is to protect against code changes that won't be picked up due to the cache returning.
|
39
|
+
* :compress_content defaults to true and sets whether large strings are compressed
|
40
|
+
* :compress_min_size defaults to 10240, which is 10k any strings larger than this are compressed before being stored
|
37
41
|
|
38
42
|
|
39
|
-
h4.
|
43
|
+
h4. fetch(initial_key, *related_objects, &block)
|
40
44
|
|
41
45
|
This is the main caching method. Pass into this method as a block the chunck of code you want cached. The first parameter is your key for this data and is then followed by as many objects as are valid for this block of code. You can even pass in arrays and they will be expanded. The only requirement for the objects being passed in is that they respond to an 'id' method. If the last parameter is a Hash, this will be used as per cache options. These options can be used for expiry of cache.
|
42
46
|
|
43
|
-
bc. user_details = @coffee_table.
|
47
|
+
bc. user_details = @coffee_table.fetch(:user_detail, @user, :expiry => 600) do
|
44
48
|
@user.get_expensive_user_details
|
45
49
|
end
|
46
50
|
|
47
|
-
Each time this is ran, a unique cache key is generated
|
51
|
+
Each time this is ran when a cache item doesn't exist, a unique cache key is generated based on the data passed in, and the code block being executed. It is good practice to put in objects that are used within the block, as in order to expire the key you need to specify the objects you want to expire for. If this key contained one of those objects, it would be removed and the next time this was ran, fresh data would be placed in the cache.
|
48
52
|
|
49
53
|
If you wish to specify a whole model type, for example, all users from above, you would pass in the class, for example:
|
50
54
|
|
51
|
-
bc. user_details = @coffee_table.
|
55
|
+
bc. user_details = @coffee_table.fetch(:user_detail, User) do
|
52
56
|
@user.get_something_that_uses_all_users
|
53
57
|
end
|
54
58
|
|
data/changelog.txt
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
|
2
|
+
0.1.3
|
3
|
+
- added compression for string content over 10k in size, can be turned off and limit changed
|
4
|
+
|
5
|
+
0.1.2
|
6
|
+
- added ObjectDefinition to allow hashes to be used in key (eg for params in the controller)
|
7
|
+
|
2
8
|
0.1.1
|
3
9
|
- updated documentation
|
4
|
-
-
|
10
|
+
- refactored get_cache method to fetch
|
5
11
|
|
6
12
|
0.1.0
|
7
13
|
|
data/coffee_table.gemspec
CHANGED
data/lib/coffee_table/key.rb
CHANGED
@@ -5,15 +5,16 @@ module CoffeeTable
|
|
5
5
|
include CoffeeTable::Utility
|
6
6
|
|
7
7
|
def self.parse(string)
|
8
|
-
elements = string.split("|").map{|e| decode_element(e) }
|
9
|
-
key = Key.new(elements[0], elements[1])
|
10
|
-
key.elements = elements[2..-
|
8
|
+
elements = string.split("|", -1).map{|e| decode_element(e) }
|
9
|
+
key = Key.new(elements[0], elements[1], Hash[elements.last.split("&").map{|kv| [kv.split("=")[0].to_sym, kv.split("=")[1]]}])
|
10
|
+
key.elements = elements[2..-2]
|
11
11
|
key
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(name, block_key, *objects)
|
14
|
+
def initialize(name, block_key, options, *objects)
|
15
15
|
@name = name
|
16
16
|
@block_key = block_key
|
17
|
+
@options = options
|
17
18
|
@elements = objects.flatten.map{|o| key_for_object(o)}
|
18
19
|
end
|
19
20
|
|
@@ -33,6 +34,14 @@ module CoffeeTable
|
|
33
34
|
@block_key
|
34
35
|
end
|
35
36
|
|
37
|
+
def options
|
38
|
+
@options
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_flag(options)
|
42
|
+
@options.merge!(options)
|
43
|
+
end
|
44
|
+
|
36
45
|
def elements
|
37
46
|
@elements
|
38
47
|
end
|
@@ -46,7 +55,7 @@ module CoffeeTable
|
|
46
55
|
end
|
47
56
|
|
48
57
|
def to_s
|
49
|
-
[encode_element(@name), encode_element(@block_key), @elements.map{|e| encode_element(e) }].flatten.join("|")
|
58
|
+
[encode_element(@name), encode_element(@block_key), @elements.map{|e| encode_element(e) }, encode_element(@options.map{|k,v| "#{k}=#{v}"}.join("&"))].flatten.join("|")
|
50
59
|
end
|
51
60
|
|
52
61
|
private
|
@@ -72,6 +81,8 @@ module CoffeeTable
|
|
72
81
|
"#{ActiveSupport::Inflector.pluralize(underscore(o.to_s))}"
|
73
82
|
elsif o.kind_of?(String) || o.kind_of?(Symbol)
|
74
83
|
"#{underscore(o.to_s)}"
|
84
|
+
elsif o.class == CoffeeTable::ObjectDefinition
|
85
|
+
o.to_s
|
75
86
|
else
|
76
87
|
"#{underscore(o.class.to_s)}[#{o.id}]"
|
77
88
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CoffeeTable
|
2
|
+
class ObjectDefinition
|
3
|
+
|
4
|
+
def initialize(klass, id)
|
5
|
+
@klass = klass
|
6
|
+
@id = id
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.from_hash(hash)
|
10
|
+
hash.map {|k, v| ObjectDefinition.new(k, v)}
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"#{@klass.to_s}[#{@id.to_s}]"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/coffee_table/version.rb
CHANGED
data/lib/coffee_table.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
require "coffee_table/version"
|
2
2
|
require "coffee_table/utility"
|
3
|
+
require "coffee_table/key"
|
4
|
+
require "coffee_table/invalid_object_error"
|
5
|
+
require "coffee_table/block_missing_error"
|
6
|
+
require "coffee_table/object_definition"
|
3
7
|
require "redis"
|
4
8
|
require 'rufus/scheduler'
|
5
9
|
require 'active_support/inflector'
|
6
|
-
require
|
10
|
+
require 'sourcify'
|
7
11
|
require 'digest/md5'
|
12
|
+
require 'gzip'
|
8
13
|
|
9
14
|
module CoffeeTable
|
10
15
|
class Cache
|
@@ -19,18 +24,18 @@ module CoffeeTable
|
|
19
24
|
default_enable_cache_to true
|
20
25
|
default_redis_namespace_to :coffee_table
|
21
26
|
default_redis_server_to "127.0.0.1"
|
22
|
-
default_redis_port_to
|
27
|
+
default_redis_port_to 6379
|
23
28
|
default_ignore_code_changes_to false
|
24
|
-
|
29
|
+
default_compress_content_to true
|
30
|
+
default_compress_min_size_to 10240
|
25
31
|
@redis = Redis.new({:server => @options[:redis_server], :port => @options[:redis_port]})
|
26
32
|
@scheduler = Rufus::Scheduler.start_new
|
27
33
|
|
28
34
|
end
|
29
35
|
|
30
|
-
|
36
|
+
|
31
37
|
def fetch(initial_key, *related_objects, &block)
|
32
|
-
|
33
|
-
raise CoffeeTableBlockMissingError, "No block given to generate cache from" unless block_given?
|
38
|
+
raise CoffeeTable::BlockMissingError, "No block given to generate cache from" unless block_given?
|
34
39
|
|
35
40
|
# extract the options hash if it is present
|
36
41
|
options = {}
|
@@ -40,7 +45,7 @@ module CoffeeTable
|
|
40
45
|
end
|
41
46
|
|
42
47
|
# check objects are valid
|
43
|
-
related_objects.flatten.map{|o| raise
|
48
|
+
related_objects.flatten.map{|o| raise CoffeeTable::InvalidObjectError, "Objects passed in must have an id method or be a class" unless object_valid?(o)}
|
44
49
|
|
45
50
|
if @options[:ignore_code_changes]
|
46
51
|
block_key = ""
|
@@ -48,8 +53,12 @@ module CoffeeTable
|
|
48
53
|
block_key = Digest::MD5.hexdigest(block.to_source)
|
49
54
|
end
|
50
55
|
|
56
|
+
flags = {}
|
57
|
+
|
58
|
+
|
59
|
+
|
51
60
|
# if first related_object is integer or fixnum it is used as an expiry time for the cache object
|
52
|
-
key = Key.new(initial_key, block_key, related_objects)
|
61
|
+
key = CoffeeTable::Key.new(initial_key, block_key, flags, related_objects)
|
53
62
|
|
54
63
|
if @options[:enable_cache]
|
55
64
|
if options.has_key?(:expiry)
|
@@ -60,15 +69,23 @@ module CoffeeTable
|
|
60
69
|
|
61
70
|
@redis.sadd "cache_keys", key unless @redis.sismember "cache_keys", key.to_s
|
62
71
|
if @redis.exists(key.to_s)
|
63
|
-
|
72
|
+
if key.options[:compressed]
|
73
|
+
result = marshal_value(@redis.get(key.to_s).gunzip)
|
74
|
+
else
|
75
|
+
result = marshal_value(@redis.get(key.to_s))
|
76
|
+
end
|
64
77
|
else
|
65
78
|
result = yield
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
79
|
+
|
80
|
+
compress_result = @options[:compress_content] && result.kind_of?(String) && result.length > @options[:compress_min_size]
|
81
|
+
|
82
|
+
if compress_result
|
83
|
+
key.add_flag(:compressed => true)
|
84
|
+
@redis.set key.to_s, Marshal.dump(result.gzip)
|
85
|
+
else
|
86
|
+
@redis.set key.to_s, Marshal.dump(result)
|
87
|
+
end
|
88
|
+
|
72
89
|
unless expiry.nil?
|
73
90
|
@redis.expire key.to_s, expiry
|
74
91
|
@scheduler.in "#{expiry}s" do
|
@@ -83,7 +100,7 @@ module CoffeeTable
|
|
83
100
|
end
|
84
101
|
|
85
102
|
def expire_key(key_value)
|
86
|
-
keys.map{|k| Key.parse(k)}.select{|key| key.has_element?(key_value) || key.to_s == key_value }.each do |key|
|
103
|
+
keys.map{|k| CoffeeTable::Key.parse(k)}.select{|key| key.has_element?(key_value) || key.to_s == key_value }.each do |key|
|
87
104
|
@redis.del(key.to_s)
|
88
105
|
@redis.srem "cache_keys", key.to_s
|
89
106
|
end
|
@@ -109,7 +126,7 @@ module CoffeeTable
|
|
109
126
|
if perform_caching
|
110
127
|
deleted_keys = []
|
111
128
|
unless objects.count == 0
|
112
|
-
keys.map{|k| Key.parse(k)}.each do |key|
|
129
|
+
keys.map{|k| CoffeeTable::Key.parse(k)}.each do |key|
|
113
130
|
expire = true
|
114
131
|
objects.each do |object|
|
115
132
|
if object.class == String || object.class == Symbol
|
@@ -154,6 +171,5 @@ module CoffeeTable
|
|
154
171
|
def object_valid?(o)
|
155
172
|
o.respond_to?(:id) || o.class == Class
|
156
173
|
end
|
157
|
-
|
158
174
|
end
|
159
175
|
end
|
@@ -18,13 +18,13 @@ describe CoffeeTable::Cache do
|
|
18
18
|
CoffeeTable::Cache.new({:test => "asdf"})
|
19
19
|
end
|
20
20
|
it "should not raise exception when hash not given" do
|
21
|
-
lambda{CoffeeTable::Cache.new}.should_not raise_exception
|
21
|
+
lambda{CoffeeTable::Cache.new}.should_not raise_exception
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
describe "fetch" do
|
26
26
|
it "should raise an exception when block not given" do
|
27
|
-
lambda{@coffee_table.fetch("asdf")}.should raise_exception
|
27
|
+
lambda{@coffee_table.fetch("asdf")}.should raise_exception CoffeeTable::BlockMissingError
|
28
28
|
end
|
29
29
|
it "should execute block when cache value not available" do
|
30
30
|
result = @coffee_table.fetch("asdf") do
|
@@ -47,6 +47,73 @@ describe CoffeeTable::Cache do
|
|
47
47
|
|
48
48
|
end
|
49
49
|
|
50
|
+
context "compressing" do
|
51
|
+
|
52
|
+
before(:each) do
|
53
|
+
@redis = Redis.new({:server => "127.0.0.1", :port => 6379})
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
it "compresses on strings greater than limit" do
|
58
|
+
@coffee_table = CoffeeTable::Cache.new(:compress_min_size => 20)
|
59
|
+
zipped_content = "this string should be long".gzip
|
60
|
+
result = @coffee_table.fetch(:test_key) do
|
61
|
+
"this string should be long"
|
62
|
+
end
|
63
|
+
result.should eql "this string should be long"
|
64
|
+
@redis.get("test_key|1bdc7485920d15e21276c0c54cdb5bcf|compressed=true").should eql Marshal.dump(zipped_content)
|
65
|
+
end
|
66
|
+
it "does not compress on non strings" do
|
67
|
+
@coffee_table = CoffeeTable::Cache.new(:compress_min_size => 20)
|
68
|
+
result = @coffee_table.fetch(:test_key) do
|
69
|
+
{:test => "this value is a decent length to trigger compress"}
|
70
|
+
end
|
71
|
+
result.should eql ({:test => "this value is a decent length to trigger compress"})
|
72
|
+
@redis.get("test_key|9e6b3fa8a7fd45acb4734946faf5b61c|").should eql "\x04\b{\x06:\ttestI\"6this value is a decent length to trigger compress\x06:\x06EF"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does not compress when turned off" do
|
76
|
+
@coffee_table = CoffeeTable::Cache.new(:compress_content => false)
|
77
|
+
result = @coffee_table.fetch(:test_key) do
|
78
|
+
"this string should be long"
|
79
|
+
end
|
80
|
+
result.should eql "this string should be long"
|
81
|
+
@redis.get("test_key|1bdc7485920d15e21276c0c54cdb5bcf|").should eql "\x04\bI\"\x1Fthis string should be long\x06:\x06EF"
|
82
|
+
end
|
83
|
+
it "does not compress on strings below limit" do
|
84
|
+
@coffee_table = CoffeeTable::Cache.new(:compress_min_size => 20)
|
85
|
+
result = @coffee_table.fetch(:test_key) do
|
86
|
+
"short"
|
87
|
+
end
|
88
|
+
result.should eql "short"
|
89
|
+
@redis.get("test_key|73a95ee5162c38b2ebda1f70a3ab5893|").should eql "\x04\bI\"\nshort\x06:\x06EF"
|
90
|
+
end
|
91
|
+
it "decompresses compressed value" do
|
92
|
+
@coffee_table = CoffeeTable::Cache.new(:compress_min_size => 20)
|
93
|
+
@coffee_table.fetch(:test_key) do
|
94
|
+
"this string should be long"
|
95
|
+
end
|
96
|
+
result = @coffee_table.fetch(:test_key) do
|
97
|
+
"this string should be long"
|
98
|
+
end
|
99
|
+
result.class.should eql String
|
100
|
+
result.should eql "this string should be long"
|
101
|
+
|
102
|
+
end
|
103
|
+
it "does not decompress a non compressed value" do
|
104
|
+
@coffee_table = CoffeeTable::Cache.new(:compress_min_size => 20)
|
105
|
+
@coffee_table.fetch(:test_key) do
|
106
|
+
"short"
|
107
|
+
end
|
108
|
+
result = @coffee_table.fetch(:test_key) do
|
109
|
+
"short"
|
110
|
+
end
|
111
|
+
result.should eql "short"
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
end
|
116
|
+
|
50
117
|
context "keys" do
|
51
118
|
it "should create a key with just the initial key" do
|
52
119
|
md5 = md5_block do
|
@@ -55,7 +122,7 @@ describe CoffeeTable::Cache do
|
|
55
122
|
result = @coffee_table.fetch(:test_key) do
|
56
123
|
"this is a changed value"
|
57
124
|
end
|
58
|
-
@coffee_table.keys.should == ["test_key|#{md5}"]
|
125
|
+
@coffee_table.keys.should == ["test_key|#{md5}|"]
|
59
126
|
end
|
60
127
|
|
61
128
|
it "should create key from class" do
|
@@ -65,7 +132,7 @@ describe CoffeeTable::Cache do
|
|
65
132
|
result = @coffee_table.fetch(:test_key, SampleClass) do
|
66
133
|
"this is a changed value"
|
67
134
|
end
|
68
|
-
@coffee_table.keys.should == ["test_key|#{md5}|sample_classes"]
|
135
|
+
@coffee_table.keys.should == ["test_key|#{md5}|sample_classes|"]
|
69
136
|
end
|
70
137
|
|
71
138
|
it "should use class name for keys" do
|
@@ -75,7 +142,7 @@ describe CoffeeTable::Cache do
|
|
75
142
|
result = @coffee_table.fetch(:test_key, SampleClass.new(2)) do
|
76
143
|
"this is a changed value"
|
77
144
|
end
|
78
|
-
@coffee_table.keys.should == ["test_key|#{md5}|sample_class[2]"]
|
145
|
+
@coffee_table.keys.should == ["test_key|#{md5}|sample_class[2]|"]
|
79
146
|
end
|
80
147
|
|
81
148
|
it "should use id from class in key" do
|
@@ -85,7 +152,7 @@ describe CoffeeTable::Cache do
|
|
85
152
|
result = @coffee_table.fetch(:test_key, SampleClass.new(2)) do
|
86
153
|
"this is a changed value"
|
87
154
|
end
|
88
|
-
@coffee_table.keys.should == ["test_key|#{md5}|sample_class[2]"]
|
155
|
+
@coffee_table.keys.should == ["test_key|#{md5}|sample_class[2]|"]
|
89
156
|
end
|
90
157
|
|
91
158
|
end
|
@@ -101,7 +168,7 @@ describe CoffeeTable::Cache do
|
|
101
168
|
"this is a changed value"
|
102
169
|
end
|
103
170
|
|
104
|
-
@coffee_table.keys.should include "test_key|#{md5}|sample_class[9938]"
|
171
|
+
@coffee_table.keys.should include "test_key|#{md5}|sample_class[9938]|"
|
105
172
|
|
106
173
|
end
|
107
174
|
it "should raise an exception if a related object does not respond_to id" do
|
@@ -111,7 +178,7 @@ describe CoffeeTable::Cache do
|
|
111
178
|
result = @coffee_table.fetch(:test_key, test_object) do
|
112
179
|
"this is a changed value"
|
113
180
|
end
|
114
|
-
}.should raise_exception
|
181
|
+
}.should raise_exception CoffeeTable::InvalidObjectError, "Objects passed in must have an id method or be a class"
|
115
182
|
|
116
183
|
end
|
117
184
|
|
@@ -124,7 +191,7 @@ describe CoffeeTable::Cache do
|
|
124
191
|
"this is a changed value"
|
125
192
|
end
|
126
193
|
|
127
|
-
@coffee_table.keys.should include "test_key|#{md5}|sample_classes"
|
194
|
+
@coffee_table.keys.should include "test_key|#{md5}|sample_classes|"
|
128
195
|
end
|
129
196
|
|
130
197
|
end
|
@@ -160,6 +227,29 @@ describe CoffeeTable::Cache do
|
|
160
227
|
result.should == "this is a changed value"
|
161
228
|
end
|
162
229
|
end
|
230
|
+
|
231
|
+
context "changing block" do
|
232
|
+
it "should not change key with same code" do
|
233
|
+
object = "object1"
|
234
|
+
@coffee_table.get_cache(:test_key) do
|
235
|
+
object
|
236
|
+
end
|
237
|
+
object = "object2"
|
238
|
+
result = @coffee_table.get_cache(:test_key) do
|
239
|
+
object
|
240
|
+
end
|
241
|
+
result.should == "object1"
|
242
|
+
end
|
243
|
+
it "should change key with changed code" do
|
244
|
+
@coffee_table.get_cache(:test_key) do
|
245
|
+
"object1"
|
246
|
+
end
|
247
|
+
result = @coffee_table.get_cache(:test_key) do
|
248
|
+
"object2"
|
249
|
+
end
|
250
|
+
result.should == "object2"
|
251
|
+
end
|
252
|
+
end
|
163
253
|
end
|
164
254
|
|
165
255
|
describe "expire_key" do
|
@@ -187,9 +277,9 @@ describe CoffeeTable::Cache do
|
|
187
277
|
"object3"
|
188
278
|
end
|
189
279
|
|
190
|
-
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}", "second_key|#{@proc_md52}", "third_key|#{@proc_md53}"].sort
|
280
|
+
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}|", "second_key|#{@proc_md52}|", "third_key|#{@proc_md53}|"].sort
|
191
281
|
@coffee_table.expire_key("second_key")
|
192
|
-
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}", "third_key|#{@proc_md53}"].sort
|
282
|
+
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}|", "third_key|#{@proc_md53}|"].sort
|
193
283
|
|
194
284
|
end
|
195
285
|
it "should not expire anything if no matches" do
|
@@ -213,9 +303,9 @@ describe CoffeeTable::Cache do
|
|
213
303
|
"object3"
|
214
304
|
end
|
215
305
|
|
216
|
-
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}", "second_key|#{@proc_md52}", "third_key|#{@proc_md53}"].sort
|
306
|
+
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}|", "second_key|#{@proc_md52}|", "third_key|#{@proc_md53}|"].sort
|
217
307
|
@coffee_table.expire_key("fourth_key")
|
218
|
-
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}", "second_key|#{@proc_md52}", "third_key|#{@proc_md53}"].sort
|
308
|
+
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}|", "second_key|#{@proc_md52}|", "third_key|#{@proc_md53}|"].sort
|
219
309
|
|
220
310
|
end
|
221
311
|
|
@@ -353,9 +443,9 @@ describe CoffeeTable::Cache do
|
|
353
443
|
"object3"
|
354
444
|
end
|
355
445
|
|
356
|
-
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}",
|
357
|
-
"second_key|#{@proc_md52}",
|
358
|
-
"third_key|#{@proc_md53}"].sort
|
446
|
+
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}|",
|
447
|
+
"second_key|#{@proc_md52}|",
|
448
|
+
"third_key|#{@proc_md53}|"].sort
|
359
449
|
|
360
450
|
end
|
361
451
|
it "should return key created with objects and ids" do
|
@@ -368,9 +458,9 @@ describe CoffeeTable::Cache do
|
|
368
458
|
@coffee_table.fetch(:third_key, @object3) do
|
369
459
|
"object3"
|
370
460
|
end
|
371
|
-
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}|sample_class[1]|sample_class[2]|sample_class[3]",
|
372
|
-
"second_key|#{@proc_md52}|sample_class[4]|sample_class[2]|sample_class[5]",
|
373
|
-
"third_key|#{@proc_md53}|sample_class[7]|sample_class[2]|sample_class[8]"].sort
|
461
|
+
@coffee_table.keys.sort.should == ["first_key|#{@proc_md51}|sample_class[1]|sample_class[2]|sample_class[3]|",
|
462
|
+
"second_key|#{@proc_md52}|sample_class[4]|sample_class[2]|sample_class[5]|",
|
463
|
+
"third_key|#{@proc_md53}|sample_class[7]|sample_class[2]|sample_class[8]|"].sort
|
374
464
|
end
|
375
465
|
|
376
466
|
end
|
data/spec/lib/key_spec.rb
CHANGED
@@ -7,16 +7,16 @@ describe CoffeeTable::Key do
|
|
7
7
|
CoffeeTable::Key.should respond_to :parse
|
8
8
|
end
|
9
9
|
it "should have a has_element? instance method" do
|
10
|
-
CoffeeTable::Key.new("name", "key").should respond_to :has_element?
|
10
|
+
CoffeeTable::Key.new("name", "key", {}).should respond_to :has_element?
|
11
11
|
end
|
12
12
|
it "should have a has_element_type? instance method" do
|
13
|
-
CoffeeTable::Key.new("name", "key").should respond_to :has_element_type?
|
13
|
+
CoffeeTable::Key.new("name", "key", {}).should respond_to :has_element_type?
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
context "parsing a string" do
|
18
18
|
it "should parse a string into its elements" do
|
19
|
-
key = CoffeeTable::Key.parse("test|asdf|sample_class")
|
19
|
+
key = CoffeeTable::Key.parse("test|asdf|sample_class|")
|
20
20
|
|
21
21
|
|
22
22
|
key.elements.count.should == 1
|
@@ -26,7 +26,7 @@ describe CoffeeTable::Key do
|
|
26
26
|
|
27
27
|
end
|
28
28
|
it "should decode encoded elements" do
|
29
|
-
key = CoffeeTable::Key.parse("te|s&t|asdf|s&|sample_|s&class")
|
29
|
+
key = CoffeeTable::Key.parse("te|s&t|asdf|s&|sample_|s&class|")
|
30
30
|
|
31
31
|
|
32
32
|
key.elements.count.should == 1
|
@@ -36,34 +36,81 @@ describe CoffeeTable::Key do
|
|
36
36
|
end
|
37
37
|
it "should encode the key data" do
|
38
38
|
|
39
|
-
key = CoffeeTable::Key.new("te|s&t", "asdf|s&", "sample_|s&class")
|
39
|
+
key = CoffeeTable::Key.new("te|s&t", "asdf|s&", {}, "sample_|s&class")
|
40
40
|
|
41
41
|
key.name.should == "te|s&t"
|
42
42
|
key.code_hash.should == "asdf|s&"
|
43
43
|
key.elements[0].should == "sample_|s&class"
|
44
44
|
|
45
|
-
key.to_s.should == "te|s&t|asdf|s&|sample_|s&class"
|
45
|
+
key.to_s.should == "te|s&t|asdf|s&|sample_|s&class|"
|
46
46
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
50
|
context "matching keys" do
|
51
51
|
it "should match a key on its name" do
|
52
|
-
key = CoffeeTable::Key.new("name", "key", "value", ["value1", "value2"])
|
52
|
+
key = CoffeeTable::Key.new("name", "key", {}, "value", ["value1", "value2"])
|
53
53
|
key.has_element?("name").should be_true
|
54
54
|
key.has_element?("key").should be_false
|
55
55
|
end
|
56
56
|
it "should match a key on its data" do
|
57
|
-
key = CoffeeTable::Key.new("name", "key", "value", ["value1", "value2"])
|
57
|
+
key = CoffeeTable::Key.new("name", "key", {}, "value", ["value1", "value2"])
|
58
58
|
key.has_element?("key").should be_false
|
59
59
|
key.has_element?("value").should be_true
|
60
60
|
key.has_element?("value1").should be_true
|
61
61
|
key.has_element?("value2").should be_true
|
62
62
|
end
|
63
63
|
it "should match a key on a class type" do
|
64
|
-
key = CoffeeTable::Key.new("name", "key", "sample_class[3]", ["value1", "value2"])
|
64
|
+
key = CoffeeTable::Key.new("name", "key", {}, "sample_class[3]", ["value1", "value2"])
|
65
65
|
key.has_element?("key").should be_false
|
66
66
|
key.has_element_type?("sample_class").should be_true
|
67
67
|
end
|
68
68
|
end
|
69
|
+
|
70
|
+
context "storing options" do
|
71
|
+
|
72
|
+
before(:each) do
|
73
|
+
@obj1 = CoffeeTable::ObjectDefinition.new(:test, 1)
|
74
|
+
@obj2 = CoffeeTable::ObjectDefinition.new(:test, 2)
|
75
|
+
@obj3 = CoffeeTable::ObjectDefinition.new(:test, 3)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should encode options into key" do
|
79
|
+
key = CoffeeTable::Key.new("name", "key", {:option => "value", :option2 => "value2"})
|
80
|
+
key.to_s.should eql "name|key|option=value&option2=value2"
|
81
|
+
end
|
82
|
+
it "should parse back options out of key" do
|
83
|
+
key = CoffeeTable::Key.parse("name|block|key=value")
|
84
|
+
key.options.should eql ({:key => "value"})
|
85
|
+
|
86
|
+
end
|
87
|
+
it "should handle no options" do
|
88
|
+
key = CoffeeTable::Key.new("name", "key", {})
|
89
|
+
key.to_s.should eql "name|key|"
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should handle one option" do
|
93
|
+
key = CoffeeTable::Key.new("name", "key", {:option => "value"})
|
94
|
+
key.to_s.should eql "name|key|option=value"
|
95
|
+
end
|
96
|
+
it "should handle multiple options" do
|
97
|
+
key = CoffeeTable::Key.new("name", "key", {:option => "value", :option2 => "value2"})
|
98
|
+
key.to_s.should eql "name|key|option=value&option2=value2"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "matches regardless of flags" do
|
102
|
+
key = CoffeeTable::Key.new("name", "key", {:option => "value", :option2 => "value2"}, @obj1, @obj2, @obj3)
|
103
|
+
key.has_element?("test[1]").should be_true
|
104
|
+
key.has_element?("test[2]").should be_true
|
105
|
+
key.has_element?("test[3]").should be_true
|
106
|
+
key.has_element?("test[4]").should be_false
|
107
|
+
end
|
108
|
+
|
109
|
+
it "does not match on flag values" do
|
110
|
+
key = CoffeeTable::Key.new("name", "key", {:option => "value", :option2 => "value2"}, @obj1, @obj2, @obj3)
|
111
|
+
key.has_element?("option=value&option2=value2").should be_false
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
69
116
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler/setup'
|
3
3
|
|
4
|
+
require 'coveralls'
|
5
|
+
Coveralls.wear!
|
6
|
+
|
4
7
|
require 'digest/md5'
|
5
8
|
require 'spork'
|
6
9
|
require 'mock_redis'
|
7
10
|
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/spec/lib/sample_class')
|
8
11
|
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/spec/lib/sample_class_without_id')
|
9
12
|
|
13
|
+
|
10
14
|
Spork.prefork do
|
11
15
|
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table.rb')
|
16
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table/block_missing_error.rb')
|
17
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table/invalid_object_error.rb')
|
12
18
|
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table/key.rb')
|
13
|
-
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table/coffee_table_block_missing_error.rb')
|
14
|
-
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table/coffee_table_invalid_object_error.rb')
|
15
19
|
end
|
16
20
|
|
17
21
|
Spork.each_run do
|
@@ -19,8 +23,8 @@ Spork.each_run do
|
|
19
23
|
config.before(:each) {
|
20
24
|
|
21
25
|
|
22
|
-
redis =
|
23
|
-
Redis.stub
|
26
|
+
redis = double(:redis)
|
27
|
+
Redis.stub(:new).and_return(MockRedis.new)
|
24
28
|
CoffeeTable::Cache.new.expire_all
|
25
29
|
|
26
30
|
}
|
@@ -30,8 +34,6 @@ Spork.each_run do
|
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
|
-
|
34
|
-
|
35
37
|
def load_sample(filename)
|
36
38
|
File.open(File.dirname(__FILE__) + "/samples/" + filename).map { |line| line}.join("\n")
|
37
39
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coffee_table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-08-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: redis
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: '0'
|
33
38
|
type: :runtime
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: rufus-scheduler
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ! '>='
|
@@ -43,10 +53,15 @@ dependencies:
|
|
43
53
|
version: '0'
|
44
54
|
type: :runtime
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
47
62
|
- !ruby/object:Gem::Dependency
|
48
63
|
name: activesupport
|
49
|
-
requirement:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
50
65
|
none: false
|
51
66
|
requirements:
|
52
67
|
- - ! '>='
|
@@ -54,10 +69,15 @@ dependencies:
|
|
54
69
|
version: '0'
|
55
70
|
type: :runtime
|
56
71
|
prerelease: false
|
57
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
58
78
|
- !ruby/object:Gem::Dependency
|
59
79
|
name: sourcify
|
60
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
61
81
|
none: false
|
62
82
|
requirements:
|
63
83
|
- - ! '>='
|
@@ -65,7 +85,28 @@ dependencies:
|
|
65
85
|
version: '0'
|
66
86
|
type: :runtime
|
67
87
|
prerelease: false
|
68
|
-
version_requirements:
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: gzip
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
69
110
|
description: rails cache gem to fragment cache with smart cache key management
|
70
111
|
email:
|
71
112
|
- stewart@theizone.co.uk
|
@@ -75,7 +116,8 @@ extra_rdoc_files: []
|
|
75
116
|
files:
|
76
117
|
- .gitignore
|
77
118
|
- .rspec
|
78
|
-
- .
|
119
|
+
- .ruby-gemset
|
120
|
+
- .ruby-version
|
79
121
|
- .travis.yml
|
80
122
|
- Gemfile
|
81
123
|
- Gemfile.lock
|
@@ -84,9 +126,10 @@ files:
|
|
84
126
|
- changelog.txt
|
85
127
|
- coffee_table.gemspec
|
86
128
|
- lib/coffee_table.rb
|
87
|
-
- lib/coffee_table/
|
88
|
-
- lib/coffee_table/
|
129
|
+
- lib/coffee_table/block_missing_error.rb
|
130
|
+
- lib/coffee_table/invalid_object_error.rb
|
89
131
|
- lib/coffee_table/key.rb
|
132
|
+
- lib/coffee_table/object_definition.rb
|
90
133
|
- lib/coffee_table/utility.rb
|
91
134
|
- lib/coffee_table/version.rb
|
92
135
|
- spec/lib/coffee_table_spec.rb
|
@@ -114,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
157
|
version: '0'
|
115
158
|
requirements: []
|
116
159
|
rubyforge_project: coffee_table
|
117
|
-
rubygems_version: 1.8.
|
160
|
+
rubygems_version: 1.8.25
|
118
161
|
signing_key:
|
119
162
|
specification_version: 3
|
120
163
|
summary: Gem to manage cache stored in redis
|
data/.rvmrc
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
rvm use 1.9.3@coffee_table
|