coffee_table 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +13 -0
- data/Rakefile +1 -0
- data/coffee_table.gemspec +24 -0
- data/lib/coffee_table/version.rb +3 -0
- data/lib/coffee_table.rb +128 -0
- data/lib/utility.rb +23 -0
- data/spec/lib/coffee_table_spec.rb +117 -0
- data/spec/lib/sample_class.rb +5 -0
- data/spec/lib/sample_class_without_id.rb +2 -0
- data/spec/spec_helper.rb +35 -0
- metadata +62 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@coffee_table
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "coffee_table/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "coffee_table"
|
7
|
+
s.version = CoffeeTable::VERSION
|
8
|
+
s.authors = ["Stewart McKee"]
|
9
|
+
s.email = ["stewart@theizone.co.uk"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = "Gem to manage cache stored in redis"
|
12
|
+
s.description = "rails cache gem to fragment cache with smart cache key management"
|
13
|
+
|
14
|
+
s.rubyforge_project = "coffee_table"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
data/lib/coffee_table.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require "coffee_table/version"
|
2
|
+
require "utility"
|
3
|
+
require "redis"
|
4
|
+
|
5
|
+
module CoffeeTable
|
6
|
+
class Cache
|
7
|
+
|
8
|
+
include CoffeeTable::Utility
|
9
|
+
|
10
|
+
def initialize(options={})
|
11
|
+
@options = options
|
12
|
+
|
13
|
+
default_enable_cache_to true
|
14
|
+
default_redis_namespace_to :coffee_table
|
15
|
+
default_redis_server_to "127.0.0.1"
|
16
|
+
default_redis_port_to 6789
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_cache(initial_key, *related_objects, &block)
|
21
|
+
setup_redis
|
22
|
+
|
23
|
+
# check objects are valid
|
24
|
+
related_objects.map{|o| raise "Objects passed in must have an id method" unless o.respond_to? "id"}
|
25
|
+
|
26
|
+
# if first related_object is integer or fixnum it is used as an expiry time for the cache object
|
27
|
+
# TODO change this to use hash and be at the end as convention
|
28
|
+
if related_objects.empty?
|
29
|
+
key = "#{initial_key}"
|
30
|
+
else
|
31
|
+
key = "#{initial_key}_#{related_objects.flatten.map{|o| "#{underscore(o.class.to_s)}[#{o.id}]"}.join("_")}"
|
32
|
+
end
|
33
|
+
|
34
|
+
if @options[:enable_cache]
|
35
|
+
# if first element is an integer or fixnum then use that as an expiry for this key
|
36
|
+
expiry = nil
|
37
|
+
if !related_objects.nil? and !related_objects.empty? and !related_objects.first.nil? and (related_objects.first.instance_of? Integer or related_objects.first.instance_of? Fixnum)
|
38
|
+
expiry = related_objects[0].to_i
|
39
|
+
related_objects = related_objects[1..-1]
|
40
|
+
end
|
41
|
+
|
42
|
+
@redis.sadd "cache_keys", key unless @redis.sismember "cache_keys", key
|
43
|
+
if @redis.exists(key)
|
44
|
+
result = marshal_value(@redis.get(key))
|
45
|
+
else
|
46
|
+
result = yield
|
47
|
+
# if its a relation, call all to get an array to cache the result
|
48
|
+
#if result.class == ActiveRecord::Relation
|
49
|
+
# @logger.debug "Expanding ActiveRecord::Relation..."
|
50
|
+
# result = result.all
|
51
|
+
#end
|
52
|
+
@redis.set key, Marshal.dump(result)
|
53
|
+
@redis.expire key expiry unless expiry.nil?
|
54
|
+
end
|
55
|
+
else
|
56
|
+
unless @options[:enable_cache] && block_given?
|
57
|
+
raise "cache is disabled and no block is given"
|
58
|
+
else
|
59
|
+
result = yield
|
60
|
+
end
|
61
|
+
end
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
def expire_key(key)
|
66
|
+
setup_redis
|
67
|
+
@redis.del(key)
|
68
|
+
@redis.srem "cache_keys", key
|
69
|
+
end
|
70
|
+
|
71
|
+
def expire_all
|
72
|
+
keys.map{|key| expire_key(key)}
|
73
|
+
end
|
74
|
+
|
75
|
+
def keys
|
76
|
+
setup_redis
|
77
|
+
@redis.smembers "cache_keys"
|
78
|
+
end
|
79
|
+
|
80
|
+
def expire_for(*objects)
|
81
|
+
perform_caching = Rails.application.configure do
|
82
|
+
config.action_controller.perform_caching
|
83
|
+
end
|
84
|
+
|
85
|
+
if perform_caching
|
86
|
+
deleted_keys = []
|
87
|
+
unless objects.empty?
|
88
|
+
self.keys.each do |key|
|
89
|
+
expire = true
|
90
|
+
objects.each do |object|
|
91
|
+
mod_key = "_#{key}_"
|
92
|
+
if object.class == String
|
93
|
+
unless mod_key.include?("_#{object}_") or mod_key.include?("_#{object.pluralize}_")
|
94
|
+
expire = false
|
95
|
+
end
|
96
|
+
else
|
97
|
+
object_type = object.class.to_s.underscore
|
98
|
+
unless mod_key.include?("_#{object_type.to_sym}[#{object.id}]_") or mod_key.include?("_#{object_type.pluralize}_")
|
99
|
+
expire = false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
if expire
|
104
|
+
expire_key(key)
|
105
|
+
deleted_keys << key
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
deleted_keys
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
def marshal_value(value)
|
115
|
+
begin
|
116
|
+
result = Marshal.load(value)
|
117
|
+
rescue ArgumentError => e
|
118
|
+
puts "Attempting to load class/module #{e.message.split(" ")[-1]}"
|
119
|
+
e.message.split(" ")[-1].constantize
|
120
|
+
result = marshal_value(value)
|
121
|
+
end
|
122
|
+
result
|
123
|
+
end
|
124
|
+
def setup_redis
|
125
|
+
@redis = Redis.new #::Namespace.new(options[:redis_namespace], :redis => Redis.new({:server => @options[:redis_server], :port => @options[:redis_port]}))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/utility.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module CoffeeTable
|
2
|
+
module Utility
|
3
|
+
|
4
|
+
|
5
|
+
# used for setting default options
|
6
|
+
def method_missing(method_sym, *arguments, &block)
|
7
|
+
if method_sym.to_s =~ /^default_(.*)_to$/
|
8
|
+
tag_name = method_sym.to_s.split("_")[1..-2].join("_").to_sym
|
9
|
+
@options[tag_name] = arguments[0] unless @options.has_key?(tag_name)
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def underscore(text)
|
16
|
+
text.gsub(/::/, '/').
|
17
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
18
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
19
|
+
tr("-", "_").
|
20
|
+
downcase
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe CoffeeTable do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@coffee_table = CoffeeTable::Cache.new
|
7
|
+
end
|
8
|
+
|
9
|
+
specify { CoffeeTable::Cache.should respond_to :new}
|
10
|
+
specify { @coffee_table.should respond_to :get_cache}
|
11
|
+
specify { @coffee_table.should respond_to :expire_key}
|
12
|
+
specify { @coffee_table.should respond_to :expire_all}
|
13
|
+
specify { @coffee_table.should respond_to :keys}
|
14
|
+
specify { @coffee_table.should respond_to :expire_for}
|
15
|
+
|
16
|
+
describe "config" do
|
17
|
+
it "should take a hash for config" do
|
18
|
+
CoffeeTable::Cache.new({:test => "asdf"})
|
19
|
+
end
|
20
|
+
it "should not raise exception when hash not given" do
|
21
|
+
lambda{CoffeeTable::Cache.new}.should_not raise_exception
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "get_cache" do
|
26
|
+
it "should raise an exception when block not given" do
|
27
|
+
lambda{@coffee_table.get_cache("asdf")}.should raise_exception "no block given (yield)"
|
28
|
+
end
|
29
|
+
it "should not raise an exception if cache value is available and no block given" do
|
30
|
+
result = @coffee_table.get_cache("test_key") do
|
31
|
+
"this is a valid result"
|
32
|
+
end
|
33
|
+
|
34
|
+
@coffee_table.get_cache("test_key").should == "this is a valid result"
|
35
|
+
end
|
36
|
+
it "should execute block when cache value not available" do
|
37
|
+
result = @coffee_table.get_cache("asdf") do
|
38
|
+
"this is a value"
|
39
|
+
end
|
40
|
+
|
41
|
+
result.should == "this is a value"
|
42
|
+
end
|
43
|
+
it "should return cached value when cache available" do
|
44
|
+
@coffee_table.get_cache("asdf") do
|
45
|
+
"this is a value"
|
46
|
+
end
|
47
|
+
result = @coffee_table.get_cache("asdf") do
|
48
|
+
"this is a changed value"
|
49
|
+
end
|
50
|
+
|
51
|
+
result.should == "this is a value"
|
52
|
+
|
53
|
+
end
|
54
|
+
it "should execute block when store not available" do
|
55
|
+
@coffee_table.get_cache(:test_key) do
|
56
|
+
TESTVAR = "testvar"
|
57
|
+
"this is a value"
|
58
|
+
end
|
59
|
+
@coffee_table.get_cache(:test_key) do
|
60
|
+
TESTVAR = "testvar2"
|
61
|
+
"this is a value"
|
62
|
+
end
|
63
|
+
|
64
|
+
TESTVAR.should == "testvar"
|
65
|
+
|
66
|
+
end
|
67
|
+
context "without related objects" do
|
68
|
+
it "should create a key with just the initial key" do
|
69
|
+
result = @coffee_table.get_cache(:test_key) do
|
70
|
+
"this is a changed value"
|
71
|
+
end
|
72
|
+
|
73
|
+
@coffee_table.keys.should include "test_key"
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
context "with related objects" do
|
78
|
+
it "should create a key from the id's of the related objects" do
|
79
|
+
test_object = SampleClass.new
|
80
|
+
result = @coffee_table.get_cache(:test_key, test_object) do
|
81
|
+
"this is a changed value"
|
82
|
+
end
|
83
|
+
|
84
|
+
puts @coffee_table.keys
|
85
|
+
@coffee_table.keys.should include "test_key_sample_class[9939]"
|
86
|
+
|
87
|
+
end
|
88
|
+
it "should raise an exception if a related object does not respond_to id" do
|
89
|
+
test_object = SampleClassWithoutId.new
|
90
|
+
|
91
|
+
lambda {
|
92
|
+
result = @coffee_table.get_cache(:test_key, test_object) do
|
93
|
+
"this is a changed value"
|
94
|
+
end
|
95
|
+
}.should raise_exception "Objects passed in must have an id method"
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
context "with expiry" do
|
100
|
+
it "should not execute block when cache available and not expired"
|
101
|
+
it "should execute block and return value when cache has expired"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "expire_key" do
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "expire_all" do
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "keys" do
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "expire_for" do
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spork'
|
3
|
+
require 'mock_redis'
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/spec/lib/sample_class')
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/spec/lib/sample_class_without_id')
|
6
|
+
|
7
|
+
Spork.prefork do
|
8
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../coffee_table/lib/coffee_table.rb')
|
9
|
+
end
|
10
|
+
|
11
|
+
Spork.each_run do
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.before(:each) {
|
14
|
+
|
15
|
+
|
16
|
+
redis = mock(:redis)
|
17
|
+
Redis.stub!(:new).and_return(MockRedis.new)
|
18
|
+
CoffeeTable::Cache.new.expire_all
|
19
|
+
|
20
|
+
}
|
21
|
+
|
22
|
+
config.after(:each) {
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
def load_sample(filename)
|
30
|
+
File.open(File.dirname(__FILE__) + "/samples/" + filename).map { |line| line}.join("\n")
|
31
|
+
end
|
32
|
+
def load_binary_sample(filename)
|
33
|
+
File.open(File.dirname(__FILE__) + "/samples/" + filename, 'rb')
|
34
|
+
end
|
35
|
+
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: coffee_table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Stewart McKee
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-16 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: rails cache gem to fragment cache with smart cache key management
|
15
|
+
email:
|
16
|
+
- stewart@theizone.co.uk
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- .rspec
|
23
|
+
- .rvmrc
|
24
|
+
- Gemfile
|
25
|
+
- Rakefile
|
26
|
+
- coffee_table.gemspec
|
27
|
+
- lib/coffee_table.rb
|
28
|
+
- lib/coffee_table/version.rb
|
29
|
+
- lib/utility.rb
|
30
|
+
- spec/lib/coffee_table_spec.rb
|
31
|
+
- spec/lib/sample_class.rb
|
32
|
+
- spec/lib/sample_class_without_id.rb
|
33
|
+
- spec/spec_helper.rb
|
34
|
+
homepage: ''
|
35
|
+
licenses: []
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project: coffee_table
|
54
|
+
rubygems_version: 1.8.10
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: Gem to manage cache stored in redis
|
58
|
+
test_files:
|
59
|
+
- spec/lib/coffee_table_spec.rb
|
60
|
+
- spec/lib/sample_class.rb
|
61
|
+
- spec/lib/sample_class_without_id.rb
|
62
|
+
- spec/spec_helper.rb
|