moneta 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +26 -8
  3. data/CHANGES +6 -0
  4. data/CONTRIBUTORS +2 -1
  5. data/Gemfile +7 -5
  6. data/README.md +2 -6
  7. data/feature_matrix.yaml +0 -10
  8. data/lib/moneta.rb +9 -9
  9. data/lib/moneta/adapters/mongo.rb +256 -7
  10. data/lib/moneta/adapters/redis.rb +5 -1
  11. data/lib/moneta/adapters/sequel.rb +45 -464
  12. data/lib/moneta/adapters/sequel/mysql.rb +66 -0
  13. data/lib/moneta/adapters/sequel/postgres.rb +80 -0
  14. data/lib/moneta/adapters/sequel/postgres_hstore.rb +240 -0
  15. data/lib/moneta/adapters/sequel/sqlite.rb +57 -0
  16. data/lib/moneta/adapters/sqlite.rb +7 -7
  17. data/lib/moneta/create_support.rb +21 -0
  18. data/lib/moneta/dbm_adapter.rb +31 -0
  19. data/lib/moneta/{mixins.rb → defaults.rb} +1 -302
  20. data/lib/moneta/each_key_support.rb +27 -0
  21. data/lib/moneta/expires_support.rb +60 -0
  22. data/lib/moneta/hash_adapter.rb +68 -0
  23. data/lib/moneta/increment_support.rb +16 -0
  24. data/lib/moneta/nil_values.rb +35 -0
  25. data/lib/moneta/option_support.rb +51 -0
  26. data/lib/moneta/transformer/helper/bson.rb +5 -15
  27. data/lib/moneta/version.rb +1 -1
  28. data/lib/rack/cache/moneta.rb +14 -15
  29. data/moneta.gemspec +7 -9
  30. data/script/benchmarks +1 -2
  31. data/script/contributors +11 -6
  32. data/spec/active_support/cache_moneta_store_spec.rb +27 -29
  33. data/spec/features/concurrent_increment.rb +2 -3
  34. data/spec/features/create_expires.rb +15 -15
  35. data/spec/features/default_expires.rb +11 -12
  36. data/spec/features/expires.rb +215 -210
  37. data/spec/helper.rb +16 -33
  38. data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +16 -1
  39. data/spec/moneta/adapters/mongo/adapter_mongo_with_default_expires_spec.rb +1 -1
  40. data/spec/moneta/adapters/mongo/standard_mongo_spec.rb +1 -1
  41. data/spec/moneta/adapters/sequel/adapter_sequel_spec.rb +7 -34
  42. data/spec/moneta/adapters/sequel/helper.rb +37 -0
  43. data/spec/moneta/adapters/sequel/standard_sequel_spec.rb +4 -10
  44. data/spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb +7 -8
  45. data/spec/moneta/proxies/shared/shared_unix_spec.rb +10 -0
  46. data/spec/restserver.rb +15 -0
  47. metadata +39 -58
  48. data/lib/moneta/adapters/mongo/base.rb +0 -103
  49. data/lib/moneta/adapters/mongo/moped.rb +0 -166
  50. data/lib/moneta/adapters/mongo/official.rb +0 -156
  51. data/spec/moneta/adapters/mongo/adapter_mongo_moped_spec.rb +0 -26
  52. data/spec/moneta/adapters/mongo/adapter_mongo_moped_with_default_expires_spec.rb +0 -14
  53. data/spec/moneta/adapters/mongo/adapter_mongo_official_spec.rb +0 -27
  54. data/spec/moneta/adapters/mongo/adapter_mongo_official_with_default_expires_spec.rb +0 -14
  55. data/spec/moneta/adapters/mongo/standard_mongo_moped_spec.rb +0 -7
  56. data/spec/moneta/adapters/mongo/standard_mongo_official_spec.rb +0 -7
  57. data/spec/quality_spec.rb +0 -51
@@ -1,103 +0,0 @@
1
- require 'bson'
2
-
3
- module Moneta
4
- module Adapters
5
- # @api private
6
- class MongoBase
7
- include Defaults
8
- include ExpiresSupport
9
-
10
- supports :each_key, :create, :increment
11
- attr_reader :backend
12
-
13
- DEFAULT_PORT = 27017
14
-
15
- def initialize(options = {})
16
- self.default_expires = options.delete(:expires)
17
- @expires_field = options.delete(:expires_field) || 'expiresAt'
18
- @value_field = options.delete(:value_field) || 'value'
19
- @type_field = options.delete(:type_field) || 'type'
20
- end
21
-
22
- # (see Proxy#fetch_values)
23
- def fetch_values(*keys, **options)
24
- return values_at(*keys, **options) unless block_given?
25
- hash = Hash[slice(*keys, **options)]
26
- keys.map do |key|
27
- if hash.key?(key)
28
- hash[key]
29
- else
30
- yield key
31
- end
32
- end
33
- end
34
-
35
- # (see Proxy#values_at)
36
- def values_at(*keys, **options)
37
- hash = Hash[slice(*keys, **options)]
38
- keys.map { |key| hash[key] }
39
- end
40
-
41
- protected
42
-
43
- def doc_to_value(doc)
44
- case doc[@type_field]
45
- when 'Hash'
46
- doc = doc.dup
47
- doc.delete('_id')
48
- doc.delete(@type_field)
49
- doc.delete(@expires_field)
50
- doc
51
- when 'Number'
52
- doc[@value_field]
53
- else
54
- # In ruby_bson version 2 (and probably up), #to_s no longer returns the binary data
55
- from_binary(doc[@value_field])
56
- end
57
- end
58
-
59
- def value_to_doc(key, value, options)
60
- case value
61
- when Hash
62
- value.merge('_id' => key,
63
- @type_field => 'Hash',
64
- # @expires_field must be a Time object (BSON date datatype)
65
- @expires_field => expires_at(options) || nil)
66
- when Float, Integer
67
- { '_id' => key,
68
- @type_field => 'Number',
69
- @value_field => value,
70
- # @expires_field must be a Time object (BSON date datatype)
71
- @expires_field => expires_at(options) || nil }
72
- when String
73
- intvalue = value.to_i
74
- { '_id' => key,
75
- @type_field => 'String',
76
- @value_field => intvalue.to_s == value ? intvalue : to_binary(value),
77
- # @expires_field must be a Time object (BSON date datatype)
78
- @expires_field => expires_at(options) || nil }
79
- else
80
- raise ArgumentError, "Invalid value type: #{value.class}"
81
- end
82
- end
83
-
84
- # BSON will use String#force_encoding to make the string 8-bit
85
- # ASCII. This could break unicode text so we should dup in this
86
- # case, and it also fails with frozen strings.
87
- def to_binary(str)
88
- str = str.dup if str.frozen? || str.encoding != Encoding::ASCII_8BIT
89
- ::BSON::Binary.new(str)
90
- end
91
-
92
- if defined?(::BSON::VERSION) and ::BSON::VERSION[0].to_i >= 2
93
- def from_binary(binary)
94
- binary.is_a?(::BSON::Binary) ? binary.data : binary.to_s
95
- end
96
- else
97
- def from_binary(binary)
98
- binary.to_s
99
- end
100
- end
101
- end
102
- end
103
- end
@@ -1,166 +0,0 @@
1
- require 'moneta/adapters/mongo/base'
2
- require 'moped'
3
-
4
- module Moneta
5
- module Adapters
6
- # MongoDB backend
7
- #
8
- # Supports expiration, documents will be automatically removed starting
9
- # with mongodb >= 2.2 (see {http://docs.mongodb.org/manual/tutorial/expire-data/}).
10
- #
11
- # You can store hashes directly using this adapter.
12
- #
13
- # @example Store hashes
14
- # db = Moneta::Adapters::MongoMoped.new
15
- # db['key'] = {a: 1, b: 2}
16
- #
17
- # @api public
18
- class MongoMoped < MongoBase
19
- # @param [Hash] options
20
- # @option options [String] :collection ('moneta') MongoDB collection name
21
- # @option options [String] :host ('127.0.0.1') MongoDB server host
22
- # @option options [String] :user Username used to authenticate
23
- # @option options [String] :password Password used to authenticate
24
- # @option options [Integer] :port (MongoDB default port) MongoDB server port
25
- # @option options [String] :db ('moneta') MongoDB database
26
- # @option options [Integer] :expires Default expiration time
27
- # @option options [String] :expires_field ('expiresAt') Document field to store expiration time
28
- # @option options [String] :value_field ('value') Document field to store value
29
- # @option options [String] :type_field ('type') Document field to store value type
30
- # @option options [::Moped::Session] :backend Use existing backend instance
31
- # @option options Other options passed to `Moped::Session#new`
32
- def initialize(options = {})
33
- super(options)
34
- collection = options.delete(:collection) || 'moneta'
35
- db = options.delete(:db) || 'moneta'
36
- user = options.delete(:user)
37
- password = options.delete(:password)
38
- @backend = options[:backend] ||
39
- begin
40
- host = options.delete(:host) || '127.0.0.1'
41
- port = options.delete(:port) || DEFAULT_PORT
42
- ::Moped::Session.new(["#{host}:#{port}"])
43
- end
44
- @backend.use(db)
45
- @backend.login(user, password) if user && password
46
- @collection = @backend[collection]
47
- if @backend.command(buildinfo: 1)['version'] >= '3.0'
48
- # Moped creates indexes in the system.indexes collection which is not writable anymore since Mongo v3
49
- warn 'Moneta::Adapters::MongoMoped - You are using the unmaintained Moped gem, expired documents will not be deleted'
50
- elsif @backend.command(buildinfo: 1)['version'] >= '2.2'
51
- @collection.indexes.create({ @expires_field => 1 }, expireAfterSeconds: 0)
52
- else
53
- warn 'Moneta::Adapters::Mongo - You are using MongoDB version < 2.2, expired documents will not be deleted'
54
- end
55
- end
56
-
57
- # (see Proxy#load)
58
- def load(key, options = {})
59
- key = to_binary(key)
60
- doc = @collection.find(_id: key).one
61
- if doc && (!doc[@expires_field] || doc[@expires_field] >= Time.now)
62
- # @expires_field must be a Time object (BSON date datatype)
63
- expires = expires_at(options, nil)
64
- @collection.find(_id: key).update(:$set => { @expires_field => expires || nil }) if expires != nil
65
- doc_to_value(doc)
66
- end
67
- end
68
-
69
- # (see Proxy#store)
70
- def store(key, value, options = {})
71
- key = to_binary(key)
72
- @collection.find(_id: key).upsert(value_to_doc(key, value, options))
73
- value
74
- end
75
-
76
- # (see Proxy#each_key)
77
- def each_key
78
- return enum_for(:each_key) unless block_given?
79
- @collection.find.each { |doc| yield from_binary(doc[:_id]) }
80
- self
81
- end
82
-
83
- # (see Proxy#delete)
84
- def delete(key, options = {})
85
- value = load(key, options)
86
- @collection.find(_id: to_binary(key)).remove if value
87
- value
88
- end
89
-
90
- # (see Proxy#increment)
91
- def increment(key, amount = 1, options = {})
92
- @backend.with(safe: true, consistency: :strong) do |safe|
93
- safe[@collection.name]
94
- .find(_id: to_binary(key))
95
- .modify({ :$inc => { @value_field => amount } },
96
- new: true, upsert: true)[@value_field]
97
- end
98
- rescue ::Moped::Errors::OperationFailure
99
- tries ||= 0
100
- tries += 1
101
- retry if tries < 3
102
- raise # otherwise
103
- end
104
-
105
- # (see Proxy#create)
106
- def create(key, value, options = {})
107
- key = to_binary(key)
108
- @backend.with(safe: true, consistency: :strong) do |safe|
109
- safe[@collection.name].insert(value_to_doc(key, value, options))
110
- end
111
- true
112
- rescue ::Moped::Errors::MongoError => ex
113
- raise if ex.details['code'] != 11000 # duplicate key error
114
- false
115
- end
116
-
117
- # (see Proxy#clear)
118
- def clear(options = {})
119
- @collection.find.remove_all
120
- self
121
- end
122
-
123
- # (see Proxy#slice)
124
- def slice(*keys, **options)
125
- query = @collection.find(_id: { :$in => keys.map(&method(:to_binary)) })
126
- pairs = query.map do |doc|
127
- next if doc[@expires_field] && doc[@expires_field] < Time.now
128
- [from_binary(doc[:_id]), doc_to_value(doc)]
129
- end.compact
130
-
131
- if (expires = expires_at(options, nil)) != nil
132
- query.update_all(:$set => { @expires_field => expires || nil })
133
- end
134
-
135
- pairs
136
- end
137
-
138
- # (see Proxy#merge!)
139
- def merge!(pairs, options = {})
140
- @backend.with(safe: true, consistency: :strong) do |safe|
141
- collection = safe[@collection.name]
142
- existing = collection
143
- .find(_id: { :$in => pairs.map { |key, _| to_binary(key) }.to_a })
144
- .map { |doc| [from_binary(doc[:_id]), doc_to_value(doc)] }
145
- .to_h
146
-
147
- update_pairs, insert_pairs = pairs.partition { |key, _| existing.key?(key) }
148
- unless insert_pairs.empty?
149
- collection.insert(insert_pairs.map do |key, value|
150
- value_to_doc(to_binary(key), value, options)
151
- end)
152
- end
153
-
154
- update_pairs.each do |key, value|
155
- value = yield(key, existing[key], value) if block_given?
156
- binary = to_binary(key)
157
- collection
158
- .find(_id: binary)
159
- .update(value_to_doc(binary, value, options))
160
- end
161
- end
162
- self
163
- end
164
- end
165
- end
166
- end
@@ -1,156 +0,0 @@
1
- require 'moneta/adapters/mongo/base'
2
- require 'mongo'
3
-
4
- module Moneta
5
- module Adapters
6
- # MongoDB backend
7
- #
8
- # Supports expiration, documents will be automatically removed starting
9
- # with mongodb >= 2.2 (see {http://docs.mongodb.org/manual/tutorial/expire-data/}).
10
- #
11
- # You can store hashes directly using this adapter.
12
- #
13
- # @example Store hashes
14
- # db = Moneta::Adapters::MongoOfficial.new
15
- # db['key'] = {a: 1, b: 2}
16
- #
17
- # @api public
18
- class MongoOfficial < MongoBase
19
- # @param [Hash] options
20
- # @option options [String] :collection ('moneta') MongoDB collection name
21
- # @option options [String] :host ('127.0.0.1') MongoDB server host
22
- # @option options [String] :user Username used to authenticate
23
- # @option options [String] :password Password used to authenticate
24
- # @option options [Integer] :port (MongoDB default port) MongoDB server port
25
- # @option options [String] :db ('moneta') MongoDB database
26
- # @option options [Integer] :expires Default expiration time
27
- # @option options [String] :expires_field ('expiresAt') Document field to store expiration time
28
- # @option options [String] :value_field ('value') Document field to store value
29
- # @option options [String] :type_field ('type') Document field to store value type
30
- # @option options [::Mongo::Client] :backend Use existing backend instance
31
- # @option options Other options passed to `Mongo::MongoClient#new`
32
- def initialize(options = {})
33
- super(options)
34
- collection = options.delete(:collection) || 'moneta'
35
- db = options.delete(:db) || 'moneta'
36
- @backend = options[:backend] ||
37
- begin
38
- host = options.delete(:host) || '127.0.0.1'
39
- port = options.delete(:port) || DEFAULT_PORT
40
- options[:logger] ||= ::Logger.new(STDERR).tap do |logger|
41
- logger.level = ::Logger::ERROR
42
- end
43
- ::Mongo::Client.new(["#{host}:#{port}"], options)
44
- end
45
- @backend.use(db)
46
- @collection = @backend[collection]
47
- if @backend.command(buildinfo: 1).documents.first['version'] >= '2.2'
48
- @collection.indexes.create_one({ @expires_field => 1 }, expire_after: 0)
49
- else
50
- warn 'Moneta::Adapters::Mongo - You are using MongoDB version < 2.2, expired documents will not be deleted'
51
- end
52
- end
53
-
54
- # (see Proxy#load)
55
- def load(key, options = {})
56
- key = to_binary(key)
57
- doc = @collection.find(_id: key).limit(1).first
58
- if doc && (!doc[@expires_field] || doc[@expires_field] >= Time.now)
59
- expires = expires_at(options, nil)
60
- # @expires_field must be a Time object (BSON date datatype)
61
- @collection.update_one({ _id: key },
62
- '$set' => { @expires_field => expires }) unless expires == nil
63
- doc_to_value(doc)
64
- end
65
- end
66
-
67
- # (see Proxy#store)
68
- def store(key, value, options = {})
69
- key = to_binary(key)
70
- @collection.replace_one({ _id: key },
71
- value_to_doc(key, value, options),
72
- upsert: true)
73
- value
74
- end
75
-
76
- # (see Proxy#each_key)
77
- def each_key
78
- return enum_for(:each_key) unless block_given?
79
- @collection.find.each { |doc| yield from_binary(doc[:_id]) }
80
- self
81
- end
82
-
83
- # (see Proxy#delete)
84
- def delete(key, options = {})
85
- key = to_binary(key)
86
- if doc = @collection.find(_id: key).find_one_and_delete and
87
- !doc[@expires_field] || doc[@expires_field] >= Time.now
88
- doc_to_value(doc)
89
- end
90
- end
91
-
92
- # (see Proxy#increment)
93
- def increment(key, amount = 1, options = {})
94
- @collection.find_one_and_update({ _id: to_binary(key) },
95
- { '$inc' => { @value_field => amount } },
96
- return_document: :after,
97
- upsert: true)[@value_field]
98
- end
99
-
100
- # (see Proxy#create)
101
- def create(key, value, options = {})
102
- key = to_binary(key)
103
- @collection.insert_one(value_to_doc(key, value, options))
104
- true
105
- rescue ::Mongo::Error::OperationFailure => ex
106
- raise unless ex.message =~ /^E11000 / # duplicate key error
107
- false
108
- end
109
-
110
- # (see Proxy#clear)
111
- def clear(options = {})
112
- @collection.delete_many
113
- self
114
- end
115
-
116
- # (see Proxy#close)
117
- def close
118
- @backend.close
119
- nil
120
- end
121
-
122
- # (see Proxy#slice)
123
- def slice(*keys, **options)
124
- query = @collection.find(_id: { :$in => keys.map(&method(:to_binary)) })
125
- pairs = query.map do |doc|
126
- next if doc[@expires_field] && doc[@expires_field] < Time.now
127
- [from_binary(doc[:_id]), doc_to_value(doc)]
128
- end.compact
129
-
130
- if (expires = expires_at(options, nil)) != nil
131
- query.update_many(:$set => { @expires_field => expires || nil })
132
- end
133
-
134
- pairs
135
- end
136
-
137
- # (see Proxy#merge!)
138
- def merge!(pairs, options = {})
139
- existing = Hash[slice(*pairs.map { |key, _| key })]
140
- update_pairs, insert_pairs = pairs.partition { |key, _| existing.key?(key) }
141
-
142
- @collection.insert_many(insert_pairs.map do |key, value|
143
- value_to_doc(to_binary(key), value, options)
144
- end)
145
-
146
- update_pairs.each do |key, value|
147
- value = yield(key, existing[key], value) if block_given?
148
- binary = to_binary(key)
149
- @collection.replace_one({ _id: binary }, value_to_doc(binary, value, options))
150
- end
151
-
152
- self
153
- end
154
- end
155
- end
156
- end
@@ -1,26 +0,0 @@
1
- describe 'adapter_mongo_moped', adapter: :Mongo do
2
- let(:t_res) { 0.125 }
3
- let(:min_ttl) { t_res }
4
-
5
- moneta_build do
6
- Moneta::Adapters::MongoMoped.new(mongo_config(
7
- db: File.basename(__FILE__, '.rb'), collection: 'moped'
8
- ))
9
- end
10
-
11
- moneta_specs ADAPTER_SPECS.with_each_key.with_native_expires.simplevalues_only
12
-
13
- it 'automatically deletes expired document', unsupported: true do
14
- store.store('key', 'val', expires: 5)
15
-
16
- i = 0
17
- query = store.instance_variable_get(:@collection).find(_id: ::BSON::Binary.new('key'))
18
- while i < 70 && query.first
19
- i += 1
20
- sleep 1 # Mongo needs up to 60 seconds
21
- end
22
-
23
- i.should be > 0 # Indicates that it took at least one sleep to expire
24
- query.count.should == 0
25
- end
26
- end