rails-cache-tags 1.2.0 → 1.3.0
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.
- checksums.yaml +7 -0
- data/lib/rails-cache-tags.rb +22 -21
- data/lib/rails/cache/tags/action_controller.rb +14 -0
- data/lib/rails/cache/tags/set.rb +47 -0
- data/lib/rails/cache/tags/store.rb +74 -70
- data/lib/rails/cache/tags/tag.rb +36 -0
- data/lib/rails/cache/tags/version.rb +1 -1
- data/spec/cache_tags_spec.rb +171 -0
- data/spec/spec_helper.rb +24 -0
- metadata +99 -52
- data/LICENSE +0 -22
- data/README.md +0 -90
- data/lib/rails/cache/tag.rb +0 -58
- data/rails-cache-tags.gemspec +0 -26
- data/test/cache_tags_test.rb +0 -99
- data/test/caching_test.rb +0 -497
- data/test/test_helper.rb +0 -30
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1fec05417aaeacefe45090371a1987e97811b4a5
|
4
|
+
data.tar.gz: 80ab586b1cece89e8463d5306a5f072dcae869bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c4926e08c481634ad5d0307b671fd3103258876c9ab51533364313f79c4ed27ff950396493bb140f7a4905341b0fd7161ddb93a99ae9116ad0c17a2dafbb03b
|
7
|
+
data.tar.gz: 2663ab5400dde3728d4cf4187c2190910be91babbde55070fdab636b319fa1429b60ba78dce8e51bb26d32ab3c6f4a5f9b77e35811ddd45d9a5d8ed800d5aa49
|
data/lib/rails-cache-tags.rb
CHANGED
@@ -1,42 +1,43 @@
|
|
1
|
-
require
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/cache'
|
2
3
|
|
3
|
-
require
|
4
|
+
require 'rails/cache/tags/store'
|
4
5
|
|
5
|
-
require "rails/cache/tag"
|
6
|
-
require "rails/cache/tags/store"
|
7
|
-
|
8
|
-
# Patch ActiveSupport common store
|
9
6
|
module ActiveSupport
|
10
7
|
module Cache
|
11
|
-
class Store
|
12
|
-
extend Rails::Cache::Tags::Store
|
13
|
-
end
|
14
|
-
|
15
8
|
class Entry
|
16
9
|
attr_accessor :tags
|
17
10
|
end
|
11
|
+
|
12
|
+
# patch built-in stores
|
13
|
+
[:FileStore, :MemCacheStore, :MemoryStore].each do |const|
|
14
|
+
if const_defined?(const)
|
15
|
+
begin
|
16
|
+
const_get(const).send(:include, Rails::Cache::Tags::Store)
|
17
|
+
rescue LoadError, NameError
|
18
|
+
# ignore
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
21
|
-
# Patch
|
22
|
-
|
23
|
-
|
24
|
-
return unless cache_configured?
|
25
|
+
# Patch ActionController
|
26
|
+
ActiveSupport.on_load(:action_controller) do
|
27
|
+
require 'rails/cache/tags/action_controller'
|
25
28
|
|
26
|
-
|
27
|
-
end
|
28
|
-
alias expire_fragments_by_tag expire_fragments_by_tags
|
29
|
+
ActionController::Base.send(:include, Rails::Cache::Tags::ActionController)
|
29
30
|
end
|
30
31
|
|
31
32
|
# Patch Dalli store
|
32
33
|
begin
|
33
|
-
require
|
34
|
-
require
|
34
|
+
require 'dalli'
|
35
|
+
require 'dalli/version'
|
35
36
|
|
36
37
|
if Dalli::VERSION.to_f > 2
|
37
|
-
require
|
38
|
+
require 'active_support/cache/dalli_store'
|
38
39
|
|
39
|
-
ActiveSupport::Cache::DalliStore.
|
40
|
+
ActiveSupport::Cache::DalliStore.send(:include, Rails::Cache::Tags::Store)
|
40
41
|
end
|
41
42
|
rescue LoadError, NameError
|
42
43
|
# ignore
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rails
|
2
|
+
module Cache
|
3
|
+
module Tags
|
4
|
+
module ActionController
|
5
|
+
def expire_fragments_by_tags(*args)
|
6
|
+
return unless cache_configured?
|
7
|
+
|
8
|
+
cache_store.delete_tag(*args)
|
9
|
+
end
|
10
|
+
alias_method :expire_fragments_by_tag, :expire_fragments_by_tags
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'rails/cache/tags/tag'
|
4
|
+
|
5
|
+
module Rails
|
6
|
+
module Cache
|
7
|
+
module Tags
|
8
|
+
class Set
|
9
|
+
KEY_PREFIX = '_tags'
|
10
|
+
|
11
|
+
# @param [ActiveSupport::Cache::Store] cache
|
12
|
+
def initialize(cache)
|
13
|
+
@cache = cache
|
14
|
+
end
|
15
|
+
|
16
|
+
def current(tag)
|
17
|
+
@cache.fetch_without_tags(tag.to_key) { 1 }.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def expire(tag)
|
21
|
+
version = current(tag) + 1
|
22
|
+
|
23
|
+
@cache.write_without_tags(tag.to_key, version, :expires_in => nil)
|
24
|
+
|
25
|
+
version
|
26
|
+
end
|
27
|
+
|
28
|
+
def check(entry)
|
29
|
+
return entry unless entry.is_a?(Store::Entry)
|
30
|
+
return entry.value if entry.tags.blank?
|
31
|
+
|
32
|
+
tags = Tag.build(entry.tags.keys)
|
33
|
+
|
34
|
+
saved_versions = entry.tags.values.map(&:to_i)
|
35
|
+
current_versions = read_multi(tags).values.map(&:to_i)
|
36
|
+
|
37
|
+
saved_versions == current_versions ? entry.value : nil
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def read_multi(tags)
|
42
|
+
@cache.read_multi_without_tags(*Array.wrap(tags).map(&:to_key))
|
43
|
+
end
|
44
|
+
end # class Set
|
45
|
+
end # module Tags
|
46
|
+
end # module Cache
|
47
|
+
end # module Rails
|
@@ -1,102 +1,106 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
+
require 'active_support/cache'
|
4
|
+
require 'active_support/concern'
|
5
|
+
require 'active_support/core_ext/module/aliasing'
|
6
|
+
|
7
|
+
require 'rails/cache/tags/set'
|
8
|
+
|
3
9
|
module Rails
|
4
10
|
module Cache
|
5
11
|
module Tags
|
6
12
|
module Store
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
included do
|
16
|
+
alias_method_chain :initialize, :tags
|
17
|
+
alias_method_chain :exist?, :tags
|
18
|
+
alias_method_chain :read, :tags
|
19
|
+
alias_method_chain :read_multi, :tags
|
20
|
+
alias_method_chain :write, :tags
|
21
|
+
alias_method_chain :fetch, :tags
|
22
|
+
end
|
23
|
+
|
7
24
|
# cache entry (for Dalli mainly)
|
8
25
|
Entry = Struct.new(:value, :tags)
|
9
26
|
|
10
|
-
|
11
|
-
def new(*args, &block) #:nodoc:
|
12
|
-
unless acts_like?(:cached_tags)
|
13
|
-
extend ClassMethods
|
14
|
-
include InstanceMethods
|
27
|
+
attr_reader :tag_set
|
15
28
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
29
|
+
def initialize_with_tags(*args)
|
30
|
+
initialize_without_tags(*args)
|
19
31
|
|
20
|
-
|
32
|
+
@tag_set = Set.new(self)
|
21
33
|
end
|
22
34
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
35
|
+
def read_with_tags(name, options = nil)
|
36
|
+
result = read_without_tags(name, options)
|
37
|
+
entry = @tag_set.check(result)
|
27
38
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
39
|
+
if entry
|
40
|
+
entry
|
41
|
+
else
|
42
|
+
delete(name)
|
32
43
|
|
33
|
-
|
44
|
+
nil
|
34
45
|
end
|
35
|
-
|
36
|
-
alias delete_by_tags delete_tag
|
37
|
-
alias expire_tag delete_tag
|
38
|
-
|
39
|
-
protected
|
40
|
-
def read_entry_with_tags(key, options) #:nodoc
|
41
|
-
entry = read_entry_without_tags(key, options)
|
46
|
+
end
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
|
48
|
+
def write_with_tags(name, value, options = nil)
|
49
|
+
if options && options[:tags].present?
|
50
|
+
tags = Tag.build(options[:tags])
|
51
|
+
tags_hash = tags.each_with_object(Hash.new) do |tag, hash|
|
52
|
+
hash[tag.name] = tag_set.current(tag)
|
46
53
|
end
|
47
54
|
|
48
|
-
|
49
|
-
|
50
|
-
saved_versions = entry.tags.values
|
51
|
-
|
52
|
-
if current_versions != saved_versions
|
53
|
-
delete_entry(key, options)
|
55
|
+
value = Entry.new(value, tags_hash)
|
56
|
+
end
|
54
57
|
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
+
write_without_tags(name, value, options)
|
59
|
+
end
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end # def read_entry_with_tags
|
61
|
+
def exist_with_tags?(name, options = nil)
|
62
|
+
exist_without_tags?(name, options) && !read(name).nil?
|
63
|
+
end
|
63
64
|
|
64
|
-
|
65
|
-
|
65
|
+
def read_multi_with_tags(*names)
|
66
|
+
result = read_multi_without_tags(*names)
|
66
67
|
|
67
|
-
|
68
|
-
|
68
|
+
names.each_with_object(Hash.new) do |name, hash|
|
69
|
+
hash[name.to_s] = @tag_set.check(result[name.to_s])
|
70
|
+
end
|
71
|
+
end
|
69
72
|
|
70
|
-
|
71
|
-
|
73
|
+
def fetch_with_tags(name, options = nil)
|
74
|
+
return read(name, options) unless block_given?
|
72
75
|
|
73
|
-
|
74
|
-
tag, value = v
|
76
|
+
yielded = false
|
75
77
|
|
76
|
-
|
78
|
+
result = fetch_without_tags(name, options) do
|
79
|
+
yielded = true
|
80
|
+
yield
|
81
|
+
end
|
77
82
|
|
78
|
-
|
79
|
-
|
83
|
+
if yielded
|
84
|
+
result
|
85
|
+
else # only :read occured
|
86
|
+
# read occured, and result is fresh
|
87
|
+
if (entry = @tag_set.check(result))
|
88
|
+
entry
|
89
|
+
else # result is stale
|
90
|
+
delete(name)
|
91
|
+
fetch(name, options) { yield }
|
80
92
|
end
|
93
|
+
end
|
94
|
+
end
|
81
95
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
stored = read_multi(*tags.map(&:to_key))
|
91
|
-
|
92
|
-
# build hash
|
93
|
-
tags.reduce(Hash.new) do |hash, t|
|
94
|
-
hash[t] = stored[t.to_key]
|
95
|
-
|
96
|
-
hash
|
97
|
-
end
|
98
|
-
end # def fetch_tags
|
99
|
-
end # module InstanceMethods
|
96
|
+
# Increment the version of tags, so all entries referring to the tags become invalid
|
97
|
+
def delete_tag(*names)
|
98
|
+
tags = Tag.build(names)
|
99
|
+
tags.each { |tag| tag_set.expire(tag) }
|
100
|
+
end
|
101
|
+
alias delete_by_tag delete_tag
|
102
|
+
alias delete_by_tags delete_tag
|
103
|
+
alias expire_tag delete_tag
|
100
104
|
end # module Store
|
101
105
|
end # module Tags
|
102
106
|
end # module Cache
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rails
|
2
|
+
module Cache
|
3
|
+
module Tags
|
4
|
+
class Tag
|
5
|
+
KEY_PREFIX = '_tags'
|
6
|
+
|
7
|
+
# {:post => ['1', '2', '3']} => [Tag(post/1), Tag(post/2), Tag(post/3)]
|
8
|
+
# {:post => 1, :user => 2} => [Tag(post/1), Tag(user/2)]
|
9
|
+
# ['post/1', 'post/2', 'post/3'] => [Tag(post/1), Tag(post/2), Tag(post/3)]
|
10
|
+
def self.build(names)
|
11
|
+
case names
|
12
|
+
when NilClass then nil
|
13
|
+
when Hash then names.map do |key, value|
|
14
|
+
Array.wrap(value).map { |v| new([key, v]) }
|
15
|
+
end.flatten
|
16
|
+
when Enumerable then names.map { |v| build(v) }.flatten
|
17
|
+
when self then names
|
18
|
+
else [new(names)]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :name
|
23
|
+
|
24
|
+
# Tag constructor
|
25
|
+
def initialize(name)
|
26
|
+
@name = ActiveSupport::Cache.expand_cache_key name
|
27
|
+
end
|
28
|
+
|
29
|
+
# real cache key
|
30
|
+
def to_key
|
31
|
+
[KEY_PREFIX, @name].join('/')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
|
7
|
+
describe Rails::Cache::Tags do
|
8
|
+
shared_examples 'cache with tags support for' do |object|
|
9
|
+
before { cache.clear }
|
10
|
+
|
11
|
+
def assert_read(key, object)
|
12
|
+
expect(cache.exist?(key)).to eq !!object
|
13
|
+
expect(cache.read(key)).to eq object
|
14
|
+
end
|
15
|
+
|
16
|
+
def assert_blank(key)
|
17
|
+
assert_read key, nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'reads and writes a key with tags' do
|
21
|
+
cache.write 'foo', object, :tags => 'baz'
|
22
|
+
|
23
|
+
assert_read 'foo', object
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'deletes a key if tag is deleted' do
|
27
|
+
cache.write('foo', object, :tags => 'baz')
|
28
|
+
cache.delete_tag 'baz'
|
29
|
+
|
30
|
+
assert_blank 'foo'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'reads a key if another tag was deleted' do
|
34
|
+
cache.write('foo', object, :tags => 'baz')
|
35
|
+
cache.delete_tag 'fu'
|
36
|
+
|
37
|
+
assert_read 'foo', object
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'reads and writes if multiple tags given' do
|
41
|
+
cache.write('foo', object, :tags => [:baz, :kung])
|
42
|
+
|
43
|
+
assert_read 'foo', object
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'deletes a key if one of tags is deleted' do
|
47
|
+
cache.write('foo', object, :tags => [:baz, :kung])
|
48
|
+
cache.delete_tag :kung
|
49
|
+
|
50
|
+
assert_blank 'foo'
|
51
|
+
end
|
52
|
+
|
53
|
+
#it 'does not read a key if it is expired' do
|
54
|
+
# ttl = 0.01
|
55
|
+
# # dalli does not support float TTLs
|
56
|
+
# ttl *= 100 if cache.class.name == 'ActiveSupport::Cache::DalliStore'
|
57
|
+
#
|
58
|
+
# cache.write 'foo', object, :tags => [:baz, :kung], :expires_in => ttl
|
59
|
+
#
|
60
|
+
# sleep ttl * 2
|
61
|
+
#
|
62
|
+
# assert_blank 'foo'
|
63
|
+
#end
|
64
|
+
|
65
|
+
it 'reads and writes a key if hash of tags given' do
|
66
|
+
cache.write('foo', object, :tags => {:baz => 1})
|
67
|
+
assert_read 'foo', object
|
68
|
+
|
69
|
+
cache.delete_tag :baz => 2
|
70
|
+
assert_read 'foo', object
|
71
|
+
|
72
|
+
cache.delete_tag :baz => 1
|
73
|
+
assert_blank 'foo'
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'reads and writes a key if array of object given as tags' do
|
77
|
+
tag1 = 1.day.ago
|
78
|
+
tag2 = 2.days.ago
|
79
|
+
|
80
|
+
cache.write 'foo', object, :tags => [tag1, tag2]
|
81
|
+
assert_read 'foo', object
|
82
|
+
|
83
|
+
cache.delete_tag tag1
|
84
|
+
assert_blank 'foo'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'reads multiple keys with tags check' do
|
88
|
+
cache.write 'foo', object, :tags => :bar
|
89
|
+
cache.write 'bar', object, :tags => :baz
|
90
|
+
|
91
|
+
assert_read 'foo', object
|
92
|
+
assert_read 'bar', object
|
93
|
+
|
94
|
+
cache.delete_tag :bar
|
95
|
+
|
96
|
+
assert_blank 'foo'
|
97
|
+
assert_read 'bar', object
|
98
|
+
|
99
|
+
expect(cache.read_multi('foo', 'bar')).to eq('foo' => nil, 'bar' => object)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'fetches key with tag check' do
|
103
|
+
cache.write 'foo', object, :tags => :bar
|
104
|
+
|
105
|
+
expect(cache.fetch('foo') { 'baz' }).to eq object
|
106
|
+
expect(cache.fetch('foo')).to eq object
|
107
|
+
|
108
|
+
cache.delete_tag :bar
|
109
|
+
|
110
|
+
expect(cache.fetch('foo')).to be_nil
|
111
|
+
expect(cache.fetch('foo', :tags => :bar) { object }).to eq object
|
112
|
+
assert_read 'foo', object
|
113
|
+
|
114
|
+
cache.delete_tag :bar
|
115
|
+
|
116
|
+
assert_blank 'foo'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class ComplexObject < Struct.new(:value)
|
121
|
+
end
|
122
|
+
|
123
|
+
SCALAR_OBJECT = 'bar'
|
124
|
+
COMPLEX_OBJECT = ComplexObject.new('bar')
|
125
|
+
|
126
|
+
shared_examples 'cache with tags support' do |*tags|
|
127
|
+
context '', tags do
|
128
|
+
include_examples 'cache with tags support for', SCALAR_OBJECT
|
129
|
+
include_examples 'cache with tags support for', COMPLEX_OBJECT
|
130
|
+
|
131
|
+
# test everything with locale cache
|
132
|
+
include_examples 'cache with tags support for', SCALAR_OBJECT do
|
133
|
+
include ActiveSupport::Cache::Strategy::LocalCache
|
134
|
+
|
135
|
+
around(:each) do |example|
|
136
|
+
if cache.respond_to?(:with_local_cache)
|
137
|
+
cache.with_local_cache { example.run }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
include_examples 'cache with tags support for', COMPLEX_OBJECT do
|
143
|
+
include ActiveSupport::Cache::Strategy::LocalCache
|
144
|
+
|
145
|
+
around(:each) do |example|
|
146
|
+
cache.with_local_cache { example.run }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it_should_behave_like 'cache with tags support', :memory_store do
|
153
|
+
let(:cache) { ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => 100) }
|
154
|
+
end
|
155
|
+
|
156
|
+
it_should_behave_like 'cache with tags support', :file_store do
|
157
|
+
let(:cache_dir) { File.join(Dir.pwd, 'tmp_cache') }
|
158
|
+
before { FileUtils.mkdir_p(cache_dir) }
|
159
|
+
after { FileUtils.rm_rf(cache_dir) }
|
160
|
+
|
161
|
+
let(:cache) { ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) }
|
162
|
+
end
|
163
|
+
|
164
|
+
it_should_behave_like 'cache with tags support', :memcache, :mem_cache_store do
|
165
|
+
let(:cache) { ActiveSupport::Cache.lookup_store(:mem_cache_store, :expires_in => 60) }
|
166
|
+
end
|
167
|
+
|
168
|
+
it_should_behave_like 'cache with tags support', :memcache, :dalli_store do
|
169
|
+
let(:cache) { ActiveSupport::Cache.lookup_store(:dalli_store, :expires_in => 60) }
|
170
|
+
end
|
171
|
+
end
|