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
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