moneta 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +26 -8
- data/CHANGES +6 -0
- data/CONTRIBUTORS +2 -1
- data/Gemfile +7 -5
- data/README.md +2 -6
- data/feature_matrix.yaml +0 -10
- data/lib/moneta.rb +9 -9
- data/lib/moneta/adapters/mongo.rb +256 -7
- data/lib/moneta/adapters/redis.rb +5 -1
- data/lib/moneta/adapters/sequel.rb +45 -464
- data/lib/moneta/adapters/sequel/mysql.rb +66 -0
- data/lib/moneta/adapters/sequel/postgres.rb +80 -0
- data/lib/moneta/adapters/sequel/postgres_hstore.rb +240 -0
- data/lib/moneta/adapters/sequel/sqlite.rb +57 -0
- data/lib/moneta/adapters/sqlite.rb +7 -7
- data/lib/moneta/create_support.rb +21 -0
- data/lib/moneta/dbm_adapter.rb +31 -0
- data/lib/moneta/{mixins.rb → defaults.rb} +1 -302
- data/lib/moneta/each_key_support.rb +27 -0
- data/lib/moneta/expires_support.rb +60 -0
- data/lib/moneta/hash_adapter.rb +68 -0
- data/lib/moneta/increment_support.rb +16 -0
- data/lib/moneta/nil_values.rb +35 -0
- data/lib/moneta/option_support.rb +51 -0
- data/lib/moneta/transformer/helper/bson.rb +5 -15
- data/lib/moneta/version.rb +1 -1
- data/lib/rack/cache/moneta.rb +14 -15
- data/moneta.gemspec +7 -9
- data/script/benchmarks +1 -2
- data/script/contributors +11 -6
- data/spec/active_support/cache_moneta_store_spec.rb +27 -29
- data/spec/features/concurrent_increment.rb +2 -3
- data/spec/features/create_expires.rb +15 -15
- data/spec/features/default_expires.rb +11 -12
- data/spec/features/expires.rb +215 -210
- data/spec/helper.rb +16 -33
- data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +16 -1
- data/spec/moneta/adapters/mongo/adapter_mongo_with_default_expires_spec.rb +1 -1
- data/spec/moneta/adapters/mongo/standard_mongo_spec.rb +1 -1
- data/spec/moneta/adapters/sequel/adapter_sequel_spec.rb +7 -34
- data/spec/moneta/adapters/sequel/helper.rb +37 -0
- data/spec/moneta/adapters/sequel/standard_sequel_spec.rb +4 -10
- data/spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb +7 -8
- data/spec/moneta/proxies/shared/shared_unix_spec.rb +10 -0
- data/spec/restserver.rb +15 -0
- metadata +39 -58
- data/lib/moneta/adapters/mongo/base.rb +0 -103
- data/lib/moneta/adapters/mongo/moped.rb +0 -166
- data/lib/moneta/adapters/mongo/official.rb +0 -156
- data/spec/moneta/adapters/mongo/adapter_mongo_moped_spec.rb +0 -26
- data/spec/moneta/adapters/mongo/adapter_mongo_moped_with_default_expires_spec.rb +0 -14
- data/spec/moneta/adapters/mongo/adapter_mongo_official_spec.rb +0 -27
- data/spec/moneta/adapters/mongo/adapter_mongo_official_with_default_expires_spec.rb +0 -14
- data/spec/moneta/adapters/mongo/standard_mongo_moped_spec.rb +0 -7
- data/spec/moneta/adapters/mongo/standard_mongo_official_spec.rb +0 -7
- data/spec/quality_spec.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 667e187cf3e0d0ce7e721e07036f70e0b65f1b52818fc09eb87311872896ce95
|
4
|
+
data.tar.gz: 7e9063dfa4ea6f1bfbe4bee54821c75e9a82d0f54f3f820c6e396fafeeba006a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e2d3880c0d3fed1ee66cb57f5ebba13a83dd7e4447ec1e99e601a85ff9bc454cf7bf4e9d62c8672324faddf9f396e4488c08400a08e1c1dd0181056655142b5
|
7
|
+
data.tar.gz: 4be30ddd9249b7222c2354984815efde88ca3c8cfe65db26c13df1585bd80425cc8f55f68b869d07016183be29b4ae8495a3408fe3b67e0f27d21ef2f6de6a83
|
data/.rubocop.yml
CHANGED
@@ -6,11 +6,7 @@ AllCops:
|
|
6
6
|
- script/**/*
|
7
7
|
- vendor/**/*
|
8
8
|
|
9
|
-
Layout/
|
10
|
-
Exclude:
|
11
|
-
- lib/moneta/transformer/config.rb
|
12
|
-
|
13
|
-
Layout/AlignHash:
|
9
|
+
Layout/ArrayAlignment:
|
14
10
|
Exclude:
|
15
11
|
- lib/moneta/transformer/config.rb
|
16
12
|
|
@@ -25,6 +21,13 @@ Layout/ExtraSpacing:
|
|
25
21
|
- lib/moneta.rb
|
26
22
|
- lib/moneta/transformer/config.rb
|
27
23
|
|
24
|
+
Layout/HashAlignment:
|
25
|
+
Exclude:
|
26
|
+
- lib/moneta/transformer/config.rb
|
27
|
+
|
28
|
+
Layout/LineLength:
|
29
|
+
Max: 160
|
30
|
+
|
28
31
|
Layout/MultilineMethodCallIndentation:
|
29
32
|
EnforcedStyle: indented
|
30
33
|
|
@@ -38,12 +41,18 @@ Layout/SpaceInsideArrayLiteralBrackets:
|
|
38
41
|
Lint/AssignmentInCondition:
|
39
42
|
Enabled: false
|
40
43
|
|
44
|
+
Lint/RaiseException:
|
45
|
+
Enabled: true
|
46
|
+
|
41
47
|
Lint/ShadowedException:
|
42
48
|
Enabled: false
|
43
49
|
|
44
50
|
Lint/ShadowingOuterLocalVariable:
|
45
51
|
Enabled: false
|
46
52
|
|
53
|
+
Lint/StructNewOverride:
|
54
|
+
Enabled: true
|
55
|
+
|
47
56
|
Lint/UnusedMethodArgument:
|
48
57
|
Enabled: false
|
49
58
|
|
@@ -67,12 +76,12 @@ Metrics/BlockLength:
|
|
67
76
|
Exclude:
|
68
77
|
- spec/**/*
|
69
78
|
|
70
|
-
Metrics/LineLength:
|
71
|
-
Max: 160
|
72
|
-
|
73
79
|
Metrics/MethodLength:
|
74
80
|
Enabled: false
|
75
81
|
|
82
|
+
Metrics/ModuleLength:
|
83
|
+
Enabled: false
|
84
|
+
|
76
85
|
Metrics/ParameterLists:
|
77
86
|
Enabled: false
|
78
87
|
|
@@ -117,6 +126,15 @@ Style/FormatString:
|
|
117
126
|
Style/GuardClause:
|
118
127
|
Enabled: false
|
119
128
|
|
129
|
+
Style/HashEachMethods:
|
130
|
+
Enabled: true
|
131
|
+
|
132
|
+
Style/HashTransformKeys:
|
133
|
+
Enabled: true
|
134
|
+
|
135
|
+
Style/HashTransformValues:
|
136
|
+
Enabled: true
|
137
|
+
|
120
138
|
Style/IfUnlessModifier:
|
121
139
|
Enabled: false
|
122
140
|
|
data/CHANGES
CHANGED
data/CONTRIBUTORS
CHANGED
@@ -5,8 +5,8 @@ Alessio Signorini <alessio@signorini.us>
|
|
5
5
|
Anthony Eden <anthonyeden@gmail.com>
|
6
6
|
Atoxhybrid <atoxhybrid@gmail.com>
|
7
7
|
AtoxIO <atoxhybrid@gmail.com>
|
8
|
-
Benjamin Yu <benjaminlyu@gmail.com>
|
9
8
|
Ben Schwarz <ben.schwarz@gmail.com>
|
9
|
+
Benjamin Yu <benjaminlyu@gmail.com>
|
10
10
|
Daniel Mendler <mail@daniel-mendler.de>
|
11
11
|
Denis Defreyne <denis.defreyne@stoneship.org>
|
12
12
|
Derek Kastner <dkastner@gmail.com>
|
@@ -18,6 +18,7 @@ Jari Bakken <jari.bakken@gmail.com>
|
|
18
18
|
Jay Mitchell <jaybmitchell@gmail.com>
|
19
19
|
Jeremy Voorhis <jvoorhis@gmail.com>
|
20
20
|
Jon Crosby <jon@joncrosby.me>
|
21
|
+
Jonathan Gnagy <jonathan.l.gnagy@sherwin.com>
|
21
22
|
lakshan <lakshan@web2media.net>
|
22
23
|
Mal McKay <mal.mckay@gmail.com>
|
23
24
|
Marek Skrobacki <skrobul@skrobul.com>
|
data/Gemfile
CHANGED
@@ -14,7 +14,7 @@ gem 'bert', platforms: :ruby, group: :bert
|
|
14
14
|
gem 'php-serialize', group: :php
|
15
15
|
|
16
16
|
group :bson do
|
17
|
-
gem 'bson', '>=
|
17
|
+
gem 'bson', '>= 4.0.0'
|
18
18
|
end
|
19
19
|
|
20
20
|
group :msgpack do
|
@@ -38,7 +38,6 @@ gem 'daybreak', group: :daybreak
|
|
38
38
|
gem 'activerecord', '~> 5.2', group: :activerecord
|
39
39
|
gem 'redis', '~> 4.0.0', group: :redis
|
40
40
|
gem 'mongo', '>= 2', group: :mongo_official
|
41
|
-
gem 'moped', '>= 2', group: :mongo_moped
|
42
41
|
gem 'sequel', group: :sequel
|
43
42
|
gem 'dalli', group: :memcached_dalli
|
44
43
|
gem 'riak-client', group: :riak
|
@@ -94,9 +93,7 @@ end
|
|
94
93
|
|
95
94
|
# Rails integration testing
|
96
95
|
group :rails do
|
97
|
-
|
98
|
-
gem 'actionpack'
|
99
|
-
end
|
96
|
+
gem 'actionpack', '~> 5.2.0'
|
100
97
|
gem 'minitest', '~> 5.0'
|
101
98
|
end
|
102
99
|
|
@@ -105,3 +102,8 @@ group :doc, optional: true do
|
|
105
102
|
gem 'kramdown', '~> 1.17.0'
|
106
103
|
gem 'yard', '~> 0.9.20'
|
107
104
|
end
|
105
|
+
|
106
|
+
# Used for running a dev console
|
107
|
+
group :console, optional: true do
|
108
|
+
gem 'irb'
|
109
|
+
end
|
data/README.md
CHANGED
@@ -120,7 +120,7 @@ Out of the box, it supports the following backends. Use the backend name symbol
|
|
120
120
|
* [Simple Samba database TDB](http://tdb.samba.org/) (`:TDB`)
|
121
121
|
* Document databases:
|
122
122
|
* [CouchDB](http://couchdb.apache.org/) (`:Couch`)
|
123
|
-
* [MongoDB](http://www.mongodb.org/) (`:Mongo
|
123
|
+
* [MongoDB](http://www.mongodb.org/) (`:Mongo`)
|
124
124
|
* Moneta network protocols:
|
125
125
|
* Moneta key/value client (`:Client` works with `Moneta::Server`)
|
126
126
|
* Moneta HTTP/REST client (`:RestClient` works with `Rack::MonetaRest`)
|
@@ -142,11 +142,7 @@ __NOTE:__ <a name="backend-matrix"></a>The backend matrix is much more readable
|
|
142
142
|
|
143
143
|
<tr><th colspan="2">Persistent stores</th><th colspan="12"></th></tr>
|
144
144
|
|
145
|
-
<tr><td>Mongo</td><td>mongo
|
146
|
-
|
147
|
-
<tr><td>MongoOfficial</td><td>mongo</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td><a href="http://www.mongodb.org/">MongoDB</a> database</td></tr>
|
148
|
-
|
149
|
-
<tr><td>MongoMoped</td><td>moped</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td><a href="http://www.mongodb.org/">MongoDB</a> database</td></tr>
|
145
|
+
<tr><td>Mongo</td><td>mongo</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td><a href="http://www.mongodb.org/">MongoDB</a> database</td></tr>
|
150
146
|
|
151
147
|
<tr><td>Redis</td><td>redis</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td><a href="http://redis.io/">Redis</a> database</td></tr>
|
152
148
|
|
data/feature_matrix.yaml
CHANGED
@@ -8,20 +8,10 @@ notes:
|
|
8
8
|
server if you want multiprocess concurrency!
|
9
9
|
backends:
|
10
10
|
- adapter: Mongo
|
11
|
-
platforms: [ MRI, JRuby ]
|
12
|
-
gems: mongo or moped
|
13
|
-
features: [ threadsafe, multiprocess, increment, create, expires, each_key, bulk_read, bulk_write ]
|
14
|
-
description: "[MongoDB](http://www.mongodb.org/) database"
|
15
|
-
- adapter: MongoOfficial
|
16
11
|
platforms: [ MRI, JRuby ]
|
17
12
|
gems: mongo
|
18
13
|
features: [ threadsafe, multiprocess, increment, create, expires, each_key, bulk_read, bulk_write ]
|
19
14
|
description: "[MongoDB](http://www.mongodb.org/) database"
|
20
|
-
- adapter: MongoMoped
|
21
|
-
platforms: [ MRI, JRuby ]
|
22
|
-
gems: moped
|
23
|
-
features: [ threadsafe, multiprocess, increment, create, expires, each_key, bulk_read, bulk_write ]
|
24
|
-
description: "[MongoDB](http://www.mongodb.org/) database"
|
25
15
|
- adapter: Redis
|
26
16
|
platforms: [ MRI, JRuby ]
|
27
17
|
gems: redis
|
data/lib/moneta.rb
CHANGED
@@ -5,20 +5,22 @@
|
|
5
5
|
module Moneta
|
6
6
|
autoload :Builder, 'moneta/builder'
|
7
7
|
autoload :Cache, 'moneta/cache'
|
8
|
-
autoload :CreateSupport, 'moneta/
|
9
|
-
autoload :
|
10
|
-
autoload :
|
8
|
+
autoload :CreateSupport, 'moneta/create_support'
|
9
|
+
autoload :DBMAdapter, 'moneta/dbm_adapter'
|
10
|
+
autoload :Defaults, 'moneta/defaults'
|
11
|
+
autoload :EachKeySupport, 'moneta/each_key_support'
|
11
12
|
autoload :Enumerable, 'moneta/enumerable'
|
12
|
-
autoload :ExpiresSupport, 'moneta/
|
13
|
+
autoload :ExpiresSupport, 'moneta/expires_support'
|
13
14
|
autoload :Expires, 'moneta/expires'
|
14
15
|
autoload :Fallback, 'moneta/fallback'
|
15
|
-
autoload :HashAdapter, 'moneta/
|
16
|
-
autoload :IncrementSupport, 'moneta/
|
16
|
+
autoload :HashAdapter, 'moneta/hash_adapter'
|
17
|
+
autoload :IncrementSupport, 'moneta/increment_support'
|
17
18
|
autoload :Lock, 'moneta/lock'
|
18
19
|
autoload :Logger, 'moneta/logger'
|
19
20
|
autoload :Mutex, 'moneta/synchronize'
|
21
|
+
autoload :NilValues, 'moneta/nil_values'
|
20
22
|
autoload :OptionMerger, 'moneta/optionmerger'
|
21
|
-
autoload :OptionSupport, 'moneta/
|
23
|
+
autoload :OptionSupport, 'moneta/option_support'
|
22
24
|
autoload :Pool, 'moneta/pool'
|
23
25
|
autoload :Proxy, 'moneta/proxy'
|
24
26
|
autoload :Semaphore, 'moneta/synchronize'
|
@@ -58,8 +60,6 @@ module Moneta
|
|
58
60
|
autoload :MemcachedNative, 'moneta/adapters/memcached/native'
|
59
61
|
autoload :Memory, 'moneta/adapters/memory'
|
60
62
|
autoload :Mongo, 'moneta/adapters/mongo'
|
61
|
-
autoload :MongoMoped, 'moneta/adapters/mongo/moped'
|
62
|
-
autoload :MongoOfficial, 'moneta/adapters/mongo/official'
|
63
63
|
autoload :Null, 'moneta/adapters/null'
|
64
64
|
autoload :PStore, 'moneta/adapters/pstore'
|
65
65
|
autoload :Redis, 'moneta/adapters/redis'
|
@@ -1,12 +1,261 @@
|
|
1
|
+
require 'mongo'
|
2
|
+
|
1
3
|
module Moneta
|
2
|
-
# @api private
|
3
4
|
module Adapters
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
# MongoDB backend
|
6
|
+
#
|
7
|
+
# Supports expiration, documents will be automatically removed starting
|
8
|
+
# with mongodb >= 2.2 (see {http://docs.mongodb.org/manual/tutorial/expire-data/}).
|
9
|
+
#
|
10
|
+
# You can store hashes directly using this adapter.
|
11
|
+
#
|
12
|
+
# @example Store hashes
|
13
|
+
# db = Moneta::Adapters::MongoOfficial.new
|
14
|
+
# db['key'] = {a: 1, b: 2}
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
class Mongo
|
18
|
+
include Defaults
|
19
|
+
include ExpiresSupport
|
20
|
+
|
21
|
+
supports :each_key, :create, :increment
|
22
|
+
attr_reader :backend
|
23
|
+
|
24
|
+
DEFAULT_PORT = 27017
|
25
|
+
|
26
|
+
# @param [Hash] options
|
27
|
+
# @option options [String] :collection ('moneta') MongoDB collection name
|
28
|
+
# @option options [String] :host ('127.0.0.1') MongoDB server host
|
29
|
+
# @option options [String] :user Username used to authenticate
|
30
|
+
# @option options [String] :password Password used to authenticate
|
31
|
+
# @option options [Integer] :port (MongoDB default port) MongoDB server port
|
32
|
+
# @option options [String] :db ('moneta') MongoDB database
|
33
|
+
# @option options [Integer] :expires Default expiration time
|
34
|
+
# @option options [String] :expires_field ('expiresAt') Document field to store expiration time
|
35
|
+
# @option options [String] :value_field ('value') Document field to store value
|
36
|
+
# @option options [String] :type_field ('type') Document field to store value type
|
37
|
+
# @option options [::Mongo::Client] :backend Use existing backend instance
|
38
|
+
# @option options Other options passed to `Mongo::MongoClient#new`
|
39
|
+
def initialize(options = {})
|
40
|
+
self.default_expires = options.delete(:expires)
|
41
|
+
@expires_field = options.delete(:expires_field) || 'expiresAt'
|
42
|
+
@value_field = options.delete(:value_field) || 'value'
|
43
|
+
@type_field = options.delete(:type_field) || 'type'
|
44
|
+
|
45
|
+
collection = options.delete(:collection) || 'moneta'
|
46
|
+
db = options.delete(:db) || 'moneta'
|
47
|
+
@backend = options[:backend] ||
|
48
|
+
begin
|
49
|
+
host = options.delete(:host) || '127.0.0.1'
|
50
|
+
port = options.delete(:port) || DEFAULT_PORT
|
51
|
+
options[:logger] ||= ::Logger.new(STDERR).tap do |logger|
|
52
|
+
logger.level = ::Logger::ERROR
|
53
|
+
end
|
54
|
+
::Mongo::Client.new(["#{host}:#{port}"], options)
|
55
|
+
end
|
56
|
+
@backend.use(db)
|
57
|
+
@collection = @backend[collection]
|
58
|
+
if @backend.command(buildinfo: 1).documents.first['version'] >= '2.2'
|
59
|
+
@collection.indexes.create_one({ @expires_field => 1 }, expire_after: 0)
|
60
|
+
else
|
61
|
+
warn 'Moneta::Adapters::Mongo - You are using MongoDB version < 2.2, expired documents will not be deleted'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# (see Proxy#load)
|
66
|
+
def load(key, options = {})
|
67
|
+
view = @collection.find(:$and => [
|
68
|
+
{ _id: to_binary(key) },
|
69
|
+
not_expired
|
70
|
+
])
|
71
|
+
|
72
|
+
doc = view.limit(1).first
|
73
|
+
|
74
|
+
if doc
|
75
|
+
update_expiry(options, nil) do |expires|
|
76
|
+
view.update_one(:$set => { @expires_field => expires })
|
77
|
+
end
|
78
|
+
|
79
|
+
doc_to_value(doc)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# (see Proxy#store)
|
84
|
+
def store(key, value, options = {})
|
85
|
+
key = to_binary(key)
|
86
|
+
@collection.replace_one({ _id: key },
|
87
|
+
value_to_doc(key, value, options),
|
88
|
+
upsert: true)
|
89
|
+
value
|
90
|
+
end
|
91
|
+
|
92
|
+
# (see Proxy#each_key)
|
93
|
+
def each_key
|
94
|
+
return enum_for(:each_key) unless block_given?
|
95
|
+
@collection.find.each { |doc| yield from_binary(doc[:_id]) }
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
# (see Proxy#delete)
|
100
|
+
def delete(key, options = {})
|
101
|
+
key = to_binary(key)
|
102
|
+
if doc = @collection.find(_id: key).find_one_and_delete and
|
103
|
+
!doc[@expires_field] || doc[@expires_field] >= Time.now
|
104
|
+
doc_to_value(doc)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# (see Proxy#increment)
|
109
|
+
def increment(key, amount = 1, options = {})
|
110
|
+
@collection.find_one_and_update({ :$and => [{ _id: to_binary(key) }, not_expired] },
|
111
|
+
{ :$inc => { @value_field => amount } },
|
112
|
+
return_document: :after,
|
113
|
+
upsert: true)[@value_field]
|
114
|
+
end
|
115
|
+
|
116
|
+
# (see Proxy#create)
|
117
|
+
def create(key, value, options = {})
|
118
|
+
key = to_binary(key)
|
119
|
+
@collection.insert_one(value_to_doc(key, value, options))
|
120
|
+
true
|
121
|
+
rescue ::Mongo::Error::OperationFailure => ex
|
122
|
+
raise unless ex.message =~ /^E11000 / # duplicate key error
|
123
|
+
false
|
124
|
+
end
|
125
|
+
|
126
|
+
# (see Proxy#clear)
|
127
|
+
def clear(options = {})
|
128
|
+
@collection.delete_many
|
129
|
+
self
|
130
|
+
end
|
131
|
+
|
132
|
+
# (see Proxy#close)
|
133
|
+
def close
|
134
|
+
@backend.close
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
138
|
+
# (see Proxy#slice)
|
139
|
+
def slice(*keys, **options)
|
140
|
+
view = @collection.find(:$and => [
|
141
|
+
{ _id: { :$in => keys.map(&method(:to_binary)) } },
|
142
|
+
not_expired
|
143
|
+
])
|
144
|
+
pairs = view.map { |doc| [from_binary(doc[:_id]), doc_to_value(doc)] }
|
145
|
+
|
146
|
+
update_expiry(options, nil) do |expires|
|
147
|
+
view.update_many(:$set => { @expires_field => expires })
|
148
|
+
end
|
149
|
+
|
150
|
+
pairs
|
151
|
+
end
|
152
|
+
|
153
|
+
# (see Proxy#merge!)
|
154
|
+
def merge!(pairs, options = {})
|
155
|
+
existing = Hash[slice(*pairs.map { |key, _| key })]
|
156
|
+
update_pairs, insert_pairs = pairs.partition { |key, _| existing.key?(key) }
|
157
|
+
|
158
|
+
@collection.insert_many(insert_pairs.map do |key, value|
|
159
|
+
value_to_doc(to_binary(key), value, options)
|
160
|
+
end)
|
161
|
+
|
162
|
+
update_pairs.each do |key, value|
|
163
|
+
value = yield(key, existing[key], value) if block_given?
|
164
|
+
binary = to_binary(key)
|
165
|
+
@collection.replace_one({ _id: binary }, value_to_doc(binary, value, options))
|
166
|
+
end
|
167
|
+
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
# (see Proxy#fetch_values)
|
172
|
+
def fetch_values(*keys, **options)
|
173
|
+
return values_at(*keys, **options) unless block_given?
|
174
|
+
hash = Hash[slice(*keys, **options)]
|
175
|
+
keys.map do |key|
|
176
|
+
if hash.key?(key)
|
177
|
+
hash[key]
|
178
|
+
else
|
179
|
+
yield key
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# (see Proxy#values_at)
|
185
|
+
def values_at(*keys, **options)
|
186
|
+
hash = Hash[slice(*keys, **options)]
|
187
|
+
keys.map { |key| hash[key] }
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def doc_to_value(doc)
|
193
|
+
case doc[@type_field]
|
194
|
+
when 'Hash'
|
195
|
+
doc = doc.dup
|
196
|
+
doc.delete('_id')
|
197
|
+
doc.delete(@type_field)
|
198
|
+
doc.delete(@expires_field)
|
199
|
+
doc
|
200
|
+
when 'Number'
|
201
|
+
doc[@value_field]
|
202
|
+
else
|
203
|
+
# In ruby_bson version 2 (and probably up), #to_s no longer returns the binary data
|
204
|
+
from_binary(doc[@value_field])
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def value_to_doc(key, value, options)
|
209
|
+
case value
|
210
|
+
when Hash
|
211
|
+
value.merge('_id' => key,
|
212
|
+
@type_field => 'Hash',
|
213
|
+
# @expires_field must be a Time object (BSON date datatype)
|
214
|
+
@expires_field => expires_at(options) || nil)
|
215
|
+
when Float, Integer
|
216
|
+
{ '_id' => key,
|
217
|
+
@type_field => 'Number',
|
218
|
+
@value_field => value,
|
219
|
+
# @expires_field must be a Time object (BSON date datatype)
|
220
|
+
@expires_field => expires_at(options) || nil }
|
221
|
+
when String
|
222
|
+
intvalue = value.to_i
|
223
|
+
{ '_id' => key,
|
224
|
+
@type_field => 'String',
|
225
|
+
@value_field => intvalue.to_s == value ? intvalue : to_binary(value),
|
226
|
+
# @expires_field must be a Time object (BSON date datatype)
|
227
|
+
@expires_field => expires_at(options) || nil }
|
228
|
+
else
|
229
|
+
raise ArgumentError, "Invalid value type: #{value.class}"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# BSON will use String#force_encoding to make the string 8-bit
|
234
|
+
# ASCII. This could break unicode text so we should dup in this
|
235
|
+
# case, and it also fails with frozen strings.
|
236
|
+
def to_binary(str)
|
237
|
+
str = str.dup if str.frozen? || str.encoding != Encoding::ASCII_8BIT
|
238
|
+
::BSON::Binary.new(str)
|
239
|
+
end
|
240
|
+
|
241
|
+
def from_binary(binary)
|
242
|
+
binary.is_a?(::BSON::Binary) ? binary.data : binary.to_s
|
243
|
+
end
|
244
|
+
|
245
|
+
def not_expired
|
246
|
+
{
|
247
|
+
:$or => [
|
248
|
+
{ @expires_field => nil },
|
249
|
+
{ @expires_field => { :$gte => Time.now } }
|
250
|
+
]
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
def update_expiry(options, default)
|
255
|
+
if (expires = expires_at(options, default)) != nil
|
256
|
+
yield(expires || nil)
|
257
|
+
end
|
258
|
+
end
|
10
259
|
end
|
11
260
|
end
|
12
261
|
end
|