moneta 0.7.9 → 0.7.10
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -2
- data/CHANGES +6 -0
- data/Gemfile +2 -0
- data/README.md +7 -4
- data/Rakefile +2 -2
- data/lib/moneta.rb +1 -0
- data/lib/moneta/adapters/activerecord.rb +1 -2
- data/lib/moneta/adapters/cassandra.rb +31 -26
- data/lib/moneta/adapters/cookie.rb +2 -2
- data/lib/moneta/adapters/couch.rb +5 -3
- data/lib/moneta/adapters/daybreak.rb +13 -9
- data/lib/moneta/adapters/dbm.rb +7 -3
- data/lib/moneta/adapters/file.rb +4 -3
- data/lib/moneta/adapters/fog.rb +5 -2
- data/lib/moneta/adapters/gdbm.rb +7 -3
- data/lib/moneta/adapters/hbase.rb +11 -9
- data/lib/moneta/adapters/kyotocabinet.rb +13 -8
- data/lib/moneta/adapters/leveldb.rb +9 -5
- data/lib/moneta/adapters/localmemcache.rb +7 -3
- data/lib/moneta/adapters/memcached/dalli.rb +16 -11
- data/lib/moneta/adapters/memcached/native.rb +20 -15
- data/lib/moneta/adapters/memory.rb +2 -1
- data/lib/moneta/adapters/mongo.rb +13 -8
- data/lib/moneta/adapters/pstore.rb +24 -21
- data/lib/moneta/adapters/redis.rb +14 -12
- data/lib/moneta/adapters/restclient.rb +10 -7
- data/lib/moneta/adapters/riak.rb +5 -1
- data/lib/moneta/adapters/sdbm.rb +7 -3
- data/lib/moneta/adapters/sequel.rb +13 -8
- data/lib/moneta/adapters/sqlite.rb +15 -10
- data/lib/moneta/adapters/tdb.rb +8 -4
- data/lib/moneta/adapters/tokyocabinet.rb +15 -10
- data/lib/moneta/adapters/tokyotyrant.rb +107 -0
- data/lib/moneta/cache.rb +17 -17
- data/lib/moneta/mixins.rb +7 -5
- data/lib/moneta/version.rb +1 -1
- data/lib/rack/moneta_store.rb +1 -1
- data/lib/rack/session/moneta.rb +6 -3
- data/script/benchmarks +28 -28
- data/script/generate-specs +96 -41
- data/script/memusage +40 -0
- data/script/start-services +1 -0
- data/spec/helper.rb +4 -1
- data/spec/moneta/adapter_tokyotyrant_spec.rb +30 -0
- data/spec/moneta/cache_file_memory_spec.rb +3 -3
- data/spec/moneta/cache_memory_null_spec.rb +1 -1
- data/spec/moneta/simple_tokyotyrant_spec.rb +155 -0
- data/spec/moneta/simple_tokyotyrant_with_expires_spec.rb +157 -0
- data/spec/monetaspecs.rb +79 -37
- data/spec/rack/moneta_store_spec.rb +1 -1
- data/spec/rack/session_moneta_spec.rb +27 -0
- metadata +10 -3
@@ -7,48 +7,51 @@ module Moneta
|
|
7
7
|
class RestClient
|
8
8
|
include Defaults
|
9
9
|
|
10
|
+
attr_reader :backend
|
11
|
+
|
10
12
|
# @param [Hash] options
|
11
13
|
# @option options [String] :url URL
|
14
|
+
# @option options [::Net::HTTP] :backend Use existing backend instance
|
12
15
|
def initialize(options = {})
|
13
16
|
raise ArgumentError, 'Option :url is required' unless url = options[:url]
|
14
17
|
url = URI(url)
|
15
18
|
@path = url.path
|
16
|
-
@
|
19
|
+
@backend = options[:backend] || ::Net::HTTP.start(url.host, url.port)
|
17
20
|
end
|
18
21
|
|
19
22
|
# (see Proxy#key?)
|
20
23
|
def key?(key, options = {})
|
21
|
-
response = @
|
24
|
+
response = @backend.request_head(@path + key)
|
22
25
|
response.code == '200'
|
23
26
|
end
|
24
27
|
|
25
28
|
# (see Proxy#load)
|
26
29
|
def load(key, options = {})
|
27
|
-
response = @
|
30
|
+
response = @backend.request_get(@path + key)
|
28
31
|
response.code == '200' ? response.body : nil
|
29
32
|
end
|
30
33
|
|
31
34
|
# (see Proxy#store)
|
32
35
|
def store(key, value, options = {})
|
33
|
-
response = @
|
36
|
+
response = @backend.request_post(@path + key, value)
|
34
37
|
raise "HTTP error #{response.code}" unless response.code == '200'
|
35
38
|
value
|
36
39
|
end
|
37
40
|
|
38
41
|
# (see Proxy#delete)
|
39
42
|
def delete(key, options = {})
|
40
|
-
response = @
|
43
|
+
response = @backend.request(::Net::HTTP::Delete.new(@path + key))
|
41
44
|
response.code == '200' ? response.body : nil
|
42
45
|
end
|
43
46
|
|
44
47
|
# (see Proxy#clear)
|
45
48
|
def clear(options = {})
|
46
|
-
@
|
49
|
+
@backend.request(::Net::HTTP::Delete.new(@path))
|
47
50
|
self
|
48
51
|
end
|
49
52
|
|
50
53
|
def close
|
51
|
-
@
|
54
|
+
@backend.finish
|
52
55
|
nil
|
53
56
|
end
|
54
57
|
end
|
data/lib/moneta/adapters/riak.rb
CHANGED
@@ -10,14 +10,18 @@ module Moneta
|
|
10
10
|
class Riak
|
11
11
|
include Defaults
|
12
12
|
|
13
|
+
attr_reader :backend
|
14
|
+
|
13
15
|
# @param [Hash] options
|
14
16
|
# @option options [String] :bucket ('moneta') Bucket name
|
15
17
|
# @option options [String] :content_type ('application/octet-stream') Default content type
|
16
18
|
# @option options All other options passed to `Riak::Client#new`
|
19
|
+
# @option options [::Riak::Client] :backend Use existing backend instance
|
17
20
|
def initialize(options = {})
|
18
21
|
bucket = options.delete(:bucket) || 'moneta'
|
19
22
|
@content_type = options.delete(:content_type) || 'application/octet-stream'
|
20
|
-
@
|
23
|
+
@backend = options[:backend] || ::Riak::Client.new(options)
|
24
|
+
@bucket = @backend.bucket(bucket)
|
21
25
|
end
|
22
26
|
|
23
27
|
# (see Proxy#key?)
|
data/lib/moneta/adapters/sdbm.rb
CHANGED
@@ -7,14 +7,18 @@ module Moneta
|
|
7
7
|
class SDBM < Memory
|
8
8
|
# @param [Hash] options
|
9
9
|
# @option options [String] :file Database file
|
10
|
+
# @option options [::SDBM] :backend Use existing backend instance
|
10
11
|
def initialize(options = {})
|
11
|
-
|
12
|
-
|
12
|
+
@backend = options[:backend] ||
|
13
|
+
begin
|
14
|
+
raise ArgumentError, 'Option :file is required' unless options[:file]
|
15
|
+
::SDBM.new(options[:file])
|
16
|
+
end
|
13
17
|
end
|
14
18
|
|
15
19
|
# (see Proxy#close)
|
16
20
|
def close
|
17
|
-
@
|
21
|
+
@backend.close
|
18
22
|
nil
|
19
23
|
end
|
20
24
|
end
|
@@ -8,20 +8,25 @@ module Moneta
|
|
8
8
|
include Defaults
|
9
9
|
|
10
10
|
supports :create, :increment
|
11
|
+
attr_reader :backend
|
11
12
|
|
12
13
|
# @param [Hash] options
|
13
14
|
# @option options [String] :db Sequel database
|
14
15
|
# @option options [String/Symbol] :table (:moneta) Table name
|
15
16
|
# @option options All other options passed to `Sequel#connect`
|
17
|
+
# @option options [Sequel connection] :backend Use existing backend instance
|
16
18
|
def initialize(options = {})
|
17
|
-
raise ArgumentError, 'Option :db is required' unless db = options.delete(:db)
|
18
19
|
table = options.delete(:table) || :moneta
|
19
|
-
@
|
20
|
-
|
20
|
+
@backend = options[:backend] ||
|
21
|
+
begin
|
22
|
+
raise ArgumentError, 'Option :db is required' unless db = options.delete(:db)
|
23
|
+
::Sequel.connect(db, options)
|
24
|
+
end
|
25
|
+
@backend.create_table?(table) do
|
21
26
|
String :k, :null => false, :primary_key => true
|
22
27
|
String :v
|
23
28
|
end
|
24
|
-
@table = @
|
29
|
+
@table = @backend[table]
|
25
30
|
end
|
26
31
|
|
27
32
|
# (see Proxy#key?)
|
@@ -37,7 +42,7 @@ module Moneta
|
|
37
42
|
|
38
43
|
# (see Proxy#store)
|
39
44
|
def store(key, value, options = {})
|
40
|
-
@
|
45
|
+
@backend.transaction do
|
41
46
|
begin
|
42
47
|
@table.insert(:k => key, :v => value)
|
43
48
|
rescue ::Sequel::DatabaseError
|
@@ -52,7 +57,7 @@ module Moneta
|
|
52
57
|
|
53
58
|
# (see Proxy#store)
|
54
59
|
def create(key, value, options = {})
|
55
|
-
@
|
60
|
+
@backend.transaction do
|
56
61
|
@table.insert(:k => key, :v => value)
|
57
62
|
end
|
58
63
|
true
|
@@ -64,7 +69,7 @@ module Moneta
|
|
64
69
|
|
65
70
|
# (see Proxy#increment)
|
66
71
|
def increment(key, amount = 1, options = {})
|
67
|
-
@
|
72
|
+
@backend.transaction do
|
68
73
|
locked_table = @table.for_update
|
69
74
|
if record = locked_table[:k => key]
|
70
75
|
value = Utils.to_int(record[:v]) + amount
|
@@ -79,7 +84,7 @@ module Moneta
|
|
79
84
|
|
80
85
|
# (see Proxy#delete)
|
81
86
|
def delete(key, options = {})
|
82
|
-
@
|
87
|
+
@backend.transaction do
|
83
88
|
if value = load(key, options)
|
84
89
|
@table.filter(:k => key).delete
|
85
90
|
value
|
@@ -9,21 +9,26 @@ module Moneta
|
|
9
9
|
include IncrementSupport
|
10
10
|
|
11
11
|
supports :create
|
12
|
+
attr_reader :backend
|
12
13
|
|
13
14
|
# @param [Hash] options
|
14
15
|
# @option options [String] :file Database file
|
15
16
|
# @option options [String] :table ('moneta') Table name
|
17
|
+
# @option options [::Sqlite3::Database] :backend Use existing backend instance
|
16
18
|
def initialize(options = {})
|
17
|
-
raise ArgumentError, 'Option :file is required' unless options[:file]
|
18
19
|
table = options[:table] || 'moneta'
|
19
|
-
@
|
20
|
-
|
20
|
+
@backend = options[:backend] ||
|
21
|
+
begin
|
22
|
+
raise ArgumentError, 'Option :file is required' unless options[:file]
|
23
|
+
::SQLite3::Database.new(options[:file])
|
24
|
+
end
|
25
|
+
@backend.execute("create table if not exists #{table} (k blob not null primary key, v blob)")
|
21
26
|
@stmts =
|
22
|
-
[@select = @
|
23
|
-
@replace = @
|
24
|
-
@delete = @
|
25
|
-
@clear = @
|
26
|
-
@create = @
|
27
|
+
[@select = @backend.prepare("select v from #{table} where k = ?"),
|
28
|
+
@replace = @backend.prepare("replace into #{table} values (?, ?)"),
|
29
|
+
@delete = @backend.prepare("delete from #{table} where k = ?"),
|
30
|
+
@clear = @backend.prepare("delete from #{table}"),
|
31
|
+
@create = @backend.prepare("insert into #{table} values (?, ?)")]
|
27
32
|
end
|
28
33
|
|
29
34
|
# (see Proxy#key?)
|
@@ -52,7 +57,7 @@ module Moneta
|
|
52
57
|
|
53
58
|
# (see Proxy#increment)
|
54
59
|
def increment(key, amount = 1, options = {})
|
55
|
-
@
|
60
|
+
@backend.transaction(:exclusive) { return super }
|
56
61
|
end
|
57
62
|
|
58
63
|
# (see Proxy#clear)
|
@@ -75,7 +80,7 @@ module Moneta
|
|
75
80
|
# (see Proxy#close)
|
76
81
|
def close
|
77
82
|
@stmts.each {|s| s.close }
|
78
|
-
@
|
83
|
+
@backend.close
|
79
84
|
nil
|
80
85
|
end
|
81
86
|
end
|
data/lib/moneta/adapters/tdb.rb
CHANGED
@@ -7,14 +7,18 @@ module Moneta
|
|
7
7
|
class TDB < Memory
|
8
8
|
# @param [Hash] options
|
9
9
|
# @option options [String] :file Database file
|
10
|
-
|
11
|
-
|
12
|
-
@
|
10
|
+
# @option options [::TDB] :backend Use existing backend instance
|
11
|
+
def initialize(options)
|
12
|
+
@backend = options[:backend] ||
|
13
|
+
begin
|
14
|
+
raise ArgumentError, 'Option :file is required' unless file = options.delete(:file)
|
15
|
+
::TDB.new(file, options)
|
16
|
+
end
|
13
17
|
end
|
14
18
|
|
15
19
|
# (see Proxy#close)
|
16
20
|
def close
|
17
|
-
@
|
21
|
+
@backend.close
|
18
22
|
nil
|
19
23
|
end
|
20
24
|
end
|
@@ -8,34 +8,39 @@ module Moneta
|
|
8
8
|
# @param [Hash] options
|
9
9
|
# @option options [String] :file Database file
|
10
10
|
# @option options [Symbol] :type (:hdb) Database type (:bdb and :hdb possible)
|
11
|
+
# @option options [::TokyoCabinet::*DB] :backend Use existing backend instance
|
11
12
|
def initialize(options = {})
|
12
|
-
|
13
|
-
|
14
|
-
@hash = ::TokyoCabinet::BDB.new
|
15
|
-
@hash.open(options[:file], ::TokyoCabinet::BDB::OWRITER | ::TokyoCabinet::BDB::OCREAT)
|
13
|
+
if options[:backend]
|
14
|
+
@backend = options[:backend]
|
16
15
|
else
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
raise ArgumentError, 'Option :file is required' unless options[:file]
|
17
|
+
if options[:type] == :bdb
|
18
|
+
@backend = ::TokyoCabinet::BDB.new
|
19
|
+
@backend.open(options[:file], ::TokyoCabinet::BDB::OWRITER | ::TokyoCabinet::BDB::OCREAT)
|
20
|
+
else
|
21
|
+
@backend = ::TokyoCabinet::HDB.new
|
22
|
+
@backend.open(options[:file], ::TokyoCabinet::HDB::OWRITER | ::TokyoCabinet::HDB::OCREAT)
|
23
|
+
end or raise @backend.errmsg(@backend.ecode)
|
24
|
+
end
|
20
25
|
end
|
21
26
|
|
22
27
|
# (see Proxy#delete)
|
23
28
|
def delete(key, options = {})
|
24
29
|
value = load(key, options)
|
25
30
|
if value
|
26
|
-
@
|
31
|
+
@backend.delete(key)
|
27
32
|
value
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
31
36
|
# (see Proxy#create)
|
32
37
|
def create(key, value, options = {})
|
33
|
-
@
|
38
|
+
@backend.putkeep(key, value)
|
34
39
|
end
|
35
40
|
|
36
41
|
# (see Proxy#close)
|
37
42
|
def close
|
38
|
-
@
|
43
|
+
@backend.close
|
39
44
|
nil
|
40
45
|
end
|
41
46
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'tokyotyrant'
|
2
|
+
|
3
|
+
module Moneta
|
4
|
+
module Adapters
|
5
|
+
# TokyoTyrant backend
|
6
|
+
# @api public
|
7
|
+
class TokyoTyrant
|
8
|
+
include Defaults
|
9
|
+
|
10
|
+
supports :create, :increment
|
11
|
+
attr_reader :backend
|
12
|
+
|
13
|
+
# @param [Hash] options
|
14
|
+
# @option options [String] :host ('127.0.0.1') Server host name
|
15
|
+
# @option options [Integer] :port (1978) Server port
|
16
|
+
# @option options [::TokyoTyrant::RDB] :backend Use existing backend instance
|
17
|
+
def initialize(options = {})
|
18
|
+
if options[:backend]
|
19
|
+
@backend = options[:backend]
|
20
|
+
else
|
21
|
+
@backend = ::TokyoTyrant::RDB.new
|
22
|
+
@backend.open(options[:host] || '127.0.0.1',
|
23
|
+
options[:port] || 1978) or raise @backend.errmsg(@backend.ecode)
|
24
|
+
end
|
25
|
+
probe = '__tokyotyrant_endianness_probe'
|
26
|
+
@backend.delete(probe)
|
27
|
+
@backend.addint(probe, 1)
|
28
|
+
@pack = @backend.delete(probe) == [1].pack('l>') ? 'l>' : 'l<'
|
29
|
+
end
|
30
|
+
|
31
|
+
# (see Proxy#key?)
|
32
|
+
def key?(key, options = {})
|
33
|
+
@backend.has_key?(key)
|
34
|
+
end
|
35
|
+
|
36
|
+
# (see Proxy#load)
|
37
|
+
def load(key, options = {})
|
38
|
+
value = @backend[key]
|
39
|
+
value && unpack(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
# (see Proxy#store)
|
43
|
+
def store(key, value, options = {})
|
44
|
+
@backend[key] = pack(value)
|
45
|
+
value
|
46
|
+
end
|
47
|
+
|
48
|
+
# (see Proxy#delete)
|
49
|
+
def delete(key, options = {})
|
50
|
+
value = load(key, options)
|
51
|
+
if value
|
52
|
+
@backend.delete(key)
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# (see Proxy#increment)
|
58
|
+
def increment(key, amount = 1, options = {})
|
59
|
+
@backend.addint(key, amount) || raise('Tried to increment non integer value')
|
60
|
+
end
|
61
|
+
|
62
|
+
# (see Proxy#create)
|
63
|
+
def create(key, value, options = {})
|
64
|
+
@backend.putkeep(key, pack(value))
|
65
|
+
end
|
66
|
+
|
67
|
+
# (see Proxy#clear)
|
68
|
+
def clear(options = {})
|
69
|
+
@backend.clear
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
# (see Proxy#close)
|
74
|
+
def close
|
75
|
+
@backend.close
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def pack(value)
|
82
|
+
intvalue = value.to_i
|
83
|
+
if intvalue >= 0 && intvalue <= 0xFFFFFFFF && intvalue.to_s == value
|
84
|
+
# Pack as 4 byte integer
|
85
|
+
[intvalue].pack(@pack)
|
86
|
+
elsif value.bytesize >= 4
|
87
|
+
# Add nul character to make value distinguishable from integer
|
88
|
+
value + "\0"
|
89
|
+
else
|
90
|
+
value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def unpack(value)
|
95
|
+
if value.bytesize == 4
|
96
|
+
# Unpack 4 byte integer
|
97
|
+
value.unpack(@pack).first.to_s
|
98
|
+
elsif value.bytesize >= 5 && value[-1] == ?\0
|
99
|
+
# Remove nul character
|
100
|
+
value[0..-2]
|
101
|
+
else
|
102
|
+
value
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/moneta/cache.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Moneta
|
2
|
-
# Combines two stores. One is used as cache, the other as backend.
|
2
|
+
# Combines two stores. One is used as cache, the other as backend adapter.
|
3
3
|
#
|
4
4
|
# @example Add `Moneta::Cache` to proxy stack
|
5
5
|
# Moneta.build do
|
6
6
|
# use(:Cache) do
|
7
|
-
#
|
7
|
+
# adapter { adapter :File, :dir => 'data' }
|
8
8
|
# cache { adapter :Memory }
|
9
9
|
# end
|
10
10
|
# end
|
@@ -21,10 +21,10 @@ module Moneta
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# @api public
|
24
|
-
def
|
25
|
-
raise '
|
24
|
+
def adapter(store = nil, &block)
|
25
|
+
raise 'Adapter already set' if @store.adapter
|
26
26
|
raise ArgumentError, 'Only argument or block allowed' if store && block
|
27
|
-
@store.
|
27
|
+
@store.adapter = store || Moneta.build(&block)
|
28
28
|
end
|
29
29
|
|
30
30
|
# @api public
|
@@ -35,27 +35,27 @@ module Moneta
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
attr_accessor :cache, :
|
38
|
+
attr_accessor :cache, :adapter
|
39
39
|
|
40
40
|
# @param [Hash] options Options hash
|
41
41
|
# @option options [Moneta store] :cache Moneta store used as cache
|
42
|
-
# @option options [Moneta store] :
|
42
|
+
# @option options [Moneta store] :adapter Moneta store used as adapter
|
43
43
|
# @yieldparam Builder block
|
44
44
|
def initialize(options = {}, &block)
|
45
|
-
@cache, @
|
45
|
+
@cache, @adapter = options[:cache], options[:adapter]
|
46
46
|
DSL.new(self, &block) if block_given?
|
47
47
|
end
|
48
48
|
|
49
49
|
# (see Proxy#key?)
|
50
50
|
def key?(key, options = {})
|
51
|
-
@cache.key?(key, options) || @
|
51
|
+
@cache.key?(key, options) || @adapter.key?(key, options)
|
52
52
|
end
|
53
53
|
|
54
54
|
# (see Proxy#load)
|
55
55
|
def load(key, options = {})
|
56
56
|
value = @cache.load(key, options)
|
57
57
|
if value == nil
|
58
|
-
value = @
|
58
|
+
value = @adapter.load(key, options)
|
59
59
|
@cache.store(key, value, options) if value != nil
|
60
60
|
end
|
61
61
|
value
|
@@ -64,18 +64,18 @@ module Moneta
|
|
64
64
|
# (see Proxy#store)
|
65
65
|
def store(key, value, options = {})
|
66
66
|
@cache.store(key, value, options)
|
67
|
-
@
|
67
|
+
@adapter.store(key, value, options)
|
68
68
|
end
|
69
69
|
|
70
70
|
# (see Proxy#increment)
|
71
71
|
def increment(key, amount = 1, options = {})
|
72
72
|
@cache.delete(key, options)
|
73
|
-
@
|
73
|
+
@adapter.increment(key, amount, options)
|
74
74
|
end
|
75
75
|
|
76
76
|
# (see Proxy#create)
|
77
77
|
def create(key, value, options = {})
|
78
|
-
if @
|
78
|
+
if @adapter.create(key, value, options)
|
79
79
|
@cache.store(key, value, options)
|
80
80
|
true
|
81
81
|
else
|
@@ -86,25 +86,25 @@ module Moneta
|
|
86
86
|
# (see Proxy#delete)
|
87
87
|
def delete(key, options = {})
|
88
88
|
@cache.delete(key, options)
|
89
|
-
@
|
89
|
+
@adapter.delete(key, options)
|
90
90
|
end
|
91
91
|
|
92
92
|
# (see Proxy#clear)
|
93
93
|
def clear(options = {})
|
94
94
|
@cache.clear(options)
|
95
|
-
@
|
95
|
+
@adapter.clear(options)
|
96
96
|
self
|
97
97
|
end
|
98
98
|
|
99
99
|
# (see Proxy#close)
|
100
100
|
def close
|
101
101
|
@cache.close
|
102
|
-
@
|
102
|
+
@adapter.close
|
103
103
|
end
|
104
104
|
|
105
105
|
# (see Proxy#features)
|
106
106
|
def features
|
107
|
-
@features ||= ((@cache.features + [:create, :increment]) & @
|
107
|
+
@features ||= ((@cache.features + [:create, :increment]) & @adapter.features).freeze
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|