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 CHANGED
@@ -1,3 +1,4 @@
1
1
  *.gem
2
2
  .bundle
3
3
  pkg/*
4
+ coverage
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
- end
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.0)
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.11)
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
- diff-lcs (1.1.3)
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
- i18n (0.6.1)
31
- mock_redis (0.6.3)
32
- multi_json (1.0.4)
33
- redis (3.0.2)
34
- rspec (2.12.0)
35
- rspec-core (~> 2.12.0)
36
- rspec-expectations (~> 2.12.0)
37
- rspec-mocks (~> 2.12.0)
38
- rspec-core (2.12.2)
39
- rspec-expectations (2.12.1)
40
- diff-lcs (~> 1.1.3)
41
- rspec-mocks (2.12.1)
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.17)
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
- tins (0.7.0)
52
- tzinfo (0.3.35)
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. get_cache(initial_key, *related_objects, &block)
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.get_cache(:user_detail, @user, :expiry => 600) do
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, assuming the @user.id is '1' the cache key would be "user_detail_user[1]". The more objects the longer the key. 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.
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.get_cache(:user_detail, User) do
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
- - refacotred get_cache method to fetch
10
+ - refactored get_cache method to fetch
5
11
 
6
12
  0.1.0
7
13
 
data/coffee_table.gemspec CHANGED
@@ -24,5 +24,6 @@ Gem::Specification.new do |s|
24
24
  s.add_dependency "rufus-scheduler"
25
25
  s.add_dependency "activesupport"
26
26
  s.add_dependency "sourcify"
27
+ s.add_dependency "gzip"
27
28
 
28
29
  end
@@ -0,0 +1,4 @@
1
+ module CoffeeTable
2
+ class BlockMissingError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module CoffeeTable
2
+ class InvalidObjectError < StandardError
3
+
4
+ end
5
+ end
@@ -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..-1]
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
@@ -1,3 +1,3 @@
1
1
  module CoffeeTable
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
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 "sourcify"
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 6789
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
- # main
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 CoffeeTableInvalidObjectError, "Objects passed in must have an id method or be a class" unless object_valid?(o)}
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
- result = marshal_value(@redis.get(key.to_s))
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
- # if its a relation, call all to get an array to cache the result
67
- #if result.class == ActiveRecord::Relation
68
- # @logger.debug "Expanding ActiveRecord::Relation..."
69
- # result = result.all
70
- #end
71
- @redis.set key.to_s, Marshal.dump(result)
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 CoffeeTableBlockMissingError
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 CoffeeTableBlockMissingError
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 CoffeeTableInvalidObjectError, "Objects passed in must have an id method or be a class"
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&#124;s&amp;t|asdf&#124;s&amp;|sample_&#124;s&amp;class")
29
+ key = CoffeeTable::Key.parse("te&#124;s&amp;t|asdf&#124;s&amp;|sample_&#124;s&amp;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&#124;s&amp;t|asdf&#124;s&amp;|sample_&#124;s&amp;class"
45
+ key.to_s.should == "te&#124;s&amp;t|asdf&#124;s&amp;|sample_&#124;s&amp;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&amp;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&amp;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&amp;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 = mock(:redis)
23
- Redis.stub!(:new).and_return(MockRedis.new)
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.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-02-06 00:00:00.000000000 Z
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: &70278554140820 !ruby/object:Gem::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: *70278554140820
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: &70278554140260 !ruby/object:Gem::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: *70278554140260
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: &70278554139760 !ruby/object:Gem::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: *70278554139760
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: &70278554139200 !ruby/object:Gem::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: *70278554139200
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: &70278554138660 !ruby/object:Gem::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: *70278554138660
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
- - .rvmrc
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/coffee_table_block_missing_error.rb
88
- - lib/coffee_table/coffee_table_invalid_object_error.rb
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.10
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
@@ -1,3 +0,0 @@
1
- class CoffeeTableBlockMissingError < StandardError
2
-
3
- end
@@ -1,3 +0,0 @@
1
- class CoffeeTableInvalidObjectError < StandardError
2
-
3
- end