coffee_table 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@coffee_table
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in coffee_table.gemspec
4
+ gemspec
5
+
6
+ gem "redis"
7
+ gem "awesome_print"
8
+
9
+ group :test do
10
+ gem "rspec"
11
+ gem "mock_redis"
12
+ gem "spork"
13
+ end
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
@@ -0,0 +1,3 @@
1
+ module CoffeeTable
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ class SampleClass
2
+ def id
3
+ 9939
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ class SampleClassWithoutId
2
+ end
@@ -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