sequel-cacheable 0.0.7 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +53 -0
- data/.pryrc +17 -0
- data/.rbenv-version +1 -0
- data/.travis.yml +4 -1
- data/Gemfile +21 -23
- data/Procfile +2 -0
- data/README.rdoc +4 -3
- data/Rakefile +1 -36
- data/lib/sequel-cacheable/class_methods.rb +74 -0
- data/lib/sequel-cacheable/dataset_methods.rb +53 -0
- data/lib/sequel-cacheable/driver/dalli.rb +19 -0
- data/lib/sequel-cacheable/driver/memcache.rb +15 -0
- data/lib/sequel-cacheable/driver/redis.rb +6 -0
- data/lib/sequel-cacheable/driver.rb +56 -0
- data/lib/sequel-cacheable/instance_methods.rb +66 -0
- data/lib/sequel-cacheable/packer.rb +34 -0
- data/lib/sequel-cacheable/version.rb +7 -0
- data/lib/sequel-cacheable.rb +18 -211
- data/sequel-cacheable.gemspec +18 -94
- data/spec/lib/sequel-cacheable/driver/dalli_driver_spec.rb +8 -0
- data/spec/lib/sequel-cacheable/driver/memcache_driver_spec.rb +7 -0
- data/spec/lib/sequel-cacheable/driver/redis_driver_spec.rb +8 -0
- data/spec/lib/sequel-cacheable/driver_spec.rb +35 -0
- data/spec/lib/sequel-cacheable_spec.rb +1 -243
- data/spec/models/dalli_spec.rb +5 -0
- data/spec/models/memcache_spec.rb +5 -0
- data/spec/models/redis_spec.rb +5 -0
- data/spec/shared/cacheable.rb +161 -0
- data/spec/shared/driver.rb +102 -0
- data/spec/spec_helper.rb +25 -28
- data/spec/support/models/dalli.rb +5 -0
- data/spec/support/models/memcache.rb +5 -0
- data/spec/support/models/redis.rb +5 -0
- metadata +57 -241
- data/Gemfile.lock +0 -66
- data/VERSION +0 -1
data/.gitignore
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
coverage.data
|
4
|
+
|
5
|
+
# rdoc generated
|
6
|
+
rdoc
|
7
|
+
|
8
|
+
# yard generated
|
9
|
+
doc
|
10
|
+
.yardoc
|
11
|
+
|
12
|
+
# bundler
|
13
|
+
.bundle
|
14
|
+
|
15
|
+
# jeweler generated
|
16
|
+
pkg
|
17
|
+
|
18
|
+
Gemfile.lock
|
19
|
+
dump.rdb
|
20
|
+
tmp
|
21
|
+
|
22
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
23
|
+
#
|
24
|
+
# * Create a file at ~/.gitignore
|
25
|
+
# * Include files you want ignored
|
26
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
27
|
+
#
|
28
|
+
# After doing this, these files will be ignored in all your git projects,
|
29
|
+
# saving you from having to 'pollute' every project you touch with them
|
30
|
+
#
|
31
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
32
|
+
#
|
33
|
+
# For MacOS:
|
34
|
+
#
|
35
|
+
#.DS_Store
|
36
|
+
|
37
|
+
# For TextMate
|
38
|
+
#*.tmproj
|
39
|
+
#tmtags
|
40
|
+
|
41
|
+
# For emacs:
|
42
|
+
#*~
|
43
|
+
#\#*
|
44
|
+
#.\#*
|
45
|
+
|
46
|
+
# For vim:
|
47
|
+
#*.swp
|
48
|
+
|
49
|
+
# For redcar:
|
50
|
+
#.redcar
|
51
|
+
|
52
|
+
# For rubinius:
|
53
|
+
#*.rbc
|
data/.pryrc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require(:default, :development)
|
4
|
+
|
5
|
+
$: << "#{Dir.pwd}/lib"
|
6
|
+
require 'sequel-cacheable'
|
7
|
+
|
8
|
+
DB = Sequel.sqlite
|
9
|
+
DB.create_table(:mock) do
|
10
|
+
primary_key :id, :auto_increment => true
|
11
|
+
String :name
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'memcache/null_server'
|
15
|
+
class Mock < Sequel::Model(:mock)
|
16
|
+
plugin :cacheable, Redis.new(server: 'localhost:6379'), :query_cache => true
|
17
|
+
end
|
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p327
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,30 +1,28 @@
|
|
1
|
-
source
|
2
|
-
# Add dependencies required to use your gem here.
|
3
|
-
# Example:
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
1
|
+
source :rubygems
|
5
2
|
|
6
|
-
|
7
|
-
|
3
|
+
gem 'bundler'
|
4
|
+
gem 'rake'
|
8
5
|
|
9
|
-
|
10
|
-
gem 'msgpack', '~> 0.4.6'
|
11
|
-
gem 'hashr', '~> 0.0.19'
|
6
|
+
gemspec
|
12
7
|
|
13
8
|
group :development, :test do
|
14
|
-
gem
|
9
|
+
gem 'rspec', "~> 2.12.0"
|
10
|
+
gem 'yard', "~> 0.8.3"
|
11
|
+
gem 'rdoc', "~> 3.12"
|
12
|
+
gem 'simplecov', "~> 0.7.1", :require => false
|
15
13
|
|
16
|
-
gem
|
17
|
-
gem
|
18
|
-
gem
|
19
|
-
gem
|
20
|
-
gem 'simplecov', "~> 0.6.1", :require => false
|
14
|
+
gem 'guard', '~> 1.6.1'
|
15
|
+
gem 'guard-rspec', '~> 2.3.3'
|
16
|
+
gem 'terminal-notifier'
|
17
|
+
gem 'listen', '~> 0.7.0'
|
21
18
|
|
22
|
-
gem
|
23
|
-
gem
|
24
|
-
gem
|
25
|
-
|
26
|
-
gem
|
27
|
-
gem
|
28
|
-
gem
|
29
|
-
gem
|
19
|
+
gem 'sqlite3'
|
20
|
+
gem 'dalli'
|
21
|
+
gem 'memcache'
|
22
|
+
gem 'hiredis'
|
23
|
+
gem 'redis', :require => ['redis', 'redis/connection/hiredis']
|
24
|
+
gem 'pry'
|
25
|
+
gem 'pry-doc'
|
26
|
+
gem 'forgery'
|
27
|
+
gem 'tapp'
|
30
28
|
end
|
data/Procfile
ADDED
data/README.rdoc
CHANGED
@@ -23,15 +23,16 @@ Caching!
|
|
23
23
|
tested cache_client:
|
24
24
|
|
25
25
|
* Redis[https://rubygems.org/gems/redis]
|
26
|
+
* Hiredis[https://rubygems.org/gems/hiredis]
|
26
27
|
* Memcached[https://rubygems.org/gems/memcache]
|
28
|
+
* Dalli[https://rubygems.org/gems/dalli]
|
27
29
|
|
28
30
|
options:
|
29
31
|
|
30
32
|
{
|
31
33
|
:ttl => 3600, # time to live. default 3600
|
32
|
-
:ignore_exception => false, # through exception. default false
|
33
34
|
:pack_lib => MessagePack, # cache packing library. This Object should be have pack / unpack method.
|
34
|
-
:query_cache => false # enable query cache. default
|
35
|
+
:query_cache => false # enable query cache. default false
|
35
36
|
}
|
36
37
|
|
37
38
|
== Cache Timing
|
@@ -48,7 +49,7 @@ options:
|
|
48
49
|
* clear all query caches of model
|
49
50
|
|
50
51
|
== Contributing to sequel-cacheable
|
51
|
-
|
52
|
+
|
52
53
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
53
54
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
54
55
|
* Fork the project.
|
data/Rakefile
CHANGED
@@ -1,37 +1,2 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'rubygems'
|
4
|
-
require 'bundler'
|
5
|
-
begin
|
6
|
-
Bundler.setup(:default, :development)
|
7
|
-
rescue Bundler::BundlerError => e
|
8
|
-
$stderr.puts e.message
|
9
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
-
exit e.status_code
|
11
|
-
end
|
12
|
-
require 'rake'
|
13
|
-
|
14
|
-
require 'jeweler'
|
15
|
-
Jeweler::Tasks.new do |gem|
|
16
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
-
gem.name = "sequel-cacheable"
|
18
|
-
gem.homepage = "http://github.com/rosylilly/sequel-cacheable"
|
19
|
-
gem.license = "MIT"
|
20
|
-
gem.summary = %Q{This plug-in caching mechanism to implement the Model of the Sequel}
|
21
|
-
gem.description = %Q{This plug-in caching mechanism to implement the Model of the Sequel}
|
22
|
-
gem.email = "rosylilly@aduca.org"
|
23
|
-
gem.authors = ["Sho Kusano <rosylilly>"]
|
24
|
-
# dependencies defined in Gemfile
|
25
|
-
end
|
26
|
-
Jeweler::RubygemsDotOrgTasks.new
|
27
|
-
|
28
|
-
require 'rspec/core'
|
29
|
-
require 'rspec/core/rake_task'
|
30
|
-
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
-
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
-
end
|
33
|
-
|
34
|
-
task :default => :spec
|
35
|
-
|
36
|
-
require 'yard'
|
37
|
-
YARD::Rake::YardocTask.new
|
2
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Sequel::Plugins
|
4
|
+
module Cacheable
|
5
|
+
module ClassMethods
|
6
|
+
attr_reader :cache_driver, :cache_options, :caches
|
7
|
+
|
8
|
+
def inherited(subclass)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def primary_key_lookup(id)
|
13
|
+
cache_fetch(id.to_s) do
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def restore_by_cache(hash)
|
19
|
+
return nil if hash.nil?
|
20
|
+
|
21
|
+
return hash.map{|hs| restore_by_cache(hs) } if hash.is_a?(Array)
|
22
|
+
|
23
|
+
return hash if hash.kind_of?(Sequel::Model)
|
24
|
+
|
25
|
+
hash.keys.each do | key |
|
26
|
+
value = hash.delete(key)
|
27
|
+
key = key.to_sym rescue key
|
28
|
+
case db_schema[key][:type]
|
29
|
+
when :date
|
30
|
+
value = Date.new(*value)
|
31
|
+
when :time
|
32
|
+
value = Sequel::SQLTime.at(value[0], value[1])
|
33
|
+
when :datetime
|
34
|
+
value = Time.at(value[0], value[1])
|
35
|
+
when :decimal
|
36
|
+
value = BigDecimal.new(value)
|
37
|
+
when :integer
|
38
|
+
value = value.to_i
|
39
|
+
end
|
40
|
+
hash[key] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
return new(hash, true)
|
44
|
+
end
|
45
|
+
|
46
|
+
def cache_key(key)
|
47
|
+
"#{self.name}:#{key}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def cache_set(key, value, ttl = @cache_options[:ttl])
|
51
|
+
@caches[key.match(/\AQuery:/) ? :query : :instance] << key
|
52
|
+
cache_driver.set(cache_key(key), value, ttl)
|
53
|
+
end
|
54
|
+
|
55
|
+
def cache_get(key)
|
56
|
+
restore_by_cache(cache_driver.get(cache_key(key)))
|
57
|
+
end
|
58
|
+
|
59
|
+
def cache_fetch(key, ttl = @cache_options[:ttl], &block)
|
60
|
+
@caches[key.match(/\AQuery:/) ? :query : :instance] << key
|
61
|
+
cache_driver.fetch(cache_key(key), ttl, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def cache_del(key)
|
65
|
+
@caches[key.match(/\AQuery:/) ? :query : :instance].delete(key)
|
66
|
+
cache_driver.del(cache_key(key))
|
67
|
+
end
|
68
|
+
|
69
|
+
def cache_clear(type)
|
70
|
+
@caches[type].dup.each {|key| cache_del(key) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Sequel::Plugins
|
4
|
+
module Cacheable
|
5
|
+
module DatasetMethods
|
6
|
+
def all(*args, &block)
|
7
|
+
if(
|
8
|
+
model &&
|
9
|
+
model.respond_to?(:cache_fetch) &&
|
10
|
+
model.cache_options[:query_cache] &&
|
11
|
+
@row_proc.kind_of?(Class) &&
|
12
|
+
@row_proc.included_modules.include?(Sequel::Model::InstanceMethods)
|
13
|
+
)
|
14
|
+
@row_proc.cache_fetch(cache_key) do
|
15
|
+
super(*args, &block)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
super(*args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def first(*args)
|
23
|
+
if(
|
24
|
+
model &&
|
25
|
+
model.respond_to?(:cache_fetch) &&
|
26
|
+
model.cache_options[:query_cache] &&
|
27
|
+
@row_proc.kind_of?(Class) &&
|
28
|
+
@row_proc.included_modules.include?(Sequel::Model::InstanceMethods)
|
29
|
+
)
|
30
|
+
@row_proc.cache_fetch("#{cache_key}:first") do
|
31
|
+
super(*args)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
super(*args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_pk(pk)
|
39
|
+
if pk.is_a(Integer)
|
40
|
+
model.cache_fetch(pk.to_s) do
|
41
|
+
super(pk)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
super(pk)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def cache_key
|
49
|
+
"Query:#{select_sql.gsub(/ +/, '_')}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Sequel::Plugins
|
2
|
+
module Cacheable
|
3
|
+
class DalliDriver < Driver
|
4
|
+
def del(key)
|
5
|
+
@store.delete(key)
|
6
|
+
|
7
|
+
return nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def expire(key, time)
|
11
|
+
if time > 0
|
12
|
+
@store.touch(key, time)
|
13
|
+
else
|
14
|
+
@store.delete(key)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Sequel::Plugins
|
2
|
+
module Cacheable
|
3
|
+
DRIVERS = %w(memcache dalli redis).freeze
|
4
|
+
|
5
|
+
class Driver
|
6
|
+
def self.factory(*args)
|
7
|
+
case args[0].class.name
|
8
|
+
when 'Memcache'
|
9
|
+
MemcacheDriver.new(*args)
|
10
|
+
when 'Dalli::Client'
|
11
|
+
DalliDriver.new(*args)
|
12
|
+
when 'Redis'
|
13
|
+
RedisDriver.new(*args)
|
14
|
+
else
|
15
|
+
Driver.new(*args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(store, pack_lib = MessagePackPacker)
|
20
|
+
@store = store
|
21
|
+
@packer = pack_lib
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(key)
|
25
|
+
val = @store.get(key)
|
26
|
+
|
27
|
+
return val.nil? || val.empty? ? nil : @packer.unpack(val)
|
28
|
+
end
|
29
|
+
|
30
|
+
def set(key, val, expire = nil)
|
31
|
+
@store.set(key, @packer.pack(val))
|
32
|
+
expire(key, expire) unless expire.nil?
|
33
|
+
|
34
|
+
return val
|
35
|
+
end
|
36
|
+
|
37
|
+
def del(key)
|
38
|
+
@store.del(key)
|
39
|
+
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def expire(key, time)
|
44
|
+
@store.expire(key, time)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch(key, *args, &block)
|
48
|
+
get(key) || set(key, block.call(*args))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
Sequel::Plugins::Cacheable::DRIVERS.each do |driver|
|
55
|
+
require "sequel-cacheable/driver/#{driver}"
|
56
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Sequel::Plugins
|
4
|
+
module Cacheable
|
5
|
+
module InstanceMethods
|
6
|
+
def after_initialize
|
7
|
+
super
|
8
|
+
cache! unless id.nil?
|
9
|
+
end
|
10
|
+
|
11
|
+
def after_save
|
12
|
+
super
|
13
|
+
recache!
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(*args)
|
17
|
+
uncache!
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def destory(*args)
|
22
|
+
uncache!
|
23
|
+
super(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache!
|
27
|
+
model.cache_set(cache_key, self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def uncache!
|
31
|
+
model.cache_del(cache_key)
|
32
|
+
model.cache_clear(:query)
|
33
|
+
end
|
34
|
+
|
35
|
+
def recache!
|
36
|
+
uncache!
|
37
|
+
cache!
|
38
|
+
end
|
39
|
+
|
40
|
+
def cache_key
|
41
|
+
"#{self.id.to_s}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_msgpack(*args)
|
45
|
+
msgpack_hash.to_msgpack
|
46
|
+
end
|
47
|
+
|
48
|
+
def msgpack_hash
|
49
|
+
hash = {}
|
50
|
+
@values.each_pair do | key, value |
|
51
|
+
case value
|
52
|
+
when Date
|
53
|
+
value = [value.year, value.mon, value.mday, value.start]
|
54
|
+
when Sequel::SQLTime, Time
|
55
|
+
value = [value.to_i, value.usec]
|
56
|
+
when BigDecimal, Bignum
|
57
|
+
value = value.to_s
|
58
|
+
end
|
59
|
+
hash[key] = value
|
60
|
+
end
|
61
|
+
hash
|
62
|
+
end
|
63
|
+
private :msgpack_hash
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Sequel::Plugins
|
4
|
+
module Cacheable
|
5
|
+
class Packer
|
6
|
+
def self.factory(lib)
|
7
|
+
case lib
|
8
|
+
when MessagePack
|
9
|
+
MessagePackPacker.new
|
10
|
+
else
|
11
|
+
Packer.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def pack(obj)
|
16
|
+
Marshal.dump(obj)
|
17
|
+
end
|
18
|
+
|
19
|
+
def unpack(string)
|
20
|
+
Marshal.load(string)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class MessagePackPacker < Packer
|
25
|
+
def pack(obj)
|
26
|
+
obj.to_msgpack
|
27
|
+
end
|
28
|
+
|
29
|
+
def unpack(string)
|
30
|
+
MessagePack.unpack(string)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|