coffee_table 0.0.3 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ gem "redis"
7
7
  gem "awesome_print"
8
8
  gem "rufus-scheduler"
9
9
  gem "activesupport"
10
+ gem "sourcify", :git => "git@github.com:stewartmckee/sourcify.git"
10
11
 
11
12
  group :test do
12
13
  gem "rspec"
data/Gemfile.lock CHANGED
@@ -1,10 +1,21 @@
1
+ GIT
2
+ remote: git@github.com:stewartmckee/sourcify.git
3
+ revision: cdef8e0b3b7017312407000985a279d6627c77e5
4
+ specs:
5
+ sourcify (0.6.0.rc3)
6
+ file-tail (~> 1.0.10)
7
+ ruby2ruby (~> 1.3.1)
8
+ ruby_parser (~> 2.3.1)
9
+ sexp_processor (~> 3.2.0)
10
+
1
11
  PATH
2
12
  remote: .
3
13
  specs:
4
- coffee_table (0.0.2)
14
+ coffee_table (0.1.0)
5
15
  activesupport
6
16
  redis
7
17
  rufus-scheduler
18
+ sourcify
8
19
 
9
20
  GEM
10
21
  remote: http://rubygems.org/
@@ -14,6 +25,8 @@ GEM
14
25
  multi_json (~> 1.0)
15
26
  awesome_print (1.1.0)
16
27
  diff-lcs (1.1.3)
28
+ file-tail (1.0.12)
29
+ tins (~> 0.5)
17
30
  i18n (0.6.1)
18
31
  mock_redis (0.6.3)
19
32
  multi_json (1.0.4)
@@ -26,9 +39,16 @@ GEM
26
39
  rspec-expectations (2.12.1)
27
40
  diff-lcs (~> 1.1.3)
28
41
  rspec-mocks (2.12.1)
42
+ ruby2ruby (1.3.1)
43
+ ruby_parser (~> 2.0)
44
+ sexp_processor (~> 3.0)
45
+ ruby_parser (2.3.1)
46
+ sexp_processor (~> 3.0)
29
47
  rufus-scheduler (2.0.17)
30
48
  tzinfo (>= 0.3.23)
49
+ sexp_processor (3.2.0)
31
50
  spork (0.9.2)
51
+ tins (0.7.0)
32
52
  tzinfo (0.3.35)
33
53
 
34
54
  PLATFORMS
@@ -42,4 +62,5 @@ DEPENDENCIES
42
62
  redis
43
63
  rspec
44
64
  rufus-scheduler
65
+ sourcify!
45
66
  spork
data/README.textile CHANGED
@@ -1,15 +1,24 @@
1
1
 
2
- h1. CoffeeTable v0.0.3
2
+ h1. CoffeeTable v0.1.1
3
3
 
4
- !https://secure.travis-ci.org/stewartmckee/coffee_table.png?branch=master!
5
4
  !https://gemnasium.com/stewartmckee/coffee_table.png!
6
5
 
7
6
  h2. Intro
8
7
 
9
- CoffeeTable was born out of a frustration with the standard caching methods around. Maintaining the cache keys constantly was a headache and 'bet its a caching issue' was a phrase uttered way too much. CoffeeTable maintains a list of its keys in a known format and when expiry is required for an object it knows which ones to expire
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
9
 
11
10
  h3. Installation
12
11
 
12
+ h4. Using Rails/Bundler
13
+
14
+ Put the following in your Gemfile and run 'bundle'
15
+
16
+ bc. gem 'coffee_table'
17
+
18
+ h4. Straight Ruby
19
+
20
+ Run the following at the command prompt
21
+
13
22
  bc. gem install coffee_table
14
23
 
15
24
  h2. Usage
@@ -18,12 +27,13 @@ h3. CoffeeTable::Cache
18
27
 
19
28
  h4. new(options)
20
29
 
21
- Creates a new cache object. You can pass various options into this method to set configuration options for the cache.
30
+ Creates a new cache object. You can pass options into this method to modify the cache behaviour.
22
31
 
23
- * :enable_cache This defaults to true, but can be set to false to disable the cache
24
- * :redis_namespace defaults to ":coffee_table" and is set to seperate out the keys from other redis users
32
+ * :enable_cache This defaults to true, but can be set to false to disable the cache
33
+ * :redis_namespace defaults to ":coffee_table" and is set to seperate out the keys from other redis users or other caches
25
34
  * :redis_server defaults to "127.0.0.1"
26
35
  * :redis_port defaults to 6789
36
+ * :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.
27
37
 
28
38
 
29
39
  h4. get_cache(initial_key, *related_objects, &block)
@@ -42,31 +52,23 @@ bc. user_details = @coffee_table.get_cache(:user_detail, User) do
42
52
  @user.get_something_that_uses_all_users
43
53
  end
44
54
 
45
- In this case the key will be "user_detail_users", for which you can expire_for(User) which will clear regardless of the specific object id.
55
+ This would be expired with 'expire_for(User)' which will clear all user cache items regardless of the specific object id.
46
56
 
47
57
  The only required field is the first parameter, so you can create keys and cache as you normally would, ignoring the objects.
48
58
 
49
- h4. expire_key(key)
50
-
51
- This method directly expires a known key.
52
-
53
- bc. @coffee_table.expire_key("user_detail_user[1]")
54
-
55
- The above code would expire the above example of cache.
56
-
57
59
  h4. expire_all
58
60
 
59
- This method clears the whole cache.
61
+ This method clears the whole cache removing all cache items.
60
62
 
61
63
  bc. @coffee_table.expire_all
62
64
 
63
65
  h4. keys
64
66
 
65
- This is a helper method to return the list of keys currently in the system. This list is maintained when cache is created and expired.
67
+ This is a helper method to return the list of keys currently in the system. This list is maintained when cache is created and expired. Can also be used for debug purposes when investigating an issue.
66
68
 
67
69
  h4. expire_for(*objects)
68
70
 
69
- This is the main expire method. In order to expire a cache, you only need to pass in any objects that would be invalidated. With the above example this would be as follows.
71
+ This is the main expire method. In order to expire a cache item, you pass in any objects that would be invalidated. With the above example this would be as follows.
70
72
 
71
73
  bc. @coffee_table.expire_for(@user)
72
74
 
@@ -77,3 +79,7 @@ You can also expire for a whole class type
77
79
  bc. @coffee_table.expire_for(User)
78
80
 
79
81
  this would expire all keys that reference the user objects.
82
+
83
+ The best practice for this is to be as specific as you can when creating the key. Also creating more targeted cache items may be better in some situations than having one large cache fragment.
84
+
85
+
data/changelog.txt ADDED
@@ -0,0 +1,11 @@
1
+
2
+ 0.1.1
3
+ - updated documentation
4
+ - refacotred get_cache method to fetch
5
+
6
+ 0.1.0
7
+
8
+ - Started a change log
9
+ - Added sourcify to md5 hash the contents of the block to remove issues around code changes not being used due to cache
10
+ - Added a key class to handle key generation and management
11
+
data/coffee_table.gemspec CHANGED
@@ -23,5 +23,6 @@ Gem::Specification.new do |s|
23
23
  s.add_dependency "redis"
24
24
  s.add_dependency "rufus-scheduler"
25
25
  s.add_dependency "activesupport"
26
+ s.add_dependency "sourcify"
26
27
 
27
28
  end
data/lib/coffee_table.rb CHANGED
@@ -1,14 +1,18 @@
1
1
  require "coffee_table/version"
2
- require "utility"
2
+ require "coffee_table/utility"
3
3
  require "redis"
4
4
  require 'rufus/scheduler'
5
5
  require 'active_support/inflector'
6
+ require "sourcify"
7
+ require 'digest/md5'
6
8
 
7
9
  module CoffeeTable
8
10
  class Cache
9
11
 
10
12
  include CoffeeTable::Utility
11
13
 
14
+
15
+ # initialize for coffee_table. takes options to setup behaviour of cache
12
16
  def initialize(options={})
13
17
  @options = options
14
18
 
@@ -16,13 +20,15 @@ module CoffeeTable
16
20
  default_redis_namespace_to :coffee_table
17
21
  default_redis_server_to "127.0.0.1"
18
22
  default_redis_port_to 6789
23
+ default_ignore_code_changes_to false
19
24
 
20
- setup_redis
25
+ @redis = Redis.new({:server => @options[:redis_server], :port => @options[:redis_port]})
21
26
  @scheduler = Rufus::Scheduler.start_new
22
27
 
23
28
  end
24
29
 
25
- def get_cache(initial_key, *related_objects, &block)
30
+ # main
31
+ def fetch(initial_key, *related_objects, &block)
26
32
 
27
33
  raise CoffeeTableBlockMissingError, "No block given to generate cache from" unless block_given?
28
34
 
@@ -36,12 +42,14 @@ module CoffeeTable
36
42
  # check objects are valid
37
43
  related_objects.flatten.map{|o| raise CoffeeTableInvalidObjectError, "Objects passed in must have an id method or be a class" unless object_valid?(o)}
38
44
 
39
- # if first related_object is integer or fixnum it is used as an expiry time for the cache object
40
- if related_objects.empty?
41
- key = "#{initial_key}"
45
+ if @options[:ignore_code_changes]
46
+ block_key = ""
42
47
  else
43
- key = "#{initial_key}|#{related_objects.flatten.map{|o| key_for_object(o)}.join("|")}"
48
+ block_key = Digest::MD5.hexdigest(block.to_source)
44
49
  end
50
+
51
+ # 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)
45
53
 
46
54
  if @options[:enable_cache]
47
55
  if options.has_key?(:expiry)
@@ -50,9 +58,9 @@ module CoffeeTable
50
58
  expiry = nil
51
59
  end
52
60
 
53
- @redis.sadd "cache_keys", key unless @redis.sismember "cache_keys", key
54
- if @redis.exists(key)
55
- result = marshal_value(@redis.get(key))
61
+ @redis.sadd "cache_keys", key unless @redis.sismember "cache_keys", key.to_s
62
+ if @redis.exists(key.to_s)
63
+ result = marshal_value(@redis.get(key.to_s))
56
64
  else
57
65
  result = yield
58
66
  # if its a relation, call all to get an array to cache the result
@@ -60,11 +68,11 @@ module CoffeeTable
60
68
  # @logger.debug "Expanding ActiveRecord::Relation..."
61
69
  # result = result.all
62
70
  #end
63
- @redis.set key, Marshal.dump(result)
71
+ @redis.set key.to_s, Marshal.dump(result)
64
72
  unless expiry.nil?
65
- @redis.expire key, expiry
73
+ @redis.expire key.to_s, expiry
66
74
  @scheduler.in "#{expiry}s" do
67
- @redis.srem "cache_keys", key
75
+ @redis.srem "cache_keys", key.to_s
68
76
  end
69
77
  end
70
78
  end
@@ -74,9 +82,11 @@ module CoffeeTable
74
82
  result
75
83
  end
76
84
 
77
- def expire_key(key)
78
- @redis.del(key)
79
- @redis.srem "cache_keys", key
85
+ 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|
87
+ @redis.del(key.to_s)
88
+ @redis.srem "cache_keys", key.to_s
89
+ end
80
90
  end
81
91
 
82
92
  def expire_all
@@ -84,7 +94,7 @@ module CoffeeTable
84
94
  end
85
95
 
86
96
  def keys
87
- @redis.smembers "cache_keys"
97
+ @redis.smembers("cache_keys")
88
98
  end
89
99
 
90
100
  def expire_for(*objects)
@@ -99,28 +109,27 @@ module CoffeeTable
99
109
  if perform_caching
100
110
  deleted_keys = []
101
111
  unless objects.count == 0
102
- keys.each do |key|
112
+ keys.map{|k| Key.parse(k)}.each do |key|
103
113
  expire = true
104
114
  objects.each do |object|
105
- mod_key = "|#{key}|"
106
115
  if object.class == String || object.class == Symbol
107
- unless mod_key.include?("|#{object}|")
116
+ unless key.has_element?(object)
108
117
  expire = false
109
118
  end
110
119
  elsif object.class == Class
111
120
  object_type = underscore(object.to_s)
112
- unless mod_key.include?("|#{object_type}[") or mod_key.include?("|#{ActiveSupport::Inflector.pluralize(object_type)}|")
121
+ unless key.has_element_type?(object_type) || key.has_element_type?(ActiveSupport::Inflector.pluralize(object_type))
113
122
  expire = false
114
123
  end
115
124
  else
116
125
  object_type = underscore(object.class.to_s)
117
- unless mod_key.include?("|#{object_type.to_sym}[#{object.id}]|") or mod_key.include?("|#{object_type}|")
126
+ unless key.has_element?("#{object_type.to_sym}[#{object.id}]") or key.has_element?(object_type)
118
127
  expire = false
119
128
  end
120
129
  end
121
130
  end
122
131
  if expire
123
- expire_key(key)
132
+ expire_key(key.to_s)
124
133
  deleted_keys << key
125
134
  end
126
135
  end
@@ -128,6 +137,8 @@ module CoffeeTable
128
137
  deleted_keys
129
138
  end
130
139
  end
140
+
141
+ alias :get_cache :fetch
131
142
 
132
143
  private
133
144
  def marshal_value(value)
@@ -140,18 +151,9 @@ module CoffeeTable
140
151
  end
141
152
  result
142
153
  end
143
- def setup_redis
144
- @redis = Redis.new #::Namespace.new(options[:redis_namespace], :redis => Redis.new({:server => @options[:redis_server], :port => @options[:redis_port]}))
145
- end
146
154
  def object_valid?(o)
147
155
  o.respond_to?(:id) || o.class == Class
148
156
  end
149
- def key_for_object(o)
150
- if o.class == Class
151
- "#{ActiveSupport::Inflector.pluralize(underscore(o.to_s))}"
152
- else
153
- "#{underscore(o.class.to_s)}[#{o.id}]"
154
- end
155
- end
157
+
156
158
  end
157
159
  end
@@ -0,0 +1,80 @@
1
+ require "coffee_table/utility"
2
+
3
+ module CoffeeTable
4
+ class Key
5
+ include CoffeeTable::Utility
6
+
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]
11
+ key
12
+ end
13
+
14
+ def initialize(name, block_key, *objects)
15
+ @name = name
16
+ @block_key = block_key
17
+ @elements = objects.flatten.map{|o| key_for_object(o)}
18
+ end
19
+
20
+ def has_element?(element)
21
+ matches?(element.to_s)
22
+ end
23
+
24
+ def has_element_type?(element)
25
+ matches?(element.to_s + "[", :match => :start)
26
+ end
27
+
28
+ def name
29
+ @name
30
+ end
31
+
32
+ def code_hash
33
+ @block_key
34
+ end
35
+
36
+ def elements
37
+ @elements
38
+ end
39
+
40
+ def elements=(elements)
41
+ @elements = elements
42
+ end
43
+
44
+ def <=>(o)
45
+ self.to_s <=> o.to_s
46
+ end
47
+
48
+ def to_s
49
+ [encode_element(@name), encode_element(@block_key), @elements.map{|e| encode_element(e) }].flatten.join("|")
50
+ end
51
+
52
+ private
53
+
54
+ def matches?(fragment, options={})
55
+ if options[:match] == :start
56
+ @name == fragment || !@elements.select{|e| e =~ /^#{Regexp.escape(fragment)}/ }.empty?
57
+ else
58
+ @name == fragment || @elements.include?(fragment)
59
+ end
60
+ end
61
+
62
+ def encode_element(element)
63
+ element.to_s.gsub("&", "&amp;").gsub("|", "&#124;")
64
+ end
65
+
66
+ def self.decode_element(element)
67
+ element.to_s.gsub("&#124;", "|").gsub("&amp;", "&")
68
+ end
69
+
70
+ def key_for_object(o)
71
+ if o.class == Class
72
+ "#{ActiveSupport::Inflector.pluralize(underscore(o.to_s))}"
73
+ elsif o.kind_of?(String) || o.kind_of?(Symbol)
74
+ "#{underscore(o.to_s)}"
75
+ else
76
+ "#{underscore(o.class.to_s)}[#{o.id}]"
77
+ end
78
+ end
79
+ end
80
+ end
File without changes
@@ -1,3 +1,3 @@
1
1
  module CoffeeTable
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -1,13 +1,13 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
- describe CoffeeTable do
3
+ describe CoffeeTable::Cache do
4
4
 
5
5
  before(:each) do
6
6
  @coffee_table = CoffeeTable::Cache.new
7
7
  end
8
8
 
9
9
  specify { CoffeeTable::Cache.should respond_to :new}
10
- specify { @coffee_table.should respond_to :get_cache}
10
+ specify { @coffee_table.should respond_to :fetch}
11
11
  specify { @coffee_table.should respond_to :expire_key}
12
12
  specify { @coffee_table.should respond_to :expire_all}
13
13
  specify { @coffee_table.should respond_to :keys}
@@ -22,69 +22,70 @@ describe CoffeeTable do
22
22
  end
23
23
  end
24
24
 
25
- describe "get_cache" do
25
+ describe "fetch" do
26
26
  it "should raise an exception when block not given" do
27
- lambda{@coffee_table.get_cache("asdf")}.should raise_exception CoffeeTableBlockMissingError
27
+ lambda{@coffee_table.fetch("asdf")}.should raise_exception CoffeeTableBlockMissingError
28
28
  end
29
29
  it "should execute block when cache value not available" do
30
- result = @coffee_table.get_cache("asdf") do
30
+ result = @coffee_table.fetch("asdf") do
31
31
  "this is a value"
32
32
  end
33
33
 
34
34
  result.should == "this is a value"
35
35
  end
36
36
  it "should return cached value when cache available" do
37
- @coffee_table.get_cache("asdf") do
38
- "this is a value"
37
+ value = "this is a value"
38
+ @coffee_table.fetch("asdf") do
39
+ value
39
40
  end
40
- result = @coffee_table.get_cache("asdf") do
41
- "this is a changed value"
41
+ value = "this is a changed value"
42
+ result = @coffee_table.fetch("asdf") do
43
+ value
42
44
  end
43
45
 
44
46
  result.should == "this is a value"
45
47
 
46
- end
47
- it "should execute block when store not available" do
48
- @coffee_table.get_cache(:test_key) do
49
- TESTVAR = "testvar"
50
- "this is a value"
51
- end
52
- @coffee_table.get_cache(:test_key) do
53
- TESTVAR = "testvar2"
54
- "this is a value"
55
- end
56
-
57
- TESTVAR.should == "testvar"
58
-
59
48
  end
60
49
 
61
50
  context "keys" do
62
51
  it "should create a key with just the initial key" do
63
- result = @coffee_table.get_cache(:test_key) do
52
+ md5 = md5_block do
64
53
  "this is a changed value"
65
54
  end
66
- @coffee_table.keys.should == ["test_key"]
55
+ result = @coffee_table.fetch(:test_key) do
56
+ "this is a changed value"
57
+ end
58
+ @coffee_table.keys.should == ["test_key|#{md5}"]
67
59
  end
68
60
 
69
61
  it "should create key from class" do
70
- result = @coffee_table.get_cache(:test_key, SampleClass) do
62
+ md5 = md5_block do
71
63
  "this is a changed value"
72
64
  end
73
- @coffee_table.keys.should == ["test_key|sample_classes"]
65
+ result = @coffee_table.fetch(:test_key, SampleClass) do
66
+ "this is a changed value"
67
+ end
68
+ @coffee_table.keys.should == ["test_key|#{md5}|sample_classes"]
74
69
  end
75
70
 
76
71
  it "should use class name for keys" do
77
- result = @coffee_table.get_cache(:test_key, SampleClass.new(2)) do
72
+ md5 = md5_block do
73
+ "this is a changed value"
74
+ end
75
+ result = @coffee_table.fetch(:test_key, SampleClass.new(2)) do
78
76
  "this is a changed value"
79
77
  end
80
- @coffee_table.keys.should == ["test_key|sample_class[2]"]
78
+ @coffee_table.keys.should == ["test_key|#{md5}|sample_class[2]"]
81
79
  end
82
80
 
83
81
  it "should use id from class in key" do
84
- result = @coffee_table.get_cache(:test_key, SampleClass.new(2)) do
82
+ md5 = md5_block do
83
+ "this is a changed value"
84
+ end
85
+ result = @coffee_table.fetch(:test_key, SampleClass.new(2)) do
85
86
  "this is a changed value"
86
87
  end
87
- @coffee_table.keys.should == ["test_key|sample_class[2]"]
88
+ @coffee_table.keys.should == ["test_key|#{md5}|sample_class[2]"]
88
89
  end
89
90
 
90
91
  end
@@ -93,18 +94,21 @@ describe CoffeeTable do
93
94
  context "with related objects" do
94
95
  it "should create a key from the id's of the related objects" do
95
96
  test_object = SampleClass.new(9938)
96
- result = @coffee_table.get_cache(:test_key, test_object) do
97
+ md5 = md5_block do
98
+ "this is a changed value"
99
+ end
100
+ result = @coffee_table.fetch(:test_key, test_object) do
97
101
  "this is a changed value"
98
102
  end
99
103
 
100
- @coffee_table.keys.should include "test_key|sample_class[9938]"
104
+ @coffee_table.keys.should include "test_key|#{md5}|sample_class[9938]"
101
105
 
102
106
  end
103
107
  it "should raise an exception if a related object does not respond_to id" do
104
108
  test_object = SampleClassWithoutId.new
105
109
 
106
110
  lambda {
107
- result = @coffee_table.get_cache(:test_key, test_object) do
111
+ result = @coffee_table.fetch(:test_key, test_object) do
108
112
  "this is a changed value"
109
113
  end
110
114
  }.should raise_exception CoffeeTableInvalidObjectError, "Objects passed in must have an id method or be a class"
@@ -112,18 +116,21 @@ describe CoffeeTable do
112
116
  end
113
117
 
114
118
  it "should create a universal key if the objects passed in are an uninitialised class" do
119
+ md5 = md5_block do
120
+ "this is a changed value"
121
+ end
115
122
 
116
- result = @coffee_table.get_cache(:test_key, SampleClass) do
123
+ result = @coffee_table.fetch(:test_key, SampleClass) do
117
124
  "this is a changed value"
118
125
  end
119
126
 
120
- @coffee_table.keys.should include "test_key|sample_classes"
127
+ @coffee_table.keys.should include "test_key|#{md5}|sample_classes"
121
128
  end
122
129
 
123
130
  end
124
131
  context "with expiry" do
125
132
  it "keys should update when cache expires" do
126
- @coffee_table.get_cache(:test_key, :expiry => 0.2) do
133
+ @coffee_table.fetch(:test_key, :expiry => 0.2) do
127
134
  "object1"
128
135
  end
129
136
  @coffee_table.keys.count.should == 1
@@ -131,21 +138,23 @@ describe CoffeeTable do
131
138
  @coffee_table.keys.count.should == 0
132
139
  end
133
140
  it "should not execute block during cache period" do
134
- @coffee_table.get_cache("asdf", :expiry => 1) do
135
- "this is a value"
141
+ value = 'this is a value'
142
+ @coffee_table.fetch("asdf", :expiry => 1) do
143
+ value
136
144
  end
137
- result = @coffee_table.get_cache("asdf") do
138
- "this is a changed value"
145
+ value = 'this is a changed value'
146
+ result = @coffee_table.fetch("asdf") do
147
+ value
139
148
  end
140
149
  result.should == "this is a value"
141
150
 
142
151
  end
143
152
  it "should execute block and return value when cache has expired" do
144
- @coffee_table.get_cache("asdf", :expiry => 1) do
153
+ @coffee_table.fetch("asdf", :expiry => 1) do
145
154
  "this is a value"
146
155
  end
147
156
  sleep 2
148
- result = @coffee_table.get_cache("asdf") do
157
+ result = @coffee_table.fetch("asdf") do
149
158
  "this is a changed value"
150
159
  end
151
160
  result.should == "this is a changed value"
@@ -154,38 +163,129 @@ describe CoffeeTable do
154
163
  end
155
164
 
156
165
  describe "expire_key" do
166
+
167
+ before(:each) do
168
+ @proc_md51 = md5_block do
169
+ "object1"
170
+ end
171
+ @proc_md52 = md5_block do
172
+ "object2"
173
+ end
174
+ @proc_md53 = md5_block do
175
+ "object3"
176
+ end
177
+ end
178
+
157
179
  it "should expire the specified key" do
158
- @coffee_table.get_cache(:first_key) do
180
+ @coffee_table.fetch(:first_key) do
159
181
  "object1"
160
182
  end
161
- @coffee_table.get_cache(:second_key) do
183
+ @coffee_table.fetch(:second_key) do
162
184
  "object2"
163
185
  end
164
- @coffee_table.get_cache(:third_key) do
186
+ @coffee_table.fetch(:third_key) do
165
187
  "object3"
166
188
  end
167
189
 
168
- @coffee_table.keys.sort.should == ["first_key", "second_key", "third_key"].sort
190
+ @coffee_table.keys.sort.should == ["first_key|#{@proc_md51}", "second_key|#{@proc_md52}", "third_key|#{@proc_md53}"].sort
169
191
  @coffee_table.expire_key("second_key")
170
- @coffee_table.keys.sort.should == ["first_key", "third_key"].sort
192
+ @coffee_table.keys.sort.should == ["first_key|#{@proc_md51}", "third_key|#{@proc_md53}"].sort
171
193
 
172
194
  end
173
195
  it "should not expire anything if no matches" do
174
- @coffee_table.get_cache(:first_key) do
196
+ @proc_md51 = md5_block do
175
197
  "object1"
176
198
  end
177
- @coffee_table.get_cache(:second_key) do
199
+ @proc_md52 = md5_block do
178
200
  "object2"
179
201
  end
180
- @coffee_table.get_cache(:third_key) do
202
+ @proc_md53 = md5_block do
181
203
  "object3"
182
204
  end
183
205
 
184
- @coffee_table.keys.sort.should == ["first_key", "second_key", "third_key"].sort
206
+ @coffee_table.fetch(:first_key) do
207
+ "object1"
208
+ end
209
+ @coffee_table.fetch(:second_key) do
210
+ "object2"
211
+ end
212
+ @coffee_table.fetch(:third_key) do
213
+ "object3"
214
+ end
215
+
216
+ @coffee_table.keys.sort.should == ["first_key|#{@proc_md51}", "second_key|#{@proc_md52}", "third_key|#{@proc_md53}"].sort
185
217
  @coffee_table.expire_key("fourth_key")
186
- @coffee_table.keys.sort.should == ["first_key", "second_key", "third_key"].sort
218
+ @coffee_table.keys.sort.should == ["first_key|#{@proc_md51}", "second_key|#{@proc_md52}", "third_key|#{@proc_md53}"].sort
187
219
 
188
220
  end
221
+
222
+ end
223
+
224
+ context "monitoring code changes in block" do
225
+ context "changed block" do
226
+
227
+ it "should invalidate cache when block has changed" do
228
+ @coffee_table.fetch(:test_key) do
229
+ "object1"
230
+ end
231
+
232
+ result = @coffee_table.fetch(:test_key) do
233
+ "object2"
234
+ end
235
+
236
+ result.should == "object2"
237
+ end
238
+
239
+ it "should not invalidate block when block has not changed" do
240
+ object = "object1"
241
+ @coffee_table.fetch(:test_key) do
242
+ object
243
+ end
244
+
245
+ object = "object2"
246
+ result = @coffee_table.fetch(:test_key) do
247
+ object
248
+ end
249
+
250
+ result.should == "object1"
251
+ end
252
+
253
+ it "should not be affected by whitespace only changes" do
254
+ object = "object1"
255
+ @coffee_table.fetch(:test_key) do
256
+ object
257
+ end
258
+
259
+ object = "object2"
260
+ result = @coffee_table.fetch(:test_key) do
261
+
262
+ object
263
+
264
+ end
265
+
266
+ result.should == "object1"
267
+ end
268
+
269
+ end
270
+
271
+ context "ignoring code changes" do
272
+
273
+ before(:each) do
274
+ @coffee_table = CoffeeTable::Cache.new(:ignore_code_changes => true)
275
+ end
276
+
277
+ it "should not invalidate cache when block has changed" do
278
+ @coffee_table.fetch(:test_key) do
279
+ "object1"
280
+ end
281
+
282
+ result = @coffee_table.fetch(:test_key) do
283
+ "object2"
284
+ end
285
+
286
+ result.should == "object1"
287
+ end
288
+ end
189
289
  end
190
290
 
191
291
  describe "expire_all" do
@@ -195,13 +295,13 @@ describe CoffeeTable do
195
295
  object2 = [SampleClass.new(4), SampleClass.new(2), SampleClass.new(5)]
196
296
  object3 = [SampleClass.new(7), SampleClass.new(2), SampleClass.new(8)]
197
297
 
198
- @coffee_table.get_cache(:first_key) do
298
+ @coffee_table.fetch(:first_key) do
199
299
  "object1"
200
300
  end
201
- @coffee_table.get_cache(:second_key) do
301
+ @coffee_table.fetch(:second_key) do
202
302
  "object2"
203
303
  end
204
- @coffee_table.get_cache(:third_key) do
304
+ @coffee_table.fetch(:third_key) do
205
305
  "object3"
206
306
  end
207
307
  end
@@ -211,7 +311,7 @@ describe CoffeeTable do
211
311
  @coffee_table.expire_all
212
312
  @coffee_table.keys.count.should == 0
213
313
 
214
- result = @coffee_table.get_cache(:first_key) do
314
+ result = @coffee_table.fetch(:first_key) do
215
315
  "changed value"
216
316
  end
217
317
 
@@ -226,6 +326,16 @@ describe CoffeeTable do
226
326
  @object2 = [SampleClass.new(4), SampleClass.new(2), SampleClass.new(5)]
227
327
  @object3 = [SampleClass.new(7), SampleClass.new(2), SampleClass.new(8)]
228
328
 
329
+ @proc_md51 = md5_block do
330
+ "object1"
331
+ end
332
+ @proc_md52 = md5_block do
333
+ "object2"
334
+ end
335
+ @proc_md53 = md5_block do
336
+ "object3"
337
+ end
338
+
229
339
  end
230
340
 
231
341
  it "should return an array of string" do
@@ -233,34 +343,34 @@ describe CoffeeTable do
233
343
  @coffee_table.keys.map{|key| key.should be_an_instance_of String}
234
344
  end
235
345
  it "should return key created without objects" do
236
- @coffee_table.get_cache(:first_key) do
346
+ @coffee_table.fetch(:first_key) do
237
347
  "object1"
238
348
  end
239
- @coffee_table.get_cache(:second_key) do
349
+ @coffee_table.fetch(:second_key) do
240
350
  "object2"
241
351
  end
242
- @coffee_table.get_cache(:third_key) do
352
+ @coffee_table.fetch(:third_key) do
243
353
  "object3"
244
354
  end
245
355
 
246
- @coffee_table.keys.sort.should == ["first_key",
247
- "second_key",
248
- "third_key"].sort
356
+ @coffee_table.keys.sort.should == ["first_key|#{@proc_md51}",
357
+ "second_key|#{@proc_md52}",
358
+ "third_key|#{@proc_md53}"].sort
249
359
 
250
360
  end
251
361
  it "should return key created with objects and ids" do
252
- @coffee_table.get_cache(:first_key, @object1) do
362
+ @coffee_table.fetch(:first_key, @object1) do
253
363
  "object1"
254
364
  end
255
- @coffee_table.get_cache(:second_key, @object2) do
365
+ @coffee_table.fetch(:second_key, @object2) do
256
366
  "object2"
257
367
  end
258
- @coffee_table.get_cache(:third_key, @object3) do
368
+ @coffee_table.fetch(:third_key, @object3) do
259
369
  "object3"
260
370
  end
261
- @coffee_table.keys.sort.should == ["first_key|sample_class[1]|sample_class[2]|sample_class[3]",
262
- "second_key|sample_class[4]|sample_class[2]|sample_class[5]",
263
- "third_key|sample_class[7]|sample_class[2]|sample_class[8]"].sort
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
264
374
  end
265
375
 
266
376
  end
@@ -271,13 +381,13 @@ describe CoffeeTable do
271
381
  object2 = [SampleClass.new(4), SampleClass.new(2), SampleClass.new(5)]
272
382
  object3 = [SampleClass.new(7), SampleClass.new(2), SampleClass.new(8)]
273
383
 
274
- @coffee_table.get_cache(:first_key, object1) do
384
+ @coffee_table.fetch(:first_key, object1) do
275
385
  "object1"
276
386
  end
277
- @coffee_table.get_cache(:second_key, object2) do
387
+ @coffee_table.fetch(:second_key, object2) do
278
388
  "object2"
279
389
  end
280
- @coffee_table.get_cache(:third_key, object3) do
390
+ @coffee_table.fetch(:third_key, object3) do
281
391
  "object3"
282
392
  end
283
393
  end
@@ -318,7 +428,7 @@ describe CoffeeTable do
318
428
  end
319
429
 
320
430
  it "should expire all keys relating to a class if uninitialised class is passed in" do
321
- @coffee_table.get_cache(:fourth_key) do
431
+ @coffee_table.fetch(:fourth_key) do
322
432
  "object4"
323
433
  end
324
434
  @coffee_table.keys.count.should == 4
@@ -0,0 +1,69 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe CoffeeTable::Key do
4
+
5
+ context "has correct methods" do
6
+ it "should have a parse class method" do
7
+ CoffeeTable::Key.should respond_to :parse
8
+ end
9
+ it "should have a has_element? instance method" do
10
+ CoffeeTable::Key.new("name", "key").should respond_to :has_element?
11
+ end
12
+ it "should have a has_element_type? instance method" do
13
+ CoffeeTable::Key.new("name", "key").should respond_to :has_element_type?
14
+ end
15
+ end
16
+
17
+ context "parsing a string" do
18
+ it "should parse a string into its elements" do
19
+ key = CoffeeTable::Key.parse("test|asdf|sample_class")
20
+
21
+
22
+ key.elements.count.should == 1
23
+ key.name.should == "test"
24
+ key.code_hash.should == "asdf"
25
+ key.elements[0].should == "sample_class"
26
+
27
+ end
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")
30
+
31
+
32
+ key.elements.count.should == 1
33
+ key.name.should == "te|s&t"
34
+ key.code_hash.should == "asdf|s&"
35
+ key.elements[0].should == "sample_|s&class"
36
+ end
37
+ it "should encode the key data" do
38
+
39
+ key = CoffeeTable::Key.new("te|s&t", "asdf|s&", "sample_|s&class")
40
+
41
+ key.name.should == "te|s&t"
42
+ key.code_hash.should == "asdf|s&"
43
+ key.elements[0].should == "sample_|s&class"
44
+
45
+ key.to_s.should == "te&#124;s&amp;t|asdf&#124;s&amp;|sample_&#124;s&amp;class"
46
+
47
+ end
48
+ end
49
+
50
+ context "matching keys" do
51
+ it "should match a key on its name" do
52
+ key = CoffeeTable::Key.new("name", "key", "value", ["value1", "value2"])
53
+ key.has_element?("name").should be_true
54
+ key.has_element?("key").should be_false
55
+ end
56
+ it "should match a key on its data" do
57
+ key = CoffeeTable::Key.new("name", "key", "value", ["value1", "value2"])
58
+ key.has_element?("key").should be_false
59
+ key.has_element?("value").should be_true
60
+ key.has_element?("value1").should be_true
61
+ key.has_element?("value2").should be_true
62
+ end
63
+ it "should match a key on a class type" do
64
+ key = CoffeeTable::Key.new("name", "key", "sample_class[3]", ["value1", "value2"])
65
+ key.has_element?("key").should be_false
66
+ key.has_element_type?("sample_class").should be_true
67
+ end
68
+ end
69
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'digest/md5'
2
5
  require 'spork'
3
6
  require 'mock_redis'
4
7
  require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/spec/lib/sample_class')
@@ -6,6 +9,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/spec/lib/
6
9
 
7
10
  Spork.prefork do
8
11
  require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table.rb')
12
+ require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table/key.rb')
9
13
  require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table/coffee_table_block_missing_error.rb')
10
14
  require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table/coffee_table_invalid_object_error.rb')
11
15
  end
@@ -35,3 +39,6 @@ def load_binary_sample(filename)
35
39
  File.open(File.dirname(__FILE__) + "/samples/" + filename, 'rb')
36
40
  end
37
41
 
42
+ def md5_block(&block)
43
+ Digest::MD5.hexdigest(block.to_source)
44
+ 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.0.3
4
+ version: 0.1.1
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-01-17 00:00:00.000000000 Z
12
+ date: 2013-02-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70181746090020 !ruby/object:Gem::Requirement
16
+ requirement: &70278554140820 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70181746090020
24
+ version_requirements: *70278554140820
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: redis
27
- requirement: &70181746089420 !ruby/object:Gem::Requirement
27
+ requirement: &70278554140260 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70181746089420
35
+ version_requirements: *70278554140260
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rufus-scheduler
38
- requirement: &70181746088880 !ruby/object:Gem::Requirement
38
+ requirement: &70278554139760 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70181746088880
46
+ version_requirements: *70278554139760
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: activesupport
49
- requirement: &70181746088280 !ruby/object:Gem::Requirement
49
+ requirement: &70278554139200 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,18 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70181746088280
57
+ version_requirements: *70278554139200
58
+ - !ruby/object:Gem::Dependency
59
+ name: sourcify
60
+ requirement: &70278554138660 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70278554138660
58
69
  description: rails cache gem to fragment cache with smart cache key management
59
70
  email:
60
71
  - stewart@theizone.co.uk
@@ -70,13 +81,16 @@ files:
70
81
  - Gemfile.lock
71
82
  - README.textile
72
83
  - Rakefile
84
+ - changelog.txt
73
85
  - coffee_table.gemspec
74
86
  - lib/coffee_table.rb
75
87
  - lib/coffee_table/coffee_table_block_missing_error.rb
76
88
  - lib/coffee_table/coffee_table_invalid_object_error.rb
89
+ - lib/coffee_table/key.rb
90
+ - lib/coffee_table/utility.rb
77
91
  - lib/coffee_table/version.rb
78
- - lib/utility.rb
79
92
  - spec/lib/coffee_table_spec.rb
93
+ - spec/lib/key_spec.rb
80
94
  - spec/lib/sample_class.rb
81
95
  - spec/lib/sample_class_without_id.rb
82
96
  - spec/spec_helper.rb
@@ -106,6 +120,7 @@ specification_version: 3
106
120
  summary: Gem to manage cache stored in redis
107
121
  test_files:
108
122
  - spec/lib/coffee_table_spec.rb
123
+ - spec/lib/key_spec.rb
109
124
  - spec/lib/sample_class.rb
110
125
  - spec/lib/sample_class_without_id.rb
111
126
  - spec/spec_helper.rb