generation_cacheable 1.0.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/.gitignore +4 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/ChangeLog +2 -0
- data/Gemfile +16 -0
- data/README.md +33 -0
- data/Rakefile +8 -0
- data/generation_cacheable.gemspec +23 -0
- data/lib/gen_cache.rb +32 -0
- data/lib/gen_cache/cache_io/fetching.rb +78 -0
- data/lib/gen_cache/cache_io/formatting.rb +75 -0
- data/lib/gen_cache/cache_io/parsing.rb +66 -0
- data/lib/gen_cache/cache_types/association_cache.rb +50 -0
- data/lib/gen_cache/cache_types/attribute_cache.rb +30 -0
- data/lib/gen_cache/cache_types/class_method_cache.rb +23 -0
- data/lib/gen_cache/cache_types/key_cache.rb +14 -0
- data/lib/gen_cache/cache_types/method_cache.rb +28 -0
- data/lib/gen_cache/caches.rb +15 -0
- data/lib/gen_cache/expiry.rb +37 -0
- data/lib/gen_cache/keys.rb +70 -0
- data/lib/gen_cache/version.rb +3 -0
- data/spec/gen_cache/cache_io/fetching_spec.rb +41 -0
- data/spec/gen_cache/cache_io/formatting_spec.rb +59 -0
- data/spec/gen_cache/cache_io/parsing_spec.rb +47 -0
- data/spec/gen_cache/cache_types/association_cache_spec.rb +313 -0
- data/spec/gen_cache/cache_types/attribute_cache_spec.rb +98 -0
- data/spec/gen_cache/cache_types/class_method_cache_spec.rb +72 -0
- data/spec/gen_cache/cache_types/key_cache_spec.rb +34 -0
- data/spec/gen_cache/cache_types/method_cache_spec.rb +108 -0
- data/spec/gen_cache/expiry_spec.rb +182 -0
- data/spec/gen_cache/keys_spec.rb +66 -0
- data/spec/gen_cache_spec.rb +76 -0
- data/spec/models/account.rb +7 -0
- data/spec/models/comment.rb +9 -0
- data/spec/models/descendant.rb +20 -0
- data/spec/models/group.rb +6 -0
- data/spec/models/image.rb +11 -0
- data/spec/models/location.rb +3 -0
- data/spec/models/post.rb +31 -0
- data/spec/models/tag.rb +6 -0
- data/spec/models/user.rb +54 -0
- data/spec/spec_helper.rb +96 -0
- metadata +149 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module GenCache
|
2
|
+
module ClassMethodCache
|
3
|
+
# Cached class method
|
4
|
+
# Should expire on any instance save
|
5
|
+
def with_class_method(*methods)
|
6
|
+
self.cached_class_methods ||= []
|
7
|
+
self.cached_class_methods += methods
|
8
|
+
|
9
|
+
methods.each do |meth|
|
10
|
+
define_singleton_method("cached_#{meth}") do |*args|
|
11
|
+
cache_key = GenCache.class_method_key(self, meth)
|
12
|
+
GenCache.fetch(cache_key, args: args) do
|
13
|
+
unless args.empty?
|
14
|
+
self.send(meth, *args)
|
15
|
+
else
|
16
|
+
self.send(meth)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module GenCache
|
2
|
+
module MethodCache
|
3
|
+
def with_method(*methods)
|
4
|
+
self.cached_methods ||= []
|
5
|
+
self.cached_methods += methods
|
6
|
+
|
7
|
+
methods.each do |meth|
|
8
|
+
method_name = "cached_#{meth}"
|
9
|
+
define_method("cached_#{meth}") do |*args|
|
10
|
+
args ||= []
|
11
|
+
cache_key = GenCache.method_key(self, meth)
|
12
|
+
memoized_name = GenCache.escape_punctuation("@#{method_name}")
|
13
|
+
if instance_variable_get(memoized_name).nil?
|
14
|
+
result = GenCache.fetch(cache_key, args: args) do
|
15
|
+
unless args.empty?
|
16
|
+
self.send(meth, *args)
|
17
|
+
else
|
18
|
+
self.send(meth)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
instance_variable_set(memoized_name, result)
|
22
|
+
end
|
23
|
+
instance_variable_get(memoized_name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "gen_cache/cache_types/key_cache"
|
2
|
+
require "gen_cache/cache_types/attribute_cache"
|
3
|
+
require "gen_cache/cache_types/method_cache"
|
4
|
+
require "gen_cache/cache_types/class_method_cache"
|
5
|
+
require "gen_cache/cache_types/association_cache"
|
6
|
+
|
7
|
+
module GenCache
|
8
|
+
module Caches
|
9
|
+
include GenCache::KeyCache
|
10
|
+
include GenCache::AttributeCache
|
11
|
+
include GenCache::MethodCache
|
12
|
+
include GenCache::ClassMethodCache
|
13
|
+
include GenCache::AssocationCache
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module GenCache
|
2
|
+
|
3
|
+
def expire_all
|
4
|
+
GenCache.expire(self)
|
5
|
+
end
|
6
|
+
|
7
|
+
# Manual expiry initiated by an object after commit
|
8
|
+
# only has to worry about key_cache, attribute_cache, and class_method_cache
|
9
|
+
def self.expire(object)
|
10
|
+
expire_instance_key(object)
|
11
|
+
expire_class_method_keys(object)
|
12
|
+
expire_attribute_keys(object)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.expire_instance_key(object)
|
16
|
+
key = instance_key(object.class, object.id)
|
17
|
+
Rails.cache.delete(key[:key])
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.expire_class_method_keys(object)
|
21
|
+
object.class.cached_class_methods.map do |class_method|
|
22
|
+
key = class_method_key(object.class, class_method)
|
23
|
+
end.each do |method_key|
|
24
|
+
Rails.cache.delete(method_key[:key])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# cached attributes are stored like:
|
29
|
+
# { attribute => [value1, value2] }
|
30
|
+
def self.expire_attribute_keys(object)
|
31
|
+
object.class.cached_indices.map do |index, values|
|
32
|
+
values.map { |v| attribute_key(object.class, index, v) }
|
33
|
+
end.flatten.each do |attribute_key|
|
34
|
+
Rails.cache.delete(attribute_key[:key])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module GenCache
|
2
|
+
|
3
|
+
# Keys generated in hashes called Key blobs => { type: :key_type, key: 'key' }
|
4
|
+
# The type is used by the fetcher so it doesn't have to parse
|
5
|
+
# the key to figure out what kind it is and how to handle its contents.
|
6
|
+
|
7
|
+
## CLASS KEYS
|
8
|
+
|
9
|
+
# HASH generated from SCHEMA to indicate MODEL GENERATIONS
|
10
|
+
# => "users/5821759535148822589"
|
11
|
+
def self.model_prefix(klass)
|
12
|
+
columns = klass.try(:columns)
|
13
|
+
return if columns.nil?
|
14
|
+
schema_string = columns.sort_by(&:name).map{|c| "#{c.name}:#{c.type}"}.join(',')
|
15
|
+
generation = CityHash.hash64(schema_string)
|
16
|
+
[klass.name.tableize, generation].join("/")
|
17
|
+
end
|
18
|
+
|
19
|
+
# => "users/5821759535148822589/64"
|
20
|
+
def self.instance_key(klass, id)
|
21
|
+
{type: :object,
|
22
|
+
key: [model_prefix(klass), id].join("/") }
|
23
|
+
end
|
24
|
+
|
25
|
+
# => "users/5821759535148822589/attribute/value"
|
26
|
+
# => "users/5821759535148822589/all/attribute/value"
|
27
|
+
def self.attribute_key(klass, attribute, args, options={})
|
28
|
+
att_args = [attribute, symbolize_args([args])].join("/")
|
29
|
+
unless options[:all]
|
30
|
+
{ type: :object,
|
31
|
+
key: [model_prefix(klass), att_args].join("/") }
|
32
|
+
else
|
33
|
+
{ type: :object,
|
34
|
+
key: [model_prefix(klass), "all", att_args].join("/") }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# => "users/5821759535148822589/method"
|
39
|
+
def self.class_method_key(klass, method)
|
40
|
+
{ type: :method,
|
41
|
+
key: [model_prefix(klass), method].join("/") }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.all_class_method_keys(klass)
|
45
|
+
klass.cached_class_methods.map { |c_method| class_method_key(klass, c_method) }
|
46
|
+
end
|
47
|
+
|
48
|
+
## INSTANCE KEYS
|
49
|
+
|
50
|
+
# HASH generated from ATTRIBUTES to indicate INSTANCE GENERATIONS
|
51
|
+
# => "users/5821759535148822589/64/12126514016877773284"
|
52
|
+
def self.instance_prefix(instance)
|
53
|
+
atts = instance.attributes
|
54
|
+
att_string = atts.sort.map { |k, v| [k,v].join(":") }.join(",")
|
55
|
+
generation = CityHash.hash64(att_string)
|
56
|
+
[model_prefix(instance.class), instance.id, generation].join("/")
|
57
|
+
end
|
58
|
+
|
59
|
+
# => "users/5821759535148822589/64/12126514016877773284/method"
|
60
|
+
def self.method_key(instance, method)
|
61
|
+
{ type: :method,
|
62
|
+
key: [instance_prefix(instance), method].join("/") }
|
63
|
+
end
|
64
|
+
|
65
|
+
# => "users/5821759535148822589/64/12126514016877773284/association"
|
66
|
+
def self.association_key(instance, association)
|
67
|
+
{ type: :association,
|
68
|
+
key: [instance_prefix(instance), association].join("/") }
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GenCache do
|
4
|
+
let(:cache) { Rails.cache }
|
5
|
+
let(:user) { User.create(:login => 'flyerhzm') }
|
6
|
+
let(:user2) { User.create(:login => 'pathouse') }
|
7
|
+
|
8
|
+
describe "read" do
|
9
|
+
|
10
|
+
it "should successfully read one key" do
|
11
|
+
user.cached_bad_iv_name!
|
12
|
+
key = GenCache.method_key(user, :bad_iv_name!)
|
13
|
+
GenCache.read_from_cache(key).should == 42
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should successfully read multiple keys" do
|
17
|
+
User.find_cached(user.id)
|
18
|
+
User.find_cached(user2.id)
|
19
|
+
|
20
|
+
key_blobs = [GenCache.instance_key(user.class, user.id),
|
21
|
+
GenCache.instance_key(user2.class, user2.id)]
|
22
|
+
GenCache.read_multi_from_cache(key_blobs).should == {key_blobs[0][:key] => user,
|
23
|
+
key_blobs[1][:key] => user2}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should successfully write one key" do
|
27
|
+
key = GenCache.instance_key(user.class, user.id)
|
28
|
+
GenCache.write_to_cache(key, user)
|
29
|
+
Rails.cache.read(key[:key]).should == {:class => User, 'attributes' => user.attributes}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should successfully write multiple keys" do
|
33
|
+
keys = []
|
34
|
+
keys << GenCache.instance_key(user.class, user.id)
|
35
|
+
keys << GenCache.instance_key(user2.class, user2.id)
|
36
|
+
GenCache.write_multi_to_cache({keys[0] => user, keys[1] => user2})
|
37
|
+
Rails.cache.read_multi(*keys.map {|k| k[:key]}).should == {keys[0][:key] => {:class => User, 'attributes' => user.attributes},
|
38
|
+
keys[1][:key] => {:class => User, 'attributes' => user2.attributes}}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GenCache do
|
4
|
+
let(:object) { User.create(:login => 'flyerhzm') }
|
5
|
+
let(:fixnum) { 11 }
|
6
|
+
let(:string) { "string cheese" }
|
7
|
+
let(:hash) { {a: 1, b: 2, c: 3} }
|
8
|
+
let(:array) { ['a','b','c']}
|
9
|
+
let(:bool) { true }
|
10
|
+
|
11
|
+
context "methods" do
|
12
|
+
|
13
|
+
it "should symbolize args correctly" do
|
14
|
+
argsym = GenCache.symbolize_args([fixnum, string, hash, array])
|
15
|
+
argsym.should == "11+string_cheese+a:1,b:2,c:3+a,b,c".to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should escape method name punctuation correctly" do
|
19
|
+
GenCache.escape_punctuation("holy_crap?").should == "holy_crap_query"
|
20
|
+
GenCache.escape_punctuation("holy_crap!").should == "holy_crap_bang"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should format objects correctly" do
|
24
|
+
GenCache.format_with_key(object, :object).should == { :class => object.class, 'attributes' => object.attributes}
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should format multiple object correctly" do
|
28
|
+
coder = { :class => object.class, 'attributes' => object.attributes}
|
29
|
+
GenCache.format_with_key([object, object], :object).should == [coder, coder]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should format methods without arguments correctly" do
|
33
|
+
GenCache.format_with_key(fixnum, :method).should == 11
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should format method with arguments correctly" do
|
37
|
+
arg1 = GenCache.symbolize_args([fixnum,string,hash])
|
38
|
+
arg2 = GenCache.symbolize_args([string,hash,fixnum])
|
39
|
+
to_be_formatted = { arg1 => object,
|
40
|
+
arg2 => object}
|
41
|
+
formatted = GenCache.format_with_key(to_be_formatted, :method)
|
42
|
+
formatted[arg1].should == {:class => object.class, 'attributes' => object.attributes }
|
43
|
+
formatted[arg2].should == {:class => object.class, 'attributes' => object.attributes }
|
44
|
+
formatted[arg1].should_not == object
|
45
|
+
formatted[arg2].should_not == object
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should format object correctly when returned from a method" do
|
49
|
+
arg1 = GenCache.symbolize_args([fixnum,string])
|
50
|
+
method_result = { arg1 => object }
|
51
|
+
GenCache.format_with_key(method_result, :method).should == { arg1 => {:class => object.class, 'attributes' => object.attributes} }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GenCache do
|
4
|
+
let(:object) { User.create(:login => 'flyerhzm') }
|
5
|
+
let(:coder) { {:class => object.class, 'attributes' => object.attributes} }
|
6
|
+
let(:fixnum) { 11 }
|
7
|
+
let(:string) { "string cheese" }
|
8
|
+
let(:hash) { {a: 1, b: 2, c: 3} }
|
9
|
+
let(:array) { ['a','b','c'] }
|
10
|
+
let(:bool) { true }
|
11
|
+
|
12
|
+
context "methods" do
|
13
|
+
|
14
|
+
it "should correctly determine if a Hash is a coder" do
|
15
|
+
GenCache.hash_inspect(hash).should be_false
|
16
|
+
GenCache.hash_inspect(coder).should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should detect coders and coders in arrays" do
|
20
|
+
GenCache.detect_coder(coder).should be_true
|
21
|
+
GenCache.detect_coder([coder]).should be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should correctly rebuild objects from coders" do
|
25
|
+
GenCache.parse_with_key(coder, :object).should == object
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should rebuild multiple objects" do
|
29
|
+
GenCache.parse_with_key([coder, coder], :object).should == [object, object]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should parse only the values of method results" do
|
33
|
+
arg1 = GenCache.symbolize_args([string,hash])
|
34
|
+
arg2 = GenCache.symbolize_args([array,bool])
|
35
|
+
method_result = { arg1 => [coder],
|
36
|
+
arg2 => string }
|
37
|
+
parsed = GenCache.parse_with_key(method_result, :method)
|
38
|
+
parsed[arg1].should == [object]
|
39
|
+
parsed[arg2].should == string
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should correctly parse methods without arguments" do
|
43
|
+
method_result = {"regular" => "hash"}
|
44
|
+
GenCache.parse_with_key(method_result, :method).should == method_result
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GenCache do
|
4
|
+
let(:cache) { Rails.cache }
|
5
|
+
let(:user) { User.create(:login => 'flyerhzm') }
|
6
|
+
let(:user2) { User.create(:login => 'ScotterC') }
|
7
|
+
|
8
|
+
|
9
|
+
before :all do
|
10
|
+
@post1 = user.posts.create(:title => 'post1')
|
11
|
+
@post2 = user.posts.create(:title => 'post2')
|
12
|
+
@post3 = Post.create
|
13
|
+
@image1 = @post1.images.create
|
14
|
+
@image2 = @post1.images.create
|
15
|
+
@comment1 = @post1.comments.create
|
16
|
+
@comment2 = @post1.comments.create
|
17
|
+
@tag1 = @post1.tags.create(title: "Rails")
|
18
|
+
@tag2 = @post1.tags.create(title: "Caching")
|
19
|
+
@group1 = Group.create(name: "Ruby On Rails")
|
20
|
+
@account = user.create_account(group: @group1)
|
21
|
+
@location = @post1.create_location(city: "New York")
|
22
|
+
end
|
23
|
+
|
24
|
+
before :each do
|
25
|
+
cache.clear
|
26
|
+
user.reload
|
27
|
+
end
|
28
|
+
|
29
|
+
context "with_association" do
|
30
|
+
before :each do
|
31
|
+
@post1.instance_variable_set("@cached_user", nil)
|
32
|
+
@comment1.instance_variable_set("@cached_commentable", nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "belongs_to" do
|
36
|
+
it "should not cache association" do
|
37
|
+
key = GenCache.association_key(@post1, :user)
|
38
|
+
Rails.cache.read(key[:key]).should be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should cache Post#user" do
|
42
|
+
@post1.cached_user.should == user
|
43
|
+
key = GenCache.association_key(@post1, :user)
|
44
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(User, user.id)]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should cache Post#user multiple times" do
|
48
|
+
@post1.cached_user
|
49
|
+
@post1.cached_user.should == user
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should cache Comment#commentable with polymorphic" do
|
53
|
+
key = GenCache.association_key(@comment1, :commentable)
|
54
|
+
Rails.cache.read(key[:key]).should be_nil
|
55
|
+
@comment1.cached_commentable.should == @post1
|
56
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Post, @post1.id)]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should return nil if there are none" do
|
60
|
+
@post3.cached_user.should be_nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "has_many" do
|
65
|
+
it "should not cache associations" do
|
66
|
+
key = GenCache.association_key(user, :posts)
|
67
|
+
Rails.cache.read(key[:key]).should be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should cache User#posts" do
|
71
|
+
user.cached_posts.should == [@post1, @post2]
|
72
|
+
key = GenCache.association_key(user, :posts)
|
73
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Post, @post1.id),
|
74
|
+
GenCache.instance_key(Post, @post2.id)]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should cache User#posts multiple times" do
|
78
|
+
user.cached_posts
|
79
|
+
user.cached_posts.should == [@post1, @post2]
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should return empty if there are none" do
|
83
|
+
user2.cached_posts.should == []
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "has_many with polymorphic" do
|
88
|
+
it "should not cache associations" do
|
89
|
+
key = GenCache.association_key(@post1, :comments)
|
90
|
+
Rails.cache.read(key[:key]).should be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should cache Post#comments" do
|
94
|
+
@post1.cached_comments.should == [@comment1, @comment2]
|
95
|
+
key = GenCache.association_key(@post1, :comments)
|
96
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Comment, @comment1.id),
|
97
|
+
GenCache.instance_key(Comment, @comment2.id)]
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should cache Post#comments multiple times" do
|
101
|
+
@post1.cached_comments
|
102
|
+
@post1.cached_comments.should == [@comment1, @comment2]
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should return empty if there are none" do
|
106
|
+
@post3.cached_comments.should == []
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "has_one" do
|
111
|
+
it "should not cache associations" do
|
112
|
+
key = GenCache.association_key(user, :account)
|
113
|
+
Rails.cache.read(key[:key]).should be_nil
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should cache User#posts" do
|
117
|
+
user.cached_account.should == @account
|
118
|
+
key = GenCache.association_key(user, :account)
|
119
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Account, @account.id)]
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should cache User#posts multiple times" do
|
123
|
+
user.cached_account
|
124
|
+
user.cached_account.should == @account
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return nil if there are none" do
|
128
|
+
user2.cached_account.should be_nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "has_many through" do
|
133
|
+
it "should not cache associations" do
|
134
|
+
key = GenCache.association_key(user, :images)
|
135
|
+
Rails.cache.read(key[:key]).should be_nil
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should cache User#images" do
|
139
|
+
user.cached_images.should == [@image1, @image2]
|
140
|
+
key = GenCache.association_key(user, :images)
|
141
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Image, @image1.id),
|
142
|
+
GenCache.instance_key(Image, @image2.id)]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should cache User#images multiple times" do
|
146
|
+
user.cached_images
|
147
|
+
user.cached_images.should == [@image1, @image2]
|
148
|
+
end
|
149
|
+
|
150
|
+
context "expiry" do
|
151
|
+
before :each do
|
152
|
+
user.instance_variable_set("@cached_images", nil)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should have the correct collection" do
|
156
|
+
@image3 = @post1.images.create
|
157
|
+
key = GenCache.association_key(user, :images)
|
158
|
+
Rails.cache.read(key[:key]).should be_nil
|
159
|
+
user.cached_images.should == [@image1, @image2, @image3]
|
160
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Image, @image1.id),
|
161
|
+
GenCache.instance_key(Image, @image2.id),
|
162
|
+
GenCache.instance_key(Image, @image3.id)]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should return empty if there are none" do
|
167
|
+
user2.cached_images.should == []
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context "has_one through belongs_to" do
|
172
|
+
it "should not cache associations" do
|
173
|
+
key = GenCache.association_key(user, :group)
|
174
|
+
Rails.cache.read(key[:key]).should be_nil
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should cache User#group" do
|
178
|
+
user.cached_group.should == @group1
|
179
|
+
key = GenCache.association_key(user, :group)
|
180
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Group, @group1.id)]
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should cache User#group multiple times" do
|
184
|
+
user.cached_group
|
185
|
+
user.cached_group.should == @group1
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should return nil if there are none" do
|
189
|
+
user2.cached_group.should be_nil
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
context "has_and_belongs_to_many" do
|
195
|
+
|
196
|
+
it "should not cache associations off the bat" do
|
197
|
+
key = GenCache.association_key(@post1, :tags)
|
198
|
+
Rails.cache.read(key[:key]).should be_nil
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should cache Post#tags" do
|
202
|
+
@post1.cached_tags.should == [@tag1, @tag2]
|
203
|
+
key = GenCache.association_key(@post1, :tags)
|
204
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Tag, @tag1.id),
|
205
|
+
GenCache.instance_key(Tag, @tag2.id)]
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should handle multiple requests" do
|
209
|
+
@post1.cached_tags
|
210
|
+
@post1.cached_tags.should == [@tag1, @tag2]
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should return empty if there are none" do
|
214
|
+
@post3.cached_tags.should == []
|
215
|
+
end
|
216
|
+
|
217
|
+
context "expiry" do
|
218
|
+
before :each do
|
219
|
+
@post1.instance_variable_set("@cached_tags", nil)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should have the correct collection" do
|
223
|
+
@tag3 = @post1.tags.create!(title: "Invalidation is hard")
|
224
|
+
key = GenCache.association_key(@post1, :tags)
|
225
|
+
Rails.cache.read(key[:key]).should be_nil
|
226
|
+
@post1.cached_tags.should == [@tag1, @tag2, @tag3]
|
227
|
+
Rails.cache.read(key[:key]).should == [GenCache.instance_key(Tag, @tag1.id),
|
228
|
+
GenCache.instance_key(Tag, @tag2.id),
|
229
|
+
GenCache.instance_key(Tag, @tag3.id)]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "memoization" do
|
236
|
+
describe "belongs to" do
|
237
|
+
before :each do
|
238
|
+
@post1.instance_variable_set("@cached_user", nil)
|
239
|
+
@post1.update_attribute(:title, rand(10000).to_s)
|
240
|
+
end
|
241
|
+
|
242
|
+
it "memoizes cache calls" do
|
243
|
+
@post1.instance_variable_get("@cached_user").should be_nil
|
244
|
+
@post1.cached_user.should == @post1.user
|
245
|
+
@post1.instance_variable_get("@cached_user").should == @post1.user
|
246
|
+
end
|
247
|
+
|
248
|
+
it "hits the cache only once" do
|
249
|
+
Rails.cache.expects(:read).once
|
250
|
+
@post1.cached_user.should == @post1.user
|
251
|
+
@post1.cached_user.should == @post1.user
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
describe "has through" do
|
256
|
+
before :each do
|
257
|
+
user.instance_variable_set("@cached_images", nil)
|
258
|
+
user.update_attribute(:login, rand(10000).to_s)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "memoizes cache calls" do
|
262
|
+
user.instance_variable_get("@cached_images").should be_nil
|
263
|
+
user.cached_images.should == user.images
|
264
|
+
user.instance_variable_get("@cached_images").should == user.images
|
265
|
+
end
|
266
|
+
|
267
|
+
it "hits the cache only once" do
|
268
|
+
Rails.cache.expects(:read)
|
269
|
+
user.cached_images.should == user.images
|
270
|
+
user.cached_images.should == user.images
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe "has and belongs to many" do
|
275
|
+
before :each do
|
276
|
+
@post1.instance_variable_set("@cached_tags", nil)
|
277
|
+
@post1.update_attribute(:title, rand(10000).to_s)
|
278
|
+
end
|
279
|
+
|
280
|
+
it "memoizes cache calls" do
|
281
|
+
@post1.instance_variable_get("@cached_tags").should be_nil
|
282
|
+
@post1.cached_tags.should == @post1.tags
|
283
|
+
@post1.instance_variable_get("@cached_tags").should == @post1.tags
|
284
|
+
end
|
285
|
+
|
286
|
+
it "hits the cache only once" do
|
287
|
+
Rails.cache.expects(:read)
|
288
|
+
@post1.cached_tags.should == @post1.tags
|
289
|
+
@post1.cached_tags.should == @post1.tags
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
describe "one to many" do
|
294
|
+
before :each do
|
295
|
+
user.instance_variable_set("@cached_posts", nil)
|
296
|
+
user.update_attribute(:login, rand(10000).to_s)
|
297
|
+
end
|
298
|
+
|
299
|
+
it "memoizes cache calls" do
|
300
|
+
user.instance_variable_get("@cached_posts").should be_nil
|
301
|
+
user.cached_posts.should == user.posts
|
302
|
+
user.instance_variable_get("@cached_posts").should == user.posts
|
303
|
+
end
|
304
|
+
|
305
|
+
it "hits the cache only once" do
|
306
|
+
Rails.cache.expects(:read)
|
307
|
+
user.cached_posts.should == user.posts
|
308
|
+
user.cached_posts.should == user.posts
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|