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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9aebec0b5bcf8b12d295aff540c30c453e88883e5d5d498f92f3666adcbf4eeb
4
- data.tar.gz: f62cbb548556e6d4a36e0465edb468a7421262620469d7329c34cb7d160553bc
3
+ metadata.gz: 667e187cf3e0d0ce7e721e07036f70e0b65f1b52818fc09eb87311872896ce95
4
+ data.tar.gz: 7e9063dfa4ea6f1bfbe4bee54821c75e9a82d0f54f3f820c6e396fafeeba006a
5
5
  SHA512:
6
- metadata.gz: e9544cff0b2b3550776646d83131b8c7db4c3a9e79083ddb56af23a4cffaa9ce5a3de4a8c1db2db3571e768fe00c409692c3a2787ac9e7e8312a3f145d36f270
7
- data.tar.gz: 96cf85c80a0dbb86c820351e711470f873139e6544ec450e427a5cba92cf23f615bedd7f2ff5fda7eca8f005631e0dbcb5e79550ef001234b6d231fcaa71c924
6
+ metadata.gz: 5e2d3880c0d3fed1ee66cb57f5ebba13a83dd7e4447ec1e99e601a85ff9bc454cf7bf4e9d62c8672324faddf9f396e4488c08400a08e1c1dd0181056655142b5
7
+ data.tar.gz: 4be30ddd9249b7222c2354984815efde88ca3c8cfe65db26c13df1585bd80425cc8f55f68b869d07016183be29b4ae8495a3408fe3b67e0f27d21ef2f6de6a83
@@ -6,11 +6,7 @@ AllCops:
6
6
  - script/**/*
7
7
  - vendor/**/*
8
8
 
9
- Layout/AlignArray:
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
@@ -1,3 +1,9 @@
1
+ 1.4.0
2
+
3
+ * Adapters::Mongo - drop support for moped gem (#182)
4
+ * Adapters::Redis - use #exists? where available (#189)
5
+ * Some reorganisation of code into more separate files (#177)
6
+
1
7
  1.3.0
2
8
 
3
9
  * Transformer - add :each_key support (#170)
@@ -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', '>= 2.0.0'
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
- git 'https://github.com/rails/rails.git', branch: '5-2-stable' do
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`, `:MongoOffical` or `:MongoMoped`)
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 or 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>
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
 
@@ -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
@@ -5,20 +5,22 @@
5
5
  module Moneta
6
6
  autoload :Builder, 'moneta/builder'
7
7
  autoload :Cache, 'moneta/cache'
8
- autoload :CreateSupport, 'moneta/mixins'
9
- autoload :Defaults, 'moneta/mixins'
10
- autoload :EachKeySupport, 'moneta/mixins'
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/mixins'
13
+ autoload :ExpiresSupport, 'moneta/expires_support'
13
14
  autoload :Expires, 'moneta/expires'
14
15
  autoload :Fallback, 'moneta/fallback'
15
- autoload :HashAdapter, 'moneta/mixins'
16
- autoload :IncrementSupport, 'moneta/mixins'
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/mixins'
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
- begin
5
- require 'moneta/adapters/mongo/official'
6
- Mongo = MongoOfficial
7
- rescue LoadError
8
- require 'moneta/adapters/mongo/moped'
9
- Mongo = MongoMoped
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