second_level_cache 1.3.2
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 +14 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +4 -0
- data/README.md +71 -0
- data/Rakefile +10 -0
- data/init.rb +2 -0
- data/lib/second_level_cache/active_record/base.rb +25 -0
- data/lib/second_level_cache/active_record/belongs_to_association.rb +30 -0
- data/lib/second_level_cache/active_record/finder_methods.rb +109 -0
- data/lib/second_level_cache/active_record/persistence.rb +31 -0
- data/lib/second_level_cache/active_record.rb +11 -0
- data/lib/second_level_cache/arel/wheres.rb +35 -0
- data/lib/second_level_cache/config.rb +22 -0
- data/lib/second_level_cache/marshal.rb +16 -0
- data/lib/second_level_cache/version.rb +4 -0
- data/lib/second_level_cache.rb +70 -0
- data/second_level_cache.gemspec +30 -0
- data/test/active_record/base_test.rb +36 -0
- data/test/active_record/belongs_to_association_test.rb +24 -0
- data/test/active_record/finder_methods_test.rb +25 -0
- data/test/active_record/model/book.rb +14 -0
- data/test/active_record/model/image.rb +13 -0
- data/test/active_record/model/post.rb +10 -0
- data/test/active_record/model/topic.rb +13 -0
- data/test/active_record/model/user.rb +14 -0
- data/test/active_record/persistence_test.rb +27 -0
- data/test/active_record/polymorphic_association_test.rb +24 -0
- data/test/active_record/second_level_cache_test.rb +20 -0
- data/test/active_record/test_helper.rb +35 -0
- data/test/test_helper.rb +11 -0
- metadata +145 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
1.2.0
|
2
|
+
-----
|
3
|
+
* [clear cache after update_counters](https://github.com/csdn-dev/second_level_cache/commit/240dde81199124092e0e8ad0500c167ac146e301)
|
4
|
+
|
5
|
+
1.2.1
|
6
|
+
-----
|
7
|
+
* [fix polymorphic association bug]
|
8
|
+
|
9
|
+
1.3.0
|
10
|
+
-----
|
11
|
+
* [clean cache after touch]
|
12
|
+
|
13
|
+
1.3.1
|
14
|
+
-----
|
15
|
+
* [clean cache after update_column/increment!/decrement!]
|
16
|
+
|
17
|
+
1.3.2
|
18
|
+
-----
|
19
|
+
* [fix has one assciation issue]
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# SecondLevelCache
|
2
|
+
|
3
|
+
SecondLevelCache is a write-through and read-through caching library inspired by Cache Money and cache_fu, support only Rails3 and ActiveRecord.
|
4
|
+
|
5
|
+
Read-Through: Queries by ID, like `current_user.articles.find(params[:id])`, will first look in cache store and then look in the database for the results of that query. If there is a cache miss, it will populate the cache.
|
6
|
+
|
7
|
+
Write-Through: As objects are created, updated, and deleted, all of the caches are automatically kept up-to-date and coherent.
|
8
|
+
|
9
|
+
## Risk
|
10
|
+
|
11
|
+
SecondLevelCache is not fully test and verify in production enviroment right now. Use it at your own risk.
|
12
|
+
|
13
|
+
## Install
|
14
|
+
|
15
|
+
In your gem file:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem "second_level_cache", :git => "git://github.com/csdn-dev/second_level_cache.git"
|
19
|
+
```
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
For example, cache User objects:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class User < ActiveRecord::Base
|
27
|
+
acts_as_cached
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Then it will fetch cached object in this situations:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
User.find(1)
|
35
|
+
User.find_by_id(1)
|
36
|
+
User.find_by_id!(1)
|
37
|
+
User.find_by_id_and_name(1, "Hooopo")
|
38
|
+
User.where(:status => 1).find_by_id(1)
|
39
|
+
user.articles.find_by_id(1)
|
40
|
+
user.articles.find(1), user.where(:status => 1).find(1)
|
41
|
+
article.user
|
42
|
+
```
|
43
|
+
|
44
|
+
Cache key:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
user = User.find 1
|
48
|
+
user.second_level_cache_key # We will get the key looks like "slc/user/1"
|
49
|
+
```
|
50
|
+
|
51
|
+
Notice:
|
52
|
+
|
53
|
+
* SecondLevelCache cache by model name and id, so only find_one query will work.
|
54
|
+
* only equal conditions query WILL get cache; and SQL string query like `User.where("name = 'Hooopo'").find(1)` WILL NOT work.
|
55
|
+
|
56
|
+
## configure
|
57
|
+
|
58
|
+
cache_store: Default is Rails.cache
|
59
|
+
logger: Default is Rails.logger
|
60
|
+
cache_key_prefix: Avoid cache key conflict with other application, Default is 'slc'
|
61
|
+
|
62
|
+
You can config like this:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
# config/initializers/second_level_cache.rb
|
66
|
+
SecondLevelCache.configure do |config|
|
67
|
+
config.cache_store = ActiveSupport::Cache::MemoryStore.new
|
68
|
+
config.logger = Logger.new($stdout)
|
69
|
+
config.cache_key_prefix = 'domain'
|
70
|
+
end
|
71
|
+
```
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module SecondLevelCache
|
3
|
+
module ActiveRecord
|
4
|
+
module Base
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
after_destroy :expire_second_level_cache
|
9
|
+
after_save :write_second_level_cache
|
10
|
+
|
11
|
+
class << self
|
12
|
+
alias_method_chain :update_counters, :cache
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def update_counters_with_cache(id, counters)
|
19
|
+
Array(id).each{|i| expire_second_level_cache(i)}
|
20
|
+
update_counters_without_cache(id, counters)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module SecondLevelCache
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
module BelongsToAssociation
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
included do
|
8
|
+
class_eval do
|
9
|
+
alias_method_chain :find_target, :second_level_cache
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_target_with_second_level_cache
|
14
|
+
return find_target_without_second_level_cache unless klass.second_level_cache_enabled?
|
15
|
+
cache_record = klass.read_second_level_cache(second_level_cache_key)
|
16
|
+
return cache_record.tap{|record| set_inverse_instance(record)} if cache_record
|
17
|
+
record = find_target_without_second_level_cache
|
18
|
+
record.write_second_level_cache
|
19
|
+
record
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def second_level_cache_key
|
25
|
+
owner[reflection.foreign_key]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'second_level_cache/arel/wheres'
|
3
|
+
|
4
|
+
module SecondLevelCache
|
5
|
+
module ActiveRecord
|
6
|
+
module FinderMethods
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_eval do
|
11
|
+
alias_method_chain :find_one, :second_level_cache
|
12
|
+
alias_method_chain :find_by_attributes, :second_level_cache
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO fetch multi ids
|
17
|
+
#
|
18
|
+
# Cacheable:
|
19
|
+
#
|
20
|
+
# current_user.articles.where(:status => 1).visiable.find(params[:id])
|
21
|
+
#
|
22
|
+
# Uncacheable:
|
23
|
+
#
|
24
|
+
# Article.where("user_id = '1'").find(params[:id])
|
25
|
+
# Article.where("user_id > 1").find(params[:id])
|
26
|
+
# Article.where("articles.user_id = 1").find(prams[:id])
|
27
|
+
# Article.where("user_id = 1 AND ...").find(params[:id])
|
28
|
+
def find_one_with_second_level_cache(id)
|
29
|
+
return find_one_without_second_level_cache(id) unless second_level_cache_enabled?
|
30
|
+
|
31
|
+
id = id.id if ActiveRecord::Base === id
|
32
|
+
if ::ActiveRecord::IdentityMap.enabled? && cachable? && record = from_identity_map(id)
|
33
|
+
return record
|
34
|
+
end
|
35
|
+
|
36
|
+
if cachable?
|
37
|
+
return record if record = @klass.read_second_level_cache(id)
|
38
|
+
end
|
39
|
+
|
40
|
+
if cachable_without_conditions?
|
41
|
+
if record = @klass.read_second_level_cache(id)
|
42
|
+
return record if where_match_with_cache?(where_values, record)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
record = find_one_without_second_level_cache(id)
|
47
|
+
record.write_second_level_cache
|
48
|
+
record
|
49
|
+
end
|
50
|
+
|
51
|
+
# TODO cache find_or_create_by_id
|
52
|
+
def find_by_attributes_with_second_level_cache(match, attributes, *args)
|
53
|
+
return find_by_attributes_without_second_level_cache(match, attributes, *args) unless second_level_cache_enabled?
|
54
|
+
|
55
|
+
conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
|
56
|
+
|
57
|
+
if conditions.has_key?("id")
|
58
|
+
result = wrap_bang(match.bang?) do
|
59
|
+
if conditions.size == 1
|
60
|
+
find_one_with_second_level_cache(conditions["id"])
|
61
|
+
else
|
62
|
+
where(conditions.except("id")).find_one_with_second_level_cache(conditions["id"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
yield(result) if block_given? #edge rails do this bug rails3.1.0 not
|
66
|
+
|
67
|
+
return result
|
68
|
+
end
|
69
|
+
|
70
|
+
find_by_attributes_without_second_level_cache(match, attributes, *args)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def wrap_bang(bang)
|
76
|
+
bang ? yield : (yield rescue nil)
|
77
|
+
end
|
78
|
+
|
79
|
+
def cachable?
|
80
|
+
where_values.blank? &&
|
81
|
+
limit_one? && order_values.blank? &&
|
82
|
+
includes_values.blank? && preload_values.blank? &&
|
83
|
+
readonly_value.nil? && joins_values.blank? && !@klass.locking_enabled?
|
84
|
+
end
|
85
|
+
|
86
|
+
def cachable_without_conditions?
|
87
|
+
limit_one? && order_values.blank? &&
|
88
|
+
includes_values.blank? && preload_values.blank? &&
|
89
|
+
readonly_value.nil? && joins_values.blank? && !@klass.locking_enabled?
|
90
|
+
end
|
91
|
+
|
92
|
+
def where_match_with_cache?(where_values, cache_record)
|
93
|
+
condition = SecondLevelCache::Arel::Wheres.new(where_values)
|
94
|
+
return false unless condition.all_equality?
|
95
|
+
condition.extract_pairs.all? do |pair|
|
96
|
+
cache_record.read_attribute(pair[:left]) == pair[:right]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def limit_one?
|
101
|
+
limit_value.blank? || limit_value == 1
|
102
|
+
end
|
103
|
+
|
104
|
+
def from_identity_map(id)
|
105
|
+
::ActiveRecord::IdentityMap.get(@klass, id)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module SecondLevelCache
|
3
|
+
module ActiveRecord
|
4
|
+
module Persistence
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_eval do
|
9
|
+
alias_method_chain :reload, :second_level_cache
|
10
|
+
alias_method_chain :touch, :second_level_cache
|
11
|
+
alias_method_chain :update_column, :second_level_cache
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_column_with_second_level_cache(name, value)
|
16
|
+
expire_second_level_cache
|
17
|
+
update_column_without_second_level_cache(name, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def reload_with_second_level_cache(options = nil)
|
21
|
+
expire_second_level_cache
|
22
|
+
reload_without_second_level_cache(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def touch_with_second_level_cache(name = nil)
|
26
|
+
expire_second_level_cache
|
27
|
+
touch_without_second_level_cache(name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'second_level_cache/active_record/base'
|
3
|
+
require 'second_level_cache/active_record/finder_methods'
|
4
|
+
require 'second_level_cache/active_record/persistence'
|
5
|
+
require 'second_level_cache/active_record/belongs_to_association'
|
6
|
+
|
7
|
+
ActiveRecord::Base.send(:include, SecondLevelCache::Mixin)
|
8
|
+
ActiveRecord::Base.send(:include, SecondLevelCache::ActiveRecord::Base)
|
9
|
+
ActiveRecord::FinderMethods.send(:include, SecondLevelCache::ActiveRecord::FinderMethods)
|
10
|
+
ActiveRecord::Base.send(:include, SecondLevelCache::ActiveRecord::Persistence)
|
11
|
+
ActiveRecord::Associations::BelongsToAssociation.send(:include, SecondLevelCache::ActiveRecord::Associations::BelongsToAssociation)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module SecondLevelCache
|
3
|
+
module Arel
|
4
|
+
class Wheres
|
5
|
+
attr_reader :where_values
|
6
|
+
|
7
|
+
def initialize(where_values)
|
8
|
+
@where_values = where_values
|
9
|
+
end
|
10
|
+
|
11
|
+
# Determine whether all conditions is equality, for example:
|
12
|
+
#
|
13
|
+
# Article.where("user_id = 1").where(:status => 1).find(1)
|
14
|
+
def all_equality?
|
15
|
+
where_values.all?{|where_value| where_value.is_a?(::Arel::Nodes::Equality)}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Extract conditions to pairs, for checking whether cache match the conditions.
|
19
|
+
def extract_pairs
|
20
|
+
where_values.map do |where_value|
|
21
|
+
if where_value.is_a?(String)
|
22
|
+
left, right = where_value.split(/\s*=\s*/, 2)
|
23
|
+
right = right.to_i
|
24
|
+
else
|
25
|
+
left, right = where_value.left.name, where_value.right
|
26
|
+
end
|
27
|
+
{
|
28
|
+
:left => left,
|
29
|
+
:right => right
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module SecondLevelCache
|
3
|
+
module Config
|
4
|
+
extend self
|
5
|
+
|
6
|
+
attr_accessor :cache_store, :logger, :cache_key_prefix
|
7
|
+
|
8
|
+
def cache_store
|
9
|
+
@cache_store ||= Rails.cache if defined?(Rails)
|
10
|
+
@cache_store
|
11
|
+
end
|
12
|
+
|
13
|
+
def logger
|
14
|
+
@logger ||= Rails.logger if defined?(Rails)
|
15
|
+
@logger ||= Logger.new(STDOUT)
|
16
|
+
end
|
17
|
+
|
18
|
+
def cache_key_prefix
|
19
|
+
@cache_key_prefix ||= 'slc'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Marshal
|
3
|
+
class << self
|
4
|
+
def load_with_constantize(value)
|
5
|
+
begin
|
6
|
+
Marshal.load_without_constantize value
|
7
|
+
rescue ArgumentError => e
|
8
|
+
_, class_name = *(/undefined class\/module (\w+)/.match(e.message))
|
9
|
+
raise if !class_name
|
10
|
+
class_name.constantize
|
11
|
+
Marshal.load value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
alias_method_chain :load, :constantize
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_support/all'
|
3
|
+
require 'second_level_cache/config'
|
4
|
+
require 'second_level_cache/marshal'
|
5
|
+
|
6
|
+
module SecondLevelCache
|
7
|
+
def self.configure
|
8
|
+
block_given? ? yield(Config) : Config
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
delegate :logger, :cache_store, :cache_key_prefix, :to => Config
|
13
|
+
end
|
14
|
+
|
15
|
+
module Mixin
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
attr_reader :second_level_cache_options
|
20
|
+
|
21
|
+
def acts_as_cached(options = {})
|
22
|
+
@second_level_cache_enabled = true
|
23
|
+
@second_level_cache_options = options
|
24
|
+
@second_level_cache_options[:expires_in] ||= 1.day
|
25
|
+
end
|
26
|
+
|
27
|
+
def second_level_cache_enabled?
|
28
|
+
!!@second_level_cache_enabled
|
29
|
+
end
|
30
|
+
|
31
|
+
def cache_store
|
32
|
+
Config.cache_store
|
33
|
+
end
|
34
|
+
|
35
|
+
def logger
|
36
|
+
Config.logger
|
37
|
+
end
|
38
|
+
|
39
|
+
def cache_key_prefix
|
40
|
+
Config.cache_key_prefix
|
41
|
+
end
|
42
|
+
|
43
|
+
def second_level_cache_key(id)
|
44
|
+
"#{cache_key_prefix}/#{name.downcase}/#{id}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def read_second_level_cache(id)
|
48
|
+
SecondLevelCache.cache_store.read(second_level_cache_key(id)) if self.second_level_cache_enabled?
|
49
|
+
end
|
50
|
+
|
51
|
+
def expire_second_level_cache(id)
|
52
|
+
SecondLevelCache.cache_store.delete(second_level_cache_key(id)) if self.second_level_cache_enabled?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def second_level_cache_key
|
57
|
+
self.class.second_level_cache_key(id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def expire_second_level_cache
|
61
|
+
SecondLevelCache.cache_store.delete(second_level_cache_key) if self.class.second_level_cache_enabled?
|
62
|
+
end
|
63
|
+
|
64
|
+
def write_second_level_cache
|
65
|
+
SecondLevelCache.cache_store.write(second_level_cache_key, self, self.class.second_level_cache_options) if self.class.second_level_cache_enabled?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
require 'second_level_cache/active_record' if defined?(ActiveRecord)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/second_level_cache/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["wangxz"]
|
6
|
+
gem.email = ["wangxz@csdn.net"]
|
7
|
+
gem.description = %q{Write Through and Read Through caching library inspired by CacheMoney and cache_fu, support only Rails3 and ActiveRecord.}
|
8
|
+
gem.summary = <<-SUMMARY
|
9
|
+
SecondLevelCache is a write-through and read-through caching library inspired by Cache Money and cache_fu, support only Rails3 and ActiveRecord.
|
10
|
+
|
11
|
+
Read-Through: Queries by ID, like current_user.articles.find(params[:id]), will first look in cache store and then look in the database for the results of that query. If there is a cache miss, it will populate the cache.
|
12
|
+
|
13
|
+
Write-Through: As objects are created, updated, and deleted, all of the caches are automatically kept up-to-date and coherent.
|
14
|
+
SUMMARY
|
15
|
+
|
16
|
+
gem.homepage = "https://github.com/csdn-dev/second_level_cache"
|
17
|
+
|
18
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
gem.files = `git ls-files`.split("\n")
|
20
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
gem.name = "second_level_cache"
|
22
|
+
gem.require_paths = ["lib"]
|
23
|
+
gem.version = SecondLevelCache::VERSION
|
24
|
+
|
25
|
+
gem.add_runtime_dependency "activesupport", ["~> 3.2.0"]
|
26
|
+
|
27
|
+
gem.add_development_dependency "activerecord", ["~> 3.2.0"]
|
28
|
+
gem.add_development_dependency "sqlite3"
|
29
|
+
gem.add_development_dependency "rake"
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_record/test_helper'
|
3
|
+
|
4
|
+
class ActiveRecord::BaseTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@user = User.create :name => 'csdn', :email => 'test@csdn.com'
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_should_have_cache_when_create
|
10
|
+
no_connection do
|
11
|
+
assert_not_nil User.read_second_level_cache(@user.id)
|
12
|
+
assert_equal @user, User.find(@user.id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_should_update_cache_when_update
|
17
|
+
@user.update_attributes :name => 'change'
|
18
|
+
|
19
|
+
no_connection do
|
20
|
+
assert_equal 'change', User.find(@user.id).name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_expire_cache_when_destroy
|
25
|
+
@user.destroy
|
26
|
+
assert_nil User.read_second_level_cache(@user.id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_should_expire_cache_when_update_counters
|
30
|
+
assert_equal @user.books_count, 0
|
31
|
+
@user.books.create
|
32
|
+
assert_nil User.read_second_level_cache(@user.id)
|
33
|
+
user = User.find(@user.id)
|
34
|
+
assert_equal user.books_count, @user.books_count + 1
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_record/test_helper'
|
3
|
+
|
4
|
+
class ActiveRecord::BelongsToAssociationTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@user = User.create :name => 'csdn', :email => 'test@csdn.com'
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_should_get_cache_when_use_belongs_to_association
|
10
|
+
book = @user.books.create
|
11
|
+
|
12
|
+
no_connection do
|
13
|
+
assert_equal @user, book.user
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_should_write_belongs_to_association_cache
|
18
|
+
book = @user.books.create
|
19
|
+
@user.expire_second_level_cache
|
20
|
+
assert_nil User.read_second_level_cache(@user.id)
|
21
|
+
assert_equal @user, book.user
|
22
|
+
# assert_not_nil User.read_second_level_cache(@user.id)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_record/test_helper'
|
3
|
+
|
4
|
+
class ActiveRecord::FinderMethodsTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@user = User.create :name => 'csdn', :email => 'test@csdn.com'
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_should_find_without_cache
|
10
|
+
SecondLevelCache.cache_store.clear
|
11
|
+
assert_equal @user, User.find(@user.id)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_should_find_with_cache
|
15
|
+
no_connection do
|
16
|
+
assert_equal @user, User.find(@user.id)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_find_with_condition
|
21
|
+
no_connection do
|
22
|
+
assert_equal @user, User.where(:name => @user.name).find(@user.id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
ActiveRecord::Base.connection.create_table(:books, :force => true) do |t|
|
3
|
+
t.string :title
|
4
|
+
t.string :body
|
5
|
+
t.integer :user_id
|
6
|
+
t.integer :images_count, :default => 0
|
7
|
+
end
|
8
|
+
|
9
|
+
class Book < ActiveRecord::Base
|
10
|
+
acts_as_cached
|
11
|
+
|
12
|
+
belongs_to :user, :counter_cache => true
|
13
|
+
has_many :images, :as => :imagable
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
ActiveRecord::Base.connection.create_table(:images, :force => true) do |t|
|
3
|
+
t.string :url
|
4
|
+
t.string :imagable_type
|
5
|
+
t.integer :imagable_id
|
6
|
+
end
|
7
|
+
|
8
|
+
class Image < ActiveRecord::Base
|
9
|
+
acts_as_cached
|
10
|
+
|
11
|
+
belongs_to :imagable, :polymorphic => true, :counter_cache => true
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
ActiveRecord::Base.connection.create_table(:users, :force => true) do |t|
|
3
|
+
t.string :name
|
4
|
+
t.string :email
|
5
|
+
t.integer :books_count, :default => 0
|
6
|
+
t.integer :images_count, :default => 0
|
7
|
+
end
|
8
|
+
|
9
|
+
class User < ActiveRecord::Base
|
10
|
+
acts_as_cached
|
11
|
+
|
12
|
+
has_many :books
|
13
|
+
has_many :images, :as => :imagable
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_record/test_helper'
|
3
|
+
|
4
|
+
class ActiveRecord::PersistenceTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@user = User.create :name => 'csdn', :email => 'test@csdn.com'
|
7
|
+
@topic = Topic.create :title => "csdn"
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_should_reload_object
|
11
|
+
User.increment_counter :books_count, @user.id
|
12
|
+
assert_equal 0, @user.books_count
|
13
|
+
assert_equal 1, @user.reload.books_count
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_should_clean_cache_after_touch
|
17
|
+
post = @topic.posts.create
|
18
|
+
post.body = "body"
|
19
|
+
post.save
|
20
|
+
new_topic = Topic.find @topic.id
|
21
|
+
assert !(new_topic.updated_at == @topic.updated_at)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_return_true_if_touch_ok
|
25
|
+
assert @topic.touch == true
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_record/test_helper'
|
3
|
+
|
4
|
+
class ActiveRecord::PolymorphicAssociationTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@user = User.create :name => 'csdn', :email => 'test@csdn.com'
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_should_get_cache_when_use_polymorphic_association
|
10
|
+
image = @user.images.create
|
11
|
+
|
12
|
+
no_connection do
|
13
|
+
assert_equal @user, image.imagable
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_should_write_polymorphic_association_cache
|
18
|
+
image = @user.images.create
|
19
|
+
@user.expire_second_level_cache
|
20
|
+
assert_nil User.read_second_level_cache(@user.id)
|
21
|
+
assert_equal @user, image.imagable
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_record/test_helper'
|
3
|
+
|
4
|
+
class ActiveRecord::SecondLevelCacheTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@user = User.create :name => 'csdn', :email => 'test@csdn.com'
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_should_get_cache_key
|
10
|
+
assert_equal "slc/user/#{@user.id}", @user.second_level_cache_key
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_should_write_and_read_cache
|
14
|
+
assert_not_nil User.read_second_level_cache(@user.id)
|
15
|
+
@user.expire_second_level_cache
|
16
|
+
assert_nil User.read_second_level_cache(@user.id)
|
17
|
+
@user.write_second_level_cache
|
18
|
+
assert_not_nil User.read_second_level_cache(@user.id)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'test_helper'
|
3
|
+
require 'active_record'
|
4
|
+
require 'second_level_cache/active_record'
|
5
|
+
|
6
|
+
def open_test_db_connect
|
7
|
+
ActiveRecord::Base.establish_connection(
|
8
|
+
:adapter => 'sqlite3',
|
9
|
+
:database => 'test/test.sqlite3'
|
10
|
+
)
|
11
|
+
end
|
12
|
+
open_test_db_connect
|
13
|
+
|
14
|
+
def close_test_db_connect
|
15
|
+
ActiveRecord::Base.connection.disconnect!
|
16
|
+
end
|
17
|
+
|
18
|
+
class Test::Unit::TestCase
|
19
|
+
def no_connection
|
20
|
+
close_test_db_connect
|
21
|
+
assert_nothing_raised { yield }
|
22
|
+
ensure
|
23
|
+
open_test_db_connect
|
24
|
+
end
|
25
|
+
|
26
|
+
def teardown
|
27
|
+
User.delete_all
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'active_record/model/user'
|
32
|
+
require 'active_record/model/book'
|
33
|
+
require 'active_record/model/image'
|
34
|
+
require 'active_record/model/topic'
|
35
|
+
require 'active_record/model/post'
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'second_level_cache'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
SecondLevelCache.configure do |config|
|
8
|
+
config.cache_store = ActiveSupport::Cache::MemoryStore.new
|
9
|
+
end
|
10
|
+
|
11
|
+
SecondLevelCache.logger.level = Logger::INFO
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: second_level_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- wangxz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activerecord
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 3.2.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: 3.2.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sqlite3
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Write Through and Read Through caching library inspired by CacheMoney
|
79
|
+
and cache_fu, support only Rails3 and ActiveRecord.
|
80
|
+
email:
|
81
|
+
- wangxz@csdn.net
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- CHANGELOG.md
|
88
|
+
- Gemfile
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- init.rb
|
92
|
+
- lib/second_level_cache.rb
|
93
|
+
- lib/second_level_cache/active_record.rb
|
94
|
+
- lib/second_level_cache/active_record/base.rb
|
95
|
+
- lib/second_level_cache/active_record/belongs_to_association.rb
|
96
|
+
- lib/second_level_cache/active_record/finder_methods.rb
|
97
|
+
- lib/second_level_cache/active_record/persistence.rb
|
98
|
+
- lib/second_level_cache/arel/wheres.rb
|
99
|
+
- lib/second_level_cache/config.rb
|
100
|
+
- lib/second_level_cache/marshal.rb
|
101
|
+
- lib/second_level_cache/version.rb
|
102
|
+
- second_level_cache.gemspec
|
103
|
+
- test/active_record/base_test.rb
|
104
|
+
- test/active_record/belongs_to_association_test.rb
|
105
|
+
- test/active_record/finder_methods_test.rb
|
106
|
+
- test/active_record/model/book.rb
|
107
|
+
- test/active_record/model/image.rb
|
108
|
+
- test/active_record/model/post.rb
|
109
|
+
- test/active_record/model/topic.rb
|
110
|
+
- test/active_record/model/user.rb
|
111
|
+
- test/active_record/persistence_test.rb
|
112
|
+
- test/active_record/polymorphic_association_test.rb
|
113
|
+
- test/active_record/second_level_cache_test.rb
|
114
|
+
- test/active_record/test_helper.rb
|
115
|
+
- test/test_helper.rb
|
116
|
+
homepage: https://github.com/csdn-dev/second_level_cache
|
117
|
+
licenses: []
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 1.8.24
|
137
|
+
signing_key:
|
138
|
+
specification_version: 3
|
139
|
+
summary: ! 'SecondLevelCache is a write-through and read-through caching library inspired
|
140
|
+
by Cache Money and cache_fu, support only Rails3 and ActiveRecord. Read-Through:
|
141
|
+
Queries by ID, like current_user.articles.find(params[:id]), will first look in
|
142
|
+
cache store and then look in the database for the results of that query. If there
|
143
|
+
is a cache miss, it will populate the cache. Write-Through: As objects are created,
|
144
|
+
updated, and deleted, all of the caches are automatically kept up-to-date and coherent.'
|
145
|
+
test_files: []
|