cachetier 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cachetier.gemspec
4
+ gemspec
5
+
6
+ group :development, :test, :cucumber do
7
+ gem "rspec"
8
+ gem "rspec-mocks"
9
+ end
10
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Yuval Larom
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,60 @@
1
+ # Cachetier
2
+
3
+ Data caching is useful when you need to invest in order to fetch the same data over and over, especially when each fetch is expensive, for example DB queries, web service calls, or the result of a non-trivial calculations.
4
+
5
+ The fastest way to cache data is local memory, but you can also use shared memory such as Redis or memcached which makes the data available to other application servers.
6
+ In some cases, even a DB can be a useful cache for very long operations, or such that you can only access a limited number of times (e.g. web service API with a daily limit).
7
+
8
+ Cachetier offers a way to define several layers of cache and automatically fetch and store the data over all layers.
9
+
10
+ ## Usage
11
+
12
+ ```ruby
13
+ def MyClass
14
+ def self.get_value(key)
15
+ ...
16
+ end
17
+
18
+ include Cachetier
19
+ cachetier :get_value, { mem: { ttl: 1.minute }, redis: { ttl: 10.minutes } }
20
+ end
21
+ ```
22
+
23
+ In the example above, accessing ```MyClass.get_value``` will:
24
+ * Search a local hash for the an entry for ```key```
25
+ * If not found, lookup entry ```key``` in Redis
26
+ * If not found, invoke the original ```get_value``` method, and will store the result in Redis and in the local hash.
27
+
28
+ Cachetier can also receive a block as a parameter instead of using an existing method. Cachetier will define a class method for the given name.
29
+
30
+ ```ruby
31
+ class MyOtherClass
32
+ cachetier :get_other_value, { mem: { ttl: 10.sec } }, do |key|
33
+ ...
34
+ end
35
+ end
36
+
37
+ MyOtherClass.get_other_value(42)
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ Cachetier configuration includes the layers at which data will be cached, and specific options per layer, such as TTL (time to live before expiration).
43
+
44
+ It currently defined are memory (hash) and Redis layers, but more layers (such as memcached and MongoDB) can be easily added.
45
+
46
+ You can set global default settings and override them for each method.
47
+
48
+ ```ruby
49
+
50
+ Cachetier.configuration = { mem: { ttl: 10.seconds } }
51
+
52
+ ```
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create new Pull Request
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ task :default => :spec
6
+ task :test => :spec
7
+
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/cachetier/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Yuval Larom"]
6
+ gem.email = ["yuval@ftbpro.com"]
7
+ gem.description = %q{Multi-tiered cache}
8
+ gem.summary = %q{Cache your data on multiple tiers: Redis, Memcached, Mongo, locally, etc}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "cachetier"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Cachetier::VERSION
17
+
18
+ gem.add_development_dependency 'rake'
19
+ gem.add_development_dependency 'rspec'
20
+ end
@@ -0,0 +1,6 @@
1
+ require "cachetier/nil_value"
2
+ require "cachetier/tier"
3
+ require "cachetier/cache"
4
+ require "cachetier/memory_tier"
5
+ require "cachetier/cachetier"
6
+ require "cachetier/version"
@@ -0,0 +1,67 @@
1
+ require 'cachetier/tier'
2
+ require 'cachetier/nil_value'
3
+
4
+ module Cachetier
5
+
6
+ class Cache
7
+
8
+ attr_reader :tiers, :getter_block
9
+
10
+ def initialize(tiers, &getter_block)
11
+ raise "Tiers cannot be nil" if !tiers
12
+ raise "Tiers cannot be empty" if tiers.empty?
13
+
14
+ @tiers = tiers.map do |name, options|
15
+ tier_class = Tier.get_tier_class(name)
16
+ tier = tier_class.new(options)
17
+ end
18
+
19
+ @getter_block = getter_block
20
+ end
21
+
22
+ def [](key)
23
+ prev_tiers = []
24
+
25
+ # some tiers override the key. save original clone of it
26
+ orig_key = begin
27
+ key.clone
28
+ rescue
29
+ key
30
+ end
31
+
32
+ tiers.each do |tier|
33
+ value = tier[key]
34
+ key = orig_key
35
+
36
+ if value
37
+ update_tiers(key, value, prev_tiers)
38
+ return nil if value == NilValue.value
39
+ return value
40
+ end
41
+ prev_tiers << tier
42
+ end
43
+
44
+ # block might change key
45
+ self[orig_key] = getter_block.call(key) if getter_block
46
+ end
47
+
48
+ def []=(key, value)
49
+
50
+ value = NilValue if value.nil?
51
+ tiers.each do |tier|
52
+ tier[key] = value if tier.writable?
53
+ end
54
+ return value
55
+ end
56
+
57
+ protected
58
+
59
+ def update_tiers(key, value, tiers)
60
+ tiers.each do |tier|
61
+ tier[key] = value if tier.writable?
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,67 @@
1
+ require 'cachetier/cache'
2
+
3
+ module Cachetier
4
+
5
+ def self.config
6
+ @@config ||= {}
7
+ end
8
+
9
+ def self.config=(val)
10
+ @@config = val
11
+ end
12
+
13
+ def self.included(base)
14
+ base.send(:extend, ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ def create_class_method(name, &block)
20
+ self.class.instance_eval do
21
+ define_method(name, &block)
22
+ end
23
+ end
24
+
25
+ def alias_class_method(new_name, original_name)
26
+ class_eval %Q{
27
+ class << self
28
+ alias_method :#{new_name}, :#{original_name}
29
+ end
30
+ }
31
+ end
32
+
33
+ def cachetier(method_name, options = nil, &block)
34
+
35
+ # given a block, create a new class method called method_name
36
+ # if not given a block, a method already exists. create a method called X_with_cachetier
37
+ cached_method_name = block ? method_name : "#{method_name}_with_cachetier"
38
+
39
+ # create a class method that uses cachetier
40
+ create_class_method cached_method_name do |key|
41
+ @@cachetiers[method_name][key]
42
+ end
43
+
44
+ # no block given - need to rename the existing method
45
+ if !block
46
+
47
+ # the original method will be called via X_without_cachetier
48
+ uncached_method_name = "#{method_name}_without_cachetier"
49
+ alias_class_method uncached_method_name, method_name
50
+
51
+ # calling the original method name will call X_with_cachetier
52
+ alias_class_method method_name, cached_method_name
53
+
54
+ # create a block for cachetier that calls the uncached version
55
+ block = proc { |key| self.send(uncached_method_name, key) }
56
+ end
57
+
58
+
59
+ options = Cachetier::config.merge(options || {})
60
+ cache = Cachetier::Cache.new(options, &block)
61
+ (@@cachetiers ||= {})[method_name] = cache
62
+ end
63
+
64
+ end
65
+
66
+
67
+ end
@@ -0,0 +1,41 @@
1
+ require 'cachetier/tier'
2
+
3
+ module Cachetier
4
+ class MemoryTier < Tier
5
+
6
+ register_tier_class :mem, MemoryTier
7
+
8
+ def initialize(options = nil)
9
+ super
10
+ @cache = {}
11
+ end
12
+
13
+ def get_val_and_expiration_time(key)
14
+ val, expiration_time = @cache[key]
15
+ end
16
+
17
+ def reset(key)
18
+ @cache.delete(key)
19
+ end
20
+
21
+ def size
22
+ @cache.size
23
+ end
24
+
25
+ def keys
26
+ return @cache.keys
27
+ end
28
+
29
+ def expired?(key)
30
+ val, expiration_time = get_val_and_expiration_time(key)
31
+ return expiration_time < Time.now
32
+ end
33
+
34
+ protected
35
+
36
+ def set(key, value, ttl)
37
+ @cache[key] = [value, Time.now + ttl.to_f]
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,12 @@
1
+ require 'cachetier/tier'
2
+
3
+ module Cachetier
4
+
5
+ class NilValue
6
+ def self.value
7
+ @@value ||= NilValue.new
8
+ end
9
+ end
10
+
11
+
12
+ end
@@ -0,0 +1,43 @@
1
+ require 'cachetier/tier'
2
+
3
+ module Cachetier
4
+ class RedisTier < Tier
5
+
6
+ register_tier_class :redis, RedisTier
7
+
8
+ def initialize(options)
9
+ super
10
+ @redis = options[:redis]
11
+ raise "Option :redis is required" if !@redis
12
+ end
13
+
14
+ def get_val_and_expiration_time(key)
15
+ val = @redis.get(key)
16
+ expiration_time = Time.now + @redis.ttl(key) if val
17
+ return [val, expiration_time]
18
+ end
19
+
20
+ def reset(key)
21
+ @redis.del(key)
22
+ end
23
+
24
+ protected
25
+
26
+ def size
27
+ 0
28
+ end
29
+
30
+ def sweepable?
31
+ false
32
+ end
33
+
34
+ def set(key, value, ttl)
35
+ @redis.multi do
36
+ @redis.set(key, value)
37
+ @redis.expire(key, ttl)
38
+ end
39
+ value
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,109 @@
1
+ module Cachetier
2
+ class Tier
3
+
4
+ def self.register_tier_class(name, klass)
5
+ @@tier_classes ||= {}
6
+ @@tier_classes[name] = klass
7
+ end
8
+
9
+ def self.get_tier_class(name)
10
+ return @@tier_classes[name]
11
+ end
12
+
13
+ DEFAULTS = {
14
+ ttl: 0,
15
+ high_watermark: nil,
16
+ low_watermark: nil
17
+ }
18
+
19
+ def initialize(options = nil)
20
+ @options = DEFAULTS.merge(options || {})
21
+ raise "TTL must be a positive number" if ttl && ttl < 0
22
+ raise "High watermark must be a positive number" if high_watermark && high_watermark <= 0
23
+ raise "Low watermark must be a positive number" if low_watermark && low_watermark <= 0
24
+ raise "High watermark must be larger than lower watermark" if high_watermark && low_watermark && high_watermark <= low_watermark
25
+ end
26
+
27
+ def ttl
28
+ @options[:ttl]
29
+ end
30
+
31
+ def high_watermark
32
+ @options[:high_watermark]
33
+ end
34
+
35
+ def low_watermark
36
+ @options[:low_watermark]
37
+ end
38
+
39
+ def [](key)
40
+ val, expiration_time = get_val_and_expiration_time(key)
41
+
42
+ if expiration_time && Time.now > expiration_time
43
+ val = nil
44
+ reset key
45
+ end
46
+
47
+ return val
48
+ end
49
+
50
+ def reset(key)
51
+ raise NotImplementedError
52
+ end
53
+
54
+ def []=(key, value)
55
+ raise "Read-only tier" if !writable?
56
+ sweep_if_needed if sweepable?
57
+ set(key, value, ttl)
58
+ end
59
+
60
+ def writable?
61
+ return @options[:writable] if @options.has_key?(:writable)
62
+ true
63
+ end
64
+
65
+ def sweepable?
66
+ return @options[:sweepable] if @options.has_key?(:sweepable)
67
+ true
68
+ end
69
+
70
+ protected
71
+
72
+ def sweep_if_needed
73
+ if high_watermark && low_watermark
74
+ sweep if size >= high_watermark
75
+ end
76
+ end
77
+
78
+ def sweep
79
+ do_sweep(force: false)
80
+ if size >= high_watermark
81
+ do_sweep(force: true)
82
+ end
83
+ end
84
+
85
+ def do_sweep(options = {force: false})
86
+ force = options[:force]
87
+ raise "Read-only tier" if !writable?
88
+ raise "Un-sweeable tier" if !sweepable?
89
+ curr_size = size
90
+ keys.each do |key|
91
+ if force || expired?(key)
92
+ reset(key)
93
+ curr_size -= 1
94
+ break if curr_size <= low_watermark
95
+ end
96
+ end
97
+ end
98
+
99
+ def get_val_and_expiration_time(key)
100
+ raise NotImplementedError
101
+ end
102
+
103
+
104
+ def keys
105
+ raise NotImplementedError
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,3 @@
1
+ module Cachetier
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+ require 'cachetier'
3
+
4
+ class DummyTier < Cachetier::Tier
5
+ def set(key, value, ttl)
6
+ end
7
+
8
+ def reset(key)
9
+ end
10
+ end
11
+
12
+
13
+ class AlwaysExpiredDummyTier < DummyTier
14
+ register_tier_class :always_expired, AlwaysExpiredDummyTier
15
+
16
+ def get_val_and_expiration_time(key)
17
+ return :dummy_cached_value_that_should_never_be_returned, Time.now - 1 # always ewxpired
18
+ end
19
+ end
20
+
21
+ class AlwaysFreshDummyTier < DummyTier
22
+ register_tier_class :always_fresh, AlwaysFreshDummyTier
23
+
24
+ def get_val_and_expiration_time(key)
25
+ return :dummy_cached_value, Time.now + 10 # never expires
26
+ end
27
+ end
28
+
29
+ class SingleValueDummyTier < DummyTier
30
+ register_tier_class :single_value, SingleValueDummyTier
31
+
32
+ def initialize(options)
33
+ super
34
+ @value = options[:value]
35
+ @expires_at = Time.now + options[:ttl]
36
+ end
37
+
38
+ def get_val_and_expiration_time(key)
39
+ return @value, @expires_at # never expires
40
+ end
41
+
42
+ def set(key, value, ttl)
43
+ @value = [:single_value_tier, value]
44
+ @expires_at = Time.now + ttl
45
+ end
46
+
47
+ def reset(key)
48
+ end
49
+
50
+ def has_key?(key)
51
+ true
52
+ end
53
+ end
54
+
55
+
56
+ describe Cachetier::Cache do
57
+
58
+ it "should return nil if nothing there" do
59
+ cache = Cachetier::Cache.new(mem: { ttl: 0.2 })
60
+ cache[:a].should == nil
61
+ end
62
+
63
+ it "should use memory tier to save data for 2 sec then expire it" do
64
+ cache = Cachetier::Cache.new(mem: { ttl: 0.2 })
65
+ cache[:a] = 1
66
+ cache[:a].should == 1
67
+ sleep 0.1
68
+ cache[:a].should == 1
69
+ sleep 0.2
70
+ cache[:a].should == nil
71
+ end
72
+
73
+ it "should use getter_block to set value" do
74
+ cache = Cachetier::Cache.new(mem: { ttl: 0.2 }) { :the_value }
75
+ cache[:a].should == :the_value
76
+ end
77
+
78
+ it "should create a cachetier for class and always return the cached value" do
79
+
80
+ class DummyClass1
81
+ include Cachetier
82
+
83
+ cachetier :get_cached_val, { always_fresh: nil } do |key|
84
+ :dummy_uncached_value
85
+ end
86
+ end
87
+
88
+ DummyClass1.get_cached_val(:dummy_key).should == :dummy_cached_value
89
+ end
90
+
91
+ it "should create a cachetier for class and never return the cached value" do
92
+
93
+ class DummyClass2
94
+ include Cachetier
95
+
96
+ cachetier :get_cached_val, { always_expired: nil } do |key|
97
+ :dummy_uncached_value
98
+ end
99
+ end
100
+
101
+ DummyClass2.get_cached_val(:dummy_key).should == :dummy_uncached_value
102
+ end
103
+
104
+ it "should fallback from one tier to the next" do
105
+
106
+ class DummyClass3
107
+ include Cachetier
108
+
109
+ cachetier :get_cached_val, { always_expired: nil, always_fresh: nil } do |key|
110
+ :dummy_uncached_value
111
+ end
112
+ end
113
+
114
+ # the first tier is always expired, the second is always fresh.
115
+ # the request should try the first tier, fail, and get from the fresh tier
116
+
117
+ DummyClass3.get_cached_val(:dummy_key).should == :dummy_cached_value
118
+ end
119
+
120
+ it "should update expired tiers" do
121
+
122
+ class DummyClass4
123
+ include Cachetier
124
+
125
+ cachetier :get_cached_val, { single_value: { value: :initial_value, ttl: 0.1 } } do |key|
126
+ :set_value
127
+ end
128
+
129
+ end
130
+
131
+ DummyClass4.get_cached_val(:whatever).should == :initial_value
132
+ sleep 0.2
133
+ DummyClass4.get_cached_val(:whatever).should == :set_value # returned by getter proc
134
+ DummyClass4.get_cached_val(:whatever).should == [:single_value_tier, :set_value] # returned by tier
135
+ end
136
+
137
+
138
+ end
139
+
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'cachetier'
3
+
4
+ describe Cachetier::MemoryTier do
5
+
6
+ it "should save data for 200ms sec then expire it" do
7
+ cache = Cachetier::MemoryTier.new(ttl: 0.2)
8
+ cache[:a] = 1
9
+ cache[:a].should == 1
10
+ sleep 0.1
11
+ cache[:a].should == 1
12
+ sleep 0.2
13
+ cache[:a].should be_nil
14
+ end
15
+
16
+ it "should sweep oldest keys when reaching high_watermark until only low_watermark items remain" do
17
+ cache = Cachetier::MemoryTier.new(ttl: 0.2, high_watermark: 20, low_watermark: 10)
18
+
19
+ 20.times do |i|
20
+ cache[i] = i
21
+ cache.size.should == i + 1
22
+ end
23
+
24
+ # now that we have 20 items in the cache,
25
+ # adding a new item should triggr a sweep old keys until size is 10,
26
+ # and then the new item is added, bringing size to 11
27
+
28
+ cache[99] = 99
29
+ cache.size.should == 11
30
+
31
+ # make sure oldest keys are gone
32
+
33
+ 10.times do |i|
34
+ cache[i].should == nil
35
+ end
36
+
37
+ # make sure newest keys are there
38
+
39
+ (11 .. 19).each do |i|
40
+ cache[i].should == i
41
+ end
42
+
43
+ cache[99].should == 99
44
+ end
45
+
46
+
47
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'cachetier'
3
+ require 'cachetier/redis_tier'
4
+
5
+ describe Cachetier::RedisTier do
6
+
7
+ it "should require redis param" do
8
+ expect { Cachetier::RedisTier.new({}) }.to raise_error
9
+ end
10
+
11
+ it "should try fetching value from redis, and check redis ttl" do
12
+ redis = double("redis")
13
+ redis.stub(get: :redis_cached_value)
14
+ redis.stub(ttl: 1)
15
+
16
+ cache = Cachetier::RedisTier.new({redis: redis})
17
+
18
+ redis.should_receive(:ttl).with(:key)
19
+ cache[:key].should == :redis_cached_value
20
+ end
21
+
22
+ it "should try fetching value from redis, and check redis ttl" do
23
+ redis = double("redis")
24
+ redis.stub(multi: 1)
25
+
26
+ cache = Cachetier::RedisTier.new({redis: redis})
27
+
28
+ redis.should_receive(:multi)
29
+ cache[:key] = 1
30
+
31
+ end
32
+ end
33
+
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'cachetier'
3
+
4
+ describe Cachetier::Tier do
5
+
6
+ it "should validate tier ctor args" do
7
+ expect { Cachetier::Tier.new(ttl: -1) }.to raise_error
8
+ Cachetier::Tier.new(ttl: 10).should_not be_nil
9
+
10
+ expect { Cachetier::Tier.new(high_watermark: -1) }.to raise_error
11
+ expect { Cachetier::Tier.new(low_watermark: -1) }.to raise_error
12
+ expect { Cachetier::Tier.new(high_watermark: 1, low_watermark: 3) }.to raise_error
13
+ Cachetier::Tier.new(high_watermark: 3, low_watermark: 1).should_not be_nil
14
+ end
15
+
16
+ it "should be able to modify tier writability and sweepability" do
17
+
18
+ Cachetier::Tier.new.writable?.should be_true
19
+ Cachetier::Tier.new.sweepable?.should be_true
20
+
21
+ Cachetier::Tier.new(writable: true, sweepable: false).writable?.should be_true
22
+ Cachetier::Tier.new(writable: true, sweepable: false).sweepable?.should be_false
23
+
24
+ Cachetier::Tier.new(writable: false, sweepable: true).writable?.should be_false
25
+ Cachetier::Tier.new(writable: false, sweepable: true).sweepable?.should be_true
26
+ end
27
+
28
+
29
+ end
@@ -0,0 +1 @@
1
+ require 'cachetier'
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cachetier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yuval Larom
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Multi-tiered cache
47
+ email:
48
+ - yuval@ftbpro.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE
56
+ - README.md
57
+ - Rakefile
58
+ - cachetier.gemspec
59
+ - lib/cachetier.rb
60
+ - lib/cachetier/cache.rb
61
+ - lib/cachetier/cachetier.rb
62
+ - lib/cachetier/memory_tier.rb
63
+ - lib/cachetier/nil_value.rb
64
+ - lib/cachetier/redis_tier.rb
65
+ - lib/cachetier/tier.rb
66
+ - lib/cachetier/version.rb
67
+ - spec/lib/cachetier_spec.rb
68
+ - spec/lib/memory_tier_spec.rb
69
+ - spec/lib/redis_tier_spec.rb
70
+ - spec/lib/tier_spec.rb
71
+ - spec/spec_helper.rb
72
+ homepage: ''
73
+ licenses: []
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ segments:
85
+ - 0
86
+ hash: -4048666811473957132
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ segments:
94
+ - 0
95
+ hash: -4048666811473957132
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 1.8.24
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: ! 'Cache your data on multiple tiers: Redis, Memcached, Mongo, locally, etc'
102
+ test_files:
103
+ - spec/lib/cachetier_spec.rb
104
+ - spec/lib/memory_tier_spec.rb
105
+ - spec/lib/redis_tier_spec.rb
106
+ - spec/lib/tier_spec.rb
107
+ - spec/spec_helper.rb