moneta 1.3.0 → 1.4.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.
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