sequel-unicache 0.9.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 +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +144 -0
- data/Rakefile +11 -0
- data/lib/sequel/unicache.rb +18 -0
- data/lib/sequel/unicache/configuration.rb +111 -0
- data/lib/sequel/unicache/expire.rb +19 -0
- data/lib/sequel/unicache/finder.rb +52 -0
- data/lib/sequel/unicache/global_configuration.rb +79 -0
- data/lib/sequel/unicache/hook.rb +56 -0
- data/lib/sequel/unicache/logger.rb +15 -0
- data/lib/sequel/unicache/transaction.rb +20 -0
- data/lib/sequel/unicache/version.rb +5 -0
- data/lib/sequel/unicache/write.rb +122 -0
- data/sequel-unicache.gemspec +35 -0
- data/spec/configuration_spec.rb +121 -0
- data/spec/finder_spec.rb +173 -0
- data/spec/global_configuration_spec.rb +56 -0
- data/spec/log_spec.rb +28 -0
- data/spec/memcache.yml.example +8 -0
- data/spec/spec_helper.rb +96 -0
- data/spec/write_spec.rb +277 -0
- metadata +221 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'sequel/unicache/write'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Unicache
|
5
|
+
class Hook # Provide after_commit & after_destroy_commit to update cache
|
6
|
+
class << self
|
7
|
+
def install_hooks_for_unicache
|
8
|
+
Sequel::Model.include InstanceMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def after_commit
|
14
|
+
if Unicache.enabled?
|
15
|
+
Write.expire self
|
16
|
+
@_unicache_previous_values = nil
|
17
|
+
end
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def after_rollback
|
22
|
+
@_unicache_previous_values = nil if Unicache.enabled?
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def after_destroy_commit
|
27
|
+
if Unicache.enabled?
|
28
|
+
Write.expire self
|
29
|
+
@_unicache_previous_values = nil
|
30
|
+
end
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def after_destroy_rollback
|
35
|
+
@_unicache_previous_values = nil if Unicache.enabled?
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def before_destroy
|
40
|
+
if Unicache.enabled? && !Write.check_completeness?(self) && primary_key
|
41
|
+
@_unicache_previous_values = self.class.with_pk(pk).values
|
42
|
+
end
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def before_update
|
47
|
+
if Unicache.enabled?
|
48
|
+
# Store all previous values, to be expired
|
49
|
+
@_unicache_previous_values = initial_values.merge(@_unicache_previous_values || {})
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Unicache
|
3
|
+
class Logger
|
4
|
+
class << self
|
5
|
+
%i[debug info warn error fatal unknown].each do |level|
|
6
|
+
define_method level do |config, message = nil, &block|
|
7
|
+
# config can be treated as a model, then fallback to model class configuration and global configuration
|
8
|
+
config = config.class.unicache_class_configuration if config.is_a? Sequel::Model
|
9
|
+
config.logger.send level, message, &block if config.logger
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Unicache
|
3
|
+
class Transaction
|
4
|
+
class << self
|
5
|
+
def install_hooks_for_unicache
|
6
|
+
Sequel::Database.prepend InstanceMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def _transaction conn, opts = Database::OPTS, &block
|
12
|
+
super conn, opts do |conn|
|
13
|
+
Unicache.suspend_unicache { block.call conn }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
private :_transaction
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'sequel/unicache/logger'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Unicache
|
5
|
+
class Write
|
6
|
+
class << self
|
7
|
+
def write model
|
8
|
+
# If model is not completed, don't cache it
|
9
|
+
if (model.columns - model.keys).empty?
|
10
|
+
cache = {}
|
11
|
+
unicache_configurations(model).each_value do |config|
|
12
|
+
continue unless enabled? model, config # if unicached is disabled, do nothing
|
13
|
+
# write cache requires if-condition returns true
|
14
|
+
# otherwise will fallback to expire
|
15
|
+
if permitted? model, config
|
16
|
+
write_for model, config, cache unless suspended? # must be allowed to write cache
|
17
|
+
else
|
18
|
+
expire_for model, config
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue => error
|
23
|
+
Unicache::Logger.fatal model, "[Unicache] Exception happen when write cache for a model, fallback to expire. Reason: #{error.message}. Model: #{model.inspect}"
|
24
|
+
error.backtrace.each do |trace|
|
25
|
+
Unicache::Logger.fatal model, "[Unicache] #{trace}"
|
26
|
+
end
|
27
|
+
expire model
|
28
|
+
end
|
29
|
+
|
30
|
+
def expire model, force: false
|
31
|
+
configs = unicache_configurations model
|
32
|
+
reload model unless check_completeness? model, configs
|
33
|
+
restore_previous model do # restore to previous values temporarily
|
34
|
+
# Unicache must be enabled then do expiration
|
35
|
+
configs.each_value { |config| expire_for model, config if force || enabled?(model, config) }
|
36
|
+
end
|
37
|
+
rescue => error
|
38
|
+
Unicache::Logger.fatal model, "[Unicache] Exception happen when expire cache for a model. Reason: #{error.message}. Model: #{model.inspect}"
|
39
|
+
error.backtrace.each do |trace|
|
40
|
+
Unicache::Logger.fatal model, "[Unicache] #{trace}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_completeness? model, configs = unicache_configurations(model)
|
45
|
+
all_unicache_keys = configs.keys.flatten.uniq
|
46
|
+
model_keys = model.keys
|
47
|
+
all_unicache_keys.all? {|key| model_keys.include? key }
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def reload model
|
53
|
+
model.reload
|
54
|
+
rescue Sequel::Error => err
|
55
|
+
raise unless err.message == 'Record not found'
|
56
|
+
end
|
57
|
+
|
58
|
+
def write_for model, config, results
|
59
|
+
key = cache_key model, config
|
60
|
+
cache = results[config.serialize]
|
61
|
+
unless cache # if serialize was run before, use the cache
|
62
|
+
cache = config.serialize.(model.values, config)
|
63
|
+
results[config.serialize] = cache
|
64
|
+
end
|
65
|
+
config.cache.set key, cache, config.ttl
|
66
|
+
rescue => error
|
67
|
+
Unicache::Logger.error config, "[Unicache] Exception happen when write cache for unicache_key, fallback to expire. Reason: #{error.message}. Model: #{model.inspect}. Config: #{config.inspect}"
|
68
|
+
error.backtrace.each do |trace|
|
69
|
+
Unicache::Logger.error config, "[Unicache] #{trace}"
|
70
|
+
end
|
71
|
+
expire_for model, config
|
72
|
+
end
|
73
|
+
|
74
|
+
def expire_for model, config
|
75
|
+
key = cache_key model, config
|
76
|
+
config.cache.delete key
|
77
|
+
rescue => error
|
78
|
+
Unicache::Logger.fatal config, "[Unicache] Exception happen when expire cache for unicache_key. Reason: #{error.message}. Model: #{model.inspect}. Config: #{config.inspect}"
|
79
|
+
error.backtrace.each do |trace|
|
80
|
+
Unicache::Logger.fatal config, "[Unicache] #{trace}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def unicache_configurations model
|
85
|
+
model.class.unicache_configurations
|
86
|
+
end
|
87
|
+
|
88
|
+
def cache_key model, config
|
89
|
+
values = select_keys model, config.unicache_keys
|
90
|
+
config.key.(values, config)
|
91
|
+
end
|
92
|
+
|
93
|
+
def restore_previous model
|
94
|
+
previous_changes = model.instance_variable_get :@_unicache_previous_values
|
95
|
+
unless previous_changes.nil? || previous_changes.empty?
|
96
|
+
origin = select_keys model, previous_changes.keys
|
97
|
+
model.set_all previous_changes
|
98
|
+
end
|
99
|
+
yield
|
100
|
+
ensure
|
101
|
+
model.set_all origin if origin
|
102
|
+
end
|
103
|
+
|
104
|
+
def select_keys model, keys
|
105
|
+
Array(keys).inject({}) { |hash, attr| hash.merge attr => model[attr] }
|
106
|
+
end
|
107
|
+
|
108
|
+
def enabled? model, config
|
109
|
+
model.class.unicache_enabled_for? config
|
110
|
+
end
|
111
|
+
|
112
|
+
def suspended?
|
113
|
+
Unicache.unicache_suspended?
|
114
|
+
end
|
115
|
+
|
116
|
+
def permitted? model, config
|
117
|
+
!config.if || config.if.(model, config)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sequel/unicache/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sequel-unicache"
|
8
|
+
spec.version = Sequel::Unicache::VERSION
|
9
|
+
spec.authors = ['Bachue Zhou']
|
10
|
+
spec.email = ['bachue.shu@gmail.com']
|
11
|
+
spec.summary = 'Write through and Read through caching library inspired by Cache Money, support Sequel 4'
|
12
|
+
spec.description = <<-SUMMARY
|
13
|
+
Read through caching library inspired by Cache Money, support Sequel 4
|
14
|
+
|
15
|
+
Read-Through: Queries by ID or any specified unique key, like `User[params[:id]]` or `User[username: 'bachue@gmail.com']`, will first look in memcache store and then look in the database for the results of that query. If there is a cache miss, it will populate the cache. As objects are created, updated, and deleted, all of the caches are automatically expired.
|
16
|
+
SUMMARY
|
17
|
+
spec.homepage = 'https://github.com/bachue/sequel-unicache'
|
18
|
+
spec.license = 'GPLv2'
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0")
|
21
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.required_ruby_version = '~> 2.1'
|
25
|
+
spec.add_runtime_dependency 'sequel', '~> 4.0'
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
spec.add_development_dependency 'dalli'
|
29
|
+
spec.add_development_dependency 'pry'
|
30
|
+
spec.add_development_dependency 'pry-doc'
|
31
|
+
spec.add_development_dependency 'pry-byebug'
|
32
|
+
spec.add_development_dependency 'rspec'
|
33
|
+
spec.add_development_dependency 'sqlite3'
|
34
|
+
spec.add_development_dependency 'activesupport'
|
35
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
describe Sequel::Unicache::Configuration do
|
2
|
+
before :each do
|
3
|
+
Sequel::Unicache.configure cache: memcache, enabled: true, ttl: 120
|
4
|
+
end
|
5
|
+
|
6
|
+
it 'should configure primary key as unicache' do
|
7
|
+
expect(User.unicache_for(:id)).to be_kind_of Sequel::Unicache::Configuration
|
8
|
+
expect(User.unicache_for(:id).cache).to be memcache
|
9
|
+
expect(User.unicache_for(:id).enabled).to be true
|
10
|
+
expect(User.unicache_for(:id).ttl).to be 120
|
11
|
+
expect(User.unicache_for(:id).model_class).to be User
|
12
|
+
expect(User.unicache_for(:id).unicache_keys).to be :id
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should let class-level configuration override global-level configuration' do
|
16
|
+
User.instance_exec { unicache :id, ttl: 150 }
|
17
|
+
expect(User.unicache_for(:id).cache).to be memcache
|
18
|
+
expect(User.unicache_for(:id).enabled).to be true
|
19
|
+
expect(User.unicache_for(:id).ttl).to be 150
|
20
|
+
expect(User.unicache_for(:id).model_class).to be User
|
21
|
+
expect(User.unicache_for(:id).unicache_keys).to be :id
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should configure primary key as unicache even primary key is changed' do
|
25
|
+
User.set_primary_key [:company_name, :department, :employee_id]
|
26
|
+
expect(User.unicache_for(:company_name, :department, :employee_id)).to be_kind_of Sequel::Unicache::Configuration
|
27
|
+
expect(User.unicache_for(:department, :company_name, :employee_id).cache).to be memcache
|
28
|
+
expect(User.unicache_for(:employee_id, :department, :company_name).enabled).to be true
|
29
|
+
expect(User.unicache_for(:department, :employee_id, :company_name).ttl).to be 120
|
30
|
+
expect(User.unicache_for(:company_name, :employee_id, :department).model_class).to be User
|
31
|
+
expect(User.unicache_for(:employee_id, :company_name, :department).unicache_keys).to eq [:company_name, :department, :employee_id]
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'can configure for primary key manually' do
|
35
|
+
condition_proc = ->(model, opts) { model.deleted? }
|
36
|
+
User.instance_exec { unicache :id, enabled: false, if: condition_proc }
|
37
|
+
expect(User.unicache_for(:id)).to be_kind_of Sequel::Unicache::Configuration
|
38
|
+
expect(User.unicache_for(:id).cache).to be memcache
|
39
|
+
expect(User.unicache_for(:id).enabled).to be false
|
40
|
+
expect(User.unicache_for(:id).ttl).to be 120
|
41
|
+
expect(User.unicache_for(:id).if).to be condition_proc
|
42
|
+
expect(User.unicache_for(:id).model_class).to be User
|
43
|
+
expect(User.unicache_for(:id).unicache_keys).to be :id
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can configure for another unicache' do
|
47
|
+
serialize_proc = ->(values, opts) { JSON.dump values }
|
48
|
+
deserialize_proc = ->(cache, opts) { JSON.load cache }
|
49
|
+
User.instance_exec {
|
50
|
+
unicache :department, :employee_id, :company_name, ttl: 60,
|
51
|
+
serialize: serialize_proc, deserialize: deserialize_proc
|
52
|
+
}
|
53
|
+
expect(User.unicache_for(:company_name, :department, :employee_id)).to be_kind_of Sequel::Unicache::Configuration
|
54
|
+
expect(User.unicache_for(:department, :company_name, :employee_id).cache).to be memcache
|
55
|
+
expect(User.unicache_for(:employee_id, :department, :company_name).enabled).to be true
|
56
|
+
expect(User.unicache_for(:department, :employee_id, :company_name).serialize).to be serialize_proc
|
57
|
+
expect(User.unicache_for(:company_name, :employee_id, :department).deserialize).to be deserialize_proc
|
58
|
+
expect(User.unicache_for(:employee_id, :company_name, :department).model_class).to be User
|
59
|
+
expect(User.unicache_for(:company_name, :department, :employee_id).unicache_keys).to eq [:company_name, :department, :employee_id]
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'can configure key-level, class-level and global level' do
|
63
|
+
condition_proc = ->(model, opts) { model.deleted? }
|
64
|
+
serialize_proc = ->(values, opts) { JSON.dump values }
|
65
|
+
deserialize_proc = ->(cache, opts) { JSON.load cache }
|
66
|
+
User.instance_exec {
|
67
|
+
unicache if: condition_proc, enabled: false
|
68
|
+
unicache :department, :employee_id, :company_name, ttl: 60, enabled: true,
|
69
|
+
serialize: serialize_proc, deserialize: deserialize_proc
|
70
|
+
}
|
71
|
+
expect(User.unicache_for(:id)).to be_kind_of Sequel::Unicache::Configuration
|
72
|
+
expect(User.unicache_for(:id).cache).to be memcache
|
73
|
+
expect(User.unicache_for(:id).enabled).to be false
|
74
|
+
expect(User.unicache_for(:id).if).to be condition_proc
|
75
|
+
expect(User.unicache_for(:id).ttl).to be 120
|
76
|
+
expect(User.unicache_for(:id).model_class).to be User
|
77
|
+
expect(User.unicache_for(:id).unicache_keys).to be :id
|
78
|
+
expect(User.unicache_for(:company_name, :department, :employee_id)).to be_kind_of Sequel::Unicache::Configuration
|
79
|
+
expect(User.unicache_for(:department, :company_name, :employee_id).cache).to be memcache
|
80
|
+
expect(User.unicache_for(:employee_id, :department, :company_name).enabled).to be true
|
81
|
+
expect(User.unicache_for(:employee_id, :department, :company_name).ttl).to be 60
|
82
|
+
expect(User.unicache_for(:department, :employee_id, :company_name).serialize).to be serialize_proc
|
83
|
+
expect(User.unicache_for(:company_name, :employee_id, :department).deserialize).to be deserialize_proc
|
84
|
+
expect(User.unicache_for(:employee_id, :company_name, :department).model_class).to be User
|
85
|
+
expect(User.unicache_for(:company_name, :department, :employee_id).unicache_keys).to eq [:company_name, :department, :employee_id]
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'can enable & disable any unicache key' do
|
89
|
+
User.instance_exec { unicache enabled: false }
|
90
|
+
expect(User.unicache_for(:id).enabled).to be false
|
91
|
+
expect(User.unicache_enabled_for?(:id)).to be false
|
92
|
+
User.unicache_for(:id).enabled = true
|
93
|
+
expect(User.unicache_for(:id).enabled).to be true
|
94
|
+
expect(User.unicache_enabled_for?(:id)).to be true
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'can fuzzy search for unicache keys' do
|
98
|
+
User.instance_exec { unicache :department, :employee_id, :company_name }
|
99
|
+
expect(User.unicache_for(:id, :department)).to be_nil
|
100
|
+
expect(User.unicache_for(:id, :department, fuzzy: true)).to be User.unicache_for(:id)
|
101
|
+
expect(User.unicache_for(:id, :department, :employee_id, :company_name, fuzzy: true)).to be User.unicache_for(:id)
|
102
|
+
User.unicache_for(:id).enabled = false
|
103
|
+
expect(User.unicache_for(:id, :department, fuzzy: true)).to be_nil
|
104
|
+
expect(User.unicache_for(:id, :department, :employee_id, :company_name, fuzzy: true)).to be User.unicache_for(:department, :employee_id, :company_name)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should disable unicache key if cache store is not specified' do
|
108
|
+
reset_global_configuration
|
109
|
+
Sequel::Unicache.configure enabled: true, ttl: 120
|
110
|
+
expect(User.unicache_enabled_for?(:id)).to be nil
|
111
|
+
cache = memcache
|
112
|
+
User.instance_exec { unicache :id, cache: cache }
|
113
|
+
expect(User.unicache_enabled_for?(:id)).to be true
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should always give default value for serialize, deserialize & key' do
|
117
|
+
expect(User.unicache_for(:id).serialize).not_to be_nil
|
118
|
+
expect(User.unicache_for(:id).deserialize).not_to be_nil
|
119
|
+
expect(User.unicache_for(:id).key).not_to be_nil
|
120
|
+
end
|
121
|
+
end
|
data/spec/finder_spec.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
describe Sequel::Unicache::Finder do
|
2
|
+
let!(:user_id) { User.first.id }
|
3
|
+
|
4
|
+
before :each do
|
5
|
+
Sequel::Unicache.configure cache: memcache
|
6
|
+
end
|
7
|
+
|
8
|
+
context '.[]' do
|
9
|
+
context 'simple pk' do
|
10
|
+
it 'should cache' do
|
11
|
+
user = User[user_id]
|
12
|
+
cache = memcache.get "User:id:#{user.id}"
|
13
|
+
expect(cache).not_to be_nil
|
14
|
+
expect(Marshal.load(cache)).to eq user.values
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should get model from cache' do
|
18
|
+
User.instance_exec { unicache :id, serialize: ->(values, _) { values.to_yaml }, deserialize: ->(values, _) { YAML.load values } }
|
19
|
+
expect(User[10]).to be_nil
|
20
|
+
values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC', employee_id: 1000 }
|
21
|
+
memcache.set 'User:id:10', values.to_yaml
|
22
|
+
user = User[10]
|
23
|
+
expect(user).not_to be_nil
|
24
|
+
expect(user.values).to eq values
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'single unicache key' do
|
29
|
+
before :each do
|
30
|
+
User.instance_exec { unicache :username }
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should cache' do
|
34
|
+
user = User[username: 'bachue@gmail.com']
|
35
|
+
cache = memcache.get 'User:username:bachue@gmail.com'
|
36
|
+
expect(cache).not_to be_nil
|
37
|
+
expect(Marshal.load(cache)).to eq user.values
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should get model from cache' do
|
41
|
+
expect(User[username: 'bachue@emc.com']).to be_nil
|
42
|
+
values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC', employee_id: 1000 }
|
43
|
+
memcache.set 'User:username:bachue@emc.com', Marshal.dump(values)
|
44
|
+
user = User[username: 'bachue@emc.com']
|
45
|
+
expect(user).not_to be_nil
|
46
|
+
expect(user.values).to eq values
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'complexed unicache key' do
|
51
|
+
before :each do
|
52
|
+
User.instance_exec { unicache :company_name, :department, :employee_id }
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should cache' do
|
56
|
+
user = User[company_name: 'EMC', department: 'Mozy', employee_id: 12345]
|
57
|
+
cache = memcache.get 'User:company_name:EMC:department:Mozy:employee_id:12345'
|
58
|
+
expect(cache).not_to be_nil
|
59
|
+
expect(Marshal.load(cache)).to eq user.values
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should get model from cache' do
|
63
|
+
expect(User[company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000]).to be_nil
|
64
|
+
values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000 }
|
65
|
+
memcache.set 'User:company_name:EMC:department:DPC\:Mozy:employee_id:1000', Marshal.dump(values)
|
66
|
+
user = User[company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000]
|
67
|
+
expect(user).not_to be_nil
|
68
|
+
expect(user.values).to eq values
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'negative cases' do
|
73
|
+
it 'should only read-through from simple dataset' do
|
74
|
+
User[user_id] # cache primary key
|
75
|
+
expect(User.where(department: 'DPC')[user_id]).to be_nil
|
76
|
+
expect(User.offset(1)[user_id]).to be_nil
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should not read-through from joined dataset' do
|
80
|
+
user = User[1] # cache primary key
|
81
|
+
ds = User.inner_join :users, {id: :manager_id}, table_alias: 'managers'
|
82
|
+
expect(ds[1].values).not_to eq user.values
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context '.find' do
|
88
|
+
context 'simple pk' do
|
89
|
+
it 'should cache' do
|
90
|
+
user = User.find user_id
|
91
|
+
cache = memcache.get "User:id:#{user.id}"
|
92
|
+
expect(cache).not_to be_nil
|
93
|
+
expect(Marshal.load(cache)).to eq user.values
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should get model from cache' do
|
97
|
+
User.instance_exec { unicache :id, serialize: ->(values, _) { values.to_yaml }, deserialize: ->(values, _) { YAML.load values } }
|
98
|
+
expect(User.find 10).to be_nil
|
99
|
+
values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC', employee_id: 1000 }
|
100
|
+
memcache.set 'User:id:10', values.to_yaml
|
101
|
+
user = User.find 10
|
102
|
+
expect(user).not_to be_nil
|
103
|
+
expect(user.values).to eq values
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'single unicache key' do
|
108
|
+
before :each do
|
109
|
+
User.instance_exec { unicache :username }
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should cache' do
|
113
|
+
user = User.find username: 'bachue@gmail.com'
|
114
|
+
cache = memcache.get "User:username:bachue@gmail.com"
|
115
|
+
expect(cache).not_to be_nil
|
116
|
+
expect(Marshal.load(cache)).to eq user.values
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should get model from cache' do
|
120
|
+
User.instance_exec { unicache :username, serialize: ->(values, _) { values.to_yaml }, deserialize: ->(values, _) { YAML.load values } }
|
121
|
+
expect(User.find(username: 'bachue@emc.com')).to be_nil
|
122
|
+
values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC', employee_id: 1000 }
|
123
|
+
memcache.set 'User:username:bachue@emc.com', values.to_yaml
|
124
|
+
user = User.find username: 'bachue@emc.com'
|
125
|
+
expect(user).not_to be_nil
|
126
|
+
expect(user.values).to eq values
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'complexed unicache key' do
|
131
|
+
before :each do
|
132
|
+
User.instance_exec { unicache :company_name, :department, :employee_id }
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should cache' do
|
136
|
+
user = User.find company_name: 'EMC', department: 'Mozy', employee_id: 12345
|
137
|
+
cache = memcache.get 'User:company_name:EMC:department:Mozy:employee_id:12345'
|
138
|
+
expect(cache).not_to be_nil
|
139
|
+
expect(Marshal.load(cache)).to eq user.values
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should get model from cache' do
|
143
|
+
expect(User.find company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000).to be_nil
|
144
|
+
values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000 }
|
145
|
+
memcache.set 'User:company_name:EMC:department:DPC\:Mozy:employee_id:1000', Marshal.dump(values)
|
146
|
+
user = User.find company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000
|
147
|
+
expect(user).not_to be_nil
|
148
|
+
expect(user.values).to eq values
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'association' do
|
154
|
+
let!(:user) { User.first }
|
155
|
+
|
156
|
+
it 'should cache associated object' do
|
157
|
+
manager = user.manager
|
158
|
+
cache = memcache.get "User:id:#{manager.id}"
|
159
|
+
expect(cache).not_to be_nil
|
160
|
+
expect(Marshal.load(cache)).to eq manager.values
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should get model from cache' do
|
164
|
+
expect(user.manager.manager).to be_nil
|
165
|
+
values = { id: 10, username: 'tim@emc.com', password: 'abcdef', company_name: 'EMC', department: 'DPC:Mozy', employee_id: 100 }
|
166
|
+
memcache.set "User:id:10", Marshal.dump(values)
|
167
|
+
user.manager.set manager_id: 10
|
168
|
+
manager = user.manager.manager
|
169
|
+
expect(manager).not_to be_nil
|
170
|
+
expect(manager.values).to eq values
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|