momento 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +3 -0
  3. data/.rubocop.yml +12 -0
  4. data/.yardopts +2 -0
  5. data/CHANGELOG.md +28 -0
  6. data/CONTRIBUTING.md +1 -1
  7. data/Gemfile.lock +7 -3
  8. data/README.md +131 -36
  9. data/README.template.md +88 -0
  10. data/examples/.gitignore +1 -0
  11. data/examples/Gemfile +5 -0
  12. data/examples/README.md +34 -0
  13. data/examples/compact.rb +43 -0
  14. data/examples/example.rb +66 -0
  15. data/examples/file.rb +57 -0
  16. data/lib/momento/cacheclient_pb.rb +2 -0
  17. data/lib/momento/cacheclient_services_pb.rb +2 -0
  18. data/lib/momento/controlclient_pb.rb +2 -0
  19. data/lib/momento/controlclient_services_pb.rb +2 -0
  20. data/lib/momento/create_cache_response.rb +9 -24
  21. data/lib/momento/create_cache_response_builder.rb +27 -0
  22. data/lib/momento/delete_cache_response.rb +6 -21
  23. data/lib/momento/delete_cache_response_builder.rb +25 -0
  24. data/lib/momento/delete_response.rb +6 -15
  25. data/lib/momento/delete_response_builder.rb +23 -0
  26. data/lib/momento/error/grpc_details.rb +38 -0
  27. data/lib/momento/error/transport_details.rb +20 -0
  28. data/lib/momento/error/types.rb +232 -0
  29. data/lib/momento/error.rb +54 -0
  30. data/lib/momento/error_builder.rb +50 -0
  31. data/lib/momento/exceptions.rb +7 -0
  32. data/lib/momento/get_response.rb +37 -40
  33. data/lib/momento/get_response_builder.rb +37 -0
  34. data/lib/momento/list_caches_response.rb +45 -21
  35. data/lib/momento/list_caches_response_builder.rb +25 -0
  36. data/lib/momento/response/error.rb +10 -3
  37. data/lib/momento/response.rb +54 -1
  38. data/lib/momento/response_builder.rb +18 -0
  39. data/lib/momento/set_response.rb +21 -21
  40. data/lib/momento/set_response_builder.rb +25 -0
  41. data/lib/momento/simple_cache_client.rb +163 -31
  42. data/lib/momento/ttl.rb +48 -0
  43. data/lib/momento/version.rb +2 -1
  44. data/momento.gemspec +1 -0
  45. data/release-please-config.json +15 -0
  46. metadata +44 -6
  47. data/examples/basic.rb +0 -45
@@ -1,50 +1,95 @@
1
1
  require 'jwt'
2
- require 'momento/cacheclient_services_pb'
3
- require 'momento/controlclient_services_pb'
4
- require 'momento/response'
2
+ require_relative 'cacheclient_services_pb'
3
+ require_relative 'controlclient_services_pb'
4
+ require_relative 'response'
5
+ require_relative 'ttl'
6
+ require_relative 'exceptions'
5
7
 
6
8
  module Momento
9
+ # rubocop:disable Metrics/ClassLength
10
+
7
11
  # A simple client for Momento.
8
12
  #
13
+ # SimpleCacheClient does not use exceptions to report errors.
14
+ # Instead it returns an error response. Please see {file:README.md#label-Error+Handling}.
15
+ #
9
16
  # @example
17
+ # token = ...your Momento JWT...
10
18
  # client = Momento::SimpleCacheClient.new(
11
- # auth_token: jwt,
12
- # default_ttl: 10_000
19
+ # auth_token: token,
20
+ # # cached items will be deleted after 100 seconds
21
+ # default_ttl: 100
13
22
  # )
14
23
  #
24
+ # response = client.create_cache("my_cache")
25
+ # if response.success?
26
+ # puts "my_cache was created"
27
+ # elsif response.already_exists?
28
+ # puts "my_cache already exists"
29
+ # elsif response.error?
30
+ # raise response.error
31
+ # end
32
+ #
33
+ # # set will only return success or error,
34
+ # # we only need to check for error
35
+ # response = client.set("my_cache", "key", "value")
36
+ # raise response.error if response.error?
37
+ #
15
38
  # response = client.get("my_cache", "key")
16
39
  # if response.hit?
17
- # puts "We got #{response}"
40
+ # puts "We got #{response.value_string}"
18
41
  # elsif response.miss?
19
42
  # puts "It's not in the cache"
20
43
  # elsif response.error?
21
- # puts "The front fell off."
44
+ # raise response.error
22
45
  # end
46
+ #
47
+ # @see Momento::Response
23
48
  class SimpleCacheClient
49
+ # This gem's version.
24
50
  VERSION = Momento::VERSION
25
51
  CACHE_CLIENT_STUB_CLASS = CacheClient::Scs::Stub
26
52
  CONTROL_CLIENT_STUB_CLASS = ControlClient::ScsControl::Stub
53
+ private_constant :CACHE_CLIENT_STUB_CLASS, :CONTROL_CLIENT_STUB_CLASS
27
54
 
28
- # The default time to live, in milliseconds.
55
+ # @return [Numeric] how long items should remain in the cache, in seconds.
29
56
  attr_accessor :default_ttl
30
57
 
31
58
  # @param auth_token [String] the JWT for your Momento account
32
- # @param default_ttl [Integer]
59
+ # @param default_ttl [Numeric] time-to-live, in seconds
60
+ # @raise [ArgumentError] if the default_ttl or auth_token is invalid
33
61
  def initialize(auth_token:, default_ttl:)
34
62
  @auth_token = auth_token
35
- @default_ttl = default_ttl
63
+ @default_ttl = Momento::Ttl.to_ttl(default_ttl)
36
64
  load_endpoints_from_token
37
65
  end
38
66
 
39
67
  # Get a value in a cache.
40
68
  #
41
- # Momento only stores bytes; the returned value will be encoded as ASCII-8BIT.
69
+ # The value can be retrieved as either bytes or a string.
70
+ # @example
71
+ # response = client.get("my_cache", "key")
72
+ # if response.hit?
73
+ # puts "We got #{response.value_string}"
74
+ # elsif response.miss?
75
+ # puts "It's not in the cache"
76
+ # elsif response.error?
77
+ # raise response.error
78
+ # end
42
79
  #
80
+ # @see Momento::GetResponse
43
81
  # @param cache_name [String]
44
82
  # @param key [String] must only contain ASCII characters
45
83
  # @return [Momento::GetResponse]
84
+ # @raise [TypeError] when the cache_name or key is not a String
46
85
  def get(cache_name, key)
47
- return GetResponse.from_block do
86
+ builder = GetResponseBuilder.new(
87
+ context: { cache_name: cache_name, key: key }
88
+ )
89
+
90
+ return builder.from_block do
91
+ validate_cache_name(cache_name)
92
+
48
93
  cache_stub.get(
49
94
  CacheClient::GetRequest.new(cache_key: to_bytes(key)),
50
95
  metadata: { cache: cache_name }
@@ -55,18 +100,32 @@ module Momento
55
100
  # Set a value in a cache.
56
101
  #
57
102
  # If ttl is not set, it will use the default_ttl.
103
+ # @example
104
+ # response = client.set("my_cache", "key", "value")
105
+ # raise response.error if response.error?
58
106
  #
107
+ # @see Momento::SetResponse
59
108
  # @param cache_name [String]
60
109
  # @param key [String] must only contain ASCII characters
61
110
  # @param value [String] the value to cache
62
- # @param ttl [Integer] time to live, in milliseconds.
111
+ # @param ttl [Numeric] time-to-live, in seconds.
112
+ # @raise [ArgumentError] if the ttl is invalid
63
113
  # @return [Momento::SetResponse]
114
+ # @raise [TypeError] when the cache_name, key, or value is not a String
64
115
  def set(cache_name, key, value, ttl: default_ttl)
65
- return SetResponse.from_block do
116
+ ttl = Momento::Ttl.to_ttl(ttl)
117
+
118
+ builder = SetResponseBuilder.new(
119
+ context: { cache_name: cache_name, key: key, value: value, ttl: ttl }
120
+ )
121
+
122
+ return builder.from_block do
123
+ validate_cache_name(cache_name)
124
+
66
125
  req = CacheClient::SetRequest.new(
67
126
  cache_key: to_bytes(key),
68
127
  cache_body: to_bytes(value),
69
- ttl_milliseconds: ttl
128
+ ttl_milliseconds: ttl.milliseconds
70
129
  )
71
130
 
72
131
  cache_stub.set(req, metadata: { cache: cache_name })
@@ -75,11 +134,24 @@ module Momento
75
134
 
76
135
  # Delete a key in a cache.
77
136
  #
137
+ # If the key does not exist, delete will still succeed.
138
+ # @example
139
+ # response = client.delete("my_cache", "key")
140
+ # raise response.error if response.error?
141
+ #
142
+ # @see Momento::DeleteResponse
78
143
  # @param cache_name [String]
79
144
  # @param key [String] must only contain ASCII characters
80
145
  # @return [Momento::DeleteResponse]
146
+ # @raise [TypeError] when the cache_name or key is not a String
81
147
  def delete(cache_name, key)
82
- return DeleteResponse.from_block do
148
+ builder = DeleteResponseBuilder.new(
149
+ context: { cache_name: cache_name, key: key }
150
+ )
151
+
152
+ return builder.from_block do
153
+ validate_cache_name(cache_name)
154
+
83
155
  cache_stub.delete(
84
156
  CacheClient::DeleteRequest.new(cache_key: to_bytes(key)),
85
157
  metadata: { cache: cache_name }
@@ -88,56 +160,97 @@ module Momento
88
160
  end
89
161
 
90
162
  # Create a new Momento cache.
163
+ # @example
164
+ # response = client.create_cache("my_cache")
165
+ # if response.success?
166
+ # puts "my_cache was created"
167
+ # elsif response.already_exists?
168
+ # puts "my_cache already exists"
169
+ # elsif response.error?
170
+ # raise response.error
171
+ # end
91
172
  #
92
- # @param name [String] the name of the cache to create.
173
+ # @see Momento::CreateCacheResponse
174
+ # @param cache_name [String] the name of the cache to create.
93
175
  # @return [Momento::CreateCacheResponse] the response from Momento.
94
- def create_cache(name)
95
- return CreateCacheResponse.from_block do
176
+ # @raise [TypeError] when the cache_name is not a String
177
+ def create_cache(cache_name)
178
+ builder = CreateCacheResponseBuilder.new(
179
+ context: { cache_name: cache_name }
180
+ )
181
+
182
+ return builder.from_block do
183
+ validate_cache_name(cache_name)
184
+
96
185
  control_stub.create_cache(
97
- ControlClient::CreateCacheRequest.new(cache_name: name)
186
+ ControlClient::CreateCacheRequest.new(cache_name: cache_name)
98
187
  )
99
188
  end
100
189
  end
101
190
 
102
191
  # Delete an existing Momento cache.
103
192
  #
104
- # @param name [String] the name of the cache to delete.
193
+ # @example
194
+ # response = client.delete_cache("my_cache")
195
+ # raise response.error if response.error?
196
+ #
197
+ # @see Momento::DeleteCacheResponse
198
+ # @param cache_name [String] the name of the cache to delete.
105
199
  # @return [Momento::DeleteCacheResponse] the response from Momento.
106
- def delete_cache(name)
107
- return DeleteCacheResponse.from_block do
200
+ # @raise [TypeError] when the cache_name is not a String
201
+ def delete_cache(cache_name)
202
+ builder = DeleteCacheResponseBuilder.new(
203
+ context: { cache_name: cache_name }
204
+ )
205
+
206
+ return builder.from_block do
207
+ validate_cache_name(cache_name)
208
+
108
209
  control_stub.delete_cache(
109
- ControlClient::DeleteCacheRequest.new(cache_name: name)
210
+ ControlClient::DeleteCacheRequest.new(cache_name: cache_name)
110
211
  )
111
212
  end
112
213
  end
113
214
 
114
215
  # List a page of your caches.
115
216
  #
217
+ # This is a low-level method. You probably want to use {#caches} instead.
218
+ #
116
219
  # The next_token indicates which page to fetch.
117
220
  # If nil or "" it will fetch the first page. Default is to fetch the first page.
118
221
  #
119
- # @params next_token [String, nil] the token of the page to request
222
+ # @see #caches
223
+ # @see Momento::ListCachesResponse
224
+ # @note Consider using `caches` instead.
225
+ # @param next_token [String, nil] the token of the page to request
120
226
  # @return [Momento::ListCachesResponse]
121
227
  def list_caches(next_token: "")
122
- return ListCachesResponse.from_block do
228
+ builder = ListCachesResponseBuilder.new(
229
+ context: { next_token: next_token }
230
+ )
231
+ return builder.from_block do
123
232
  control_stub.list_caches(
124
233
  ControlClient::ListCachesRequest.new(next_token: next_token)
125
234
  )
126
235
  end
127
236
  end
128
237
 
129
- # Lists the names of all your caches.
238
+ # Lists the names of all your caches, as a lazy Enumerator.
239
+ # @example
240
+ # cache_names = client.caches
241
+ # cache_names.each { |name| puts name }
130
242
  #
243
+ # @note Unlike other methods, this will raise if there is a problem
244
+ # with the client or service.
131
245
  # @return [Enumerator::Lazy<String>] the cache names
132
- # @raise [GRPC::BadStatus]
133
- # rubocop:disable Metrics/MethodLength
246
+ # @raise [Momento::Error] when there is an error listing caches.
134
247
  def caches
135
248
  Enumerator.new do |yielder|
136
249
  next_token = ""
137
250
 
138
251
  loop do
139
252
  response = list_caches(next_token: next_token)
140
- raise response.grpc_exception if response.is_a? Momento::Response::Error
253
+ raise response.error if response.is_a? Momento::Response::Error
141
254
 
142
255
  response.cache_names.each do |name|
143
256
  yielder << name
@@ -149,7 +262,6 @@ module Momento
149
262
  end
150
263
  end.lazy
151
264
  end
152
- # rubocop:enable Metrics/MethodLength
153
265
 
154
266
  private
155
267
 
@@ -170,6 +282,8 @@ module Momento
170
282
 
171
283
  @control_endpoint = claim["cp"]
172
284
  @cache_endpoint = claim["c"]
285
+ rescue JWT::DecodeError
286
+ raise ArgumentError, "Invalid Momento auth token."
173
287
  end
174
288
 
175
289
  def make_combined_credentials
@@ -195,10 +309,28 @@ module Momento
195
309
  #
196
310
  # @param string [String] the string to make safe for GRPC bytes
197
311
  # @return [String] a duplicate safe to use as GRPC bytes
312
+ # @raise [TypeError] when the string is not a String
198
313
  def to_bytes(string)
314
+ raise TypeError, "expected a String, got a #{string.class}" unless string.is_a?(String)
315
+
199
316
  # dup in case the value is frozen and to avoid changing the value's encoding
200
317
  # for the caller.
201
318
  return string.dup.force_encoding(Encoding::ASCII_8BIT)
202
319
  end
320
+
321
+ # This is not a complete validation of the cache name, just
322
+ # issues that might cause an exception in the client. Let the server
323
+ # handle the rest of the validation.
324
+ #
325
+ # @param name [String] the cache name to validate
326
+ # @raise [TypeError] when the name is not a String
327
+ # @raise [Momento::CacheNameError] when the name is not ASCII
328
+ def validate_cache_name(name)
329
+ raise TypeError, "Cache name must be a String, got a #{name.class}" unless name.is_a?(String)
330
+ raise Momento::CacheNameError, "Cache name must be ASCII, got '#{name}'" if name.match?(/[^[:ascii:]]/)
331
+
332
+ return
333
+ end
203
334
  end
335
+ # rubocop:enable Metrics/ClassLength
204
336
  end
@@ -0,0 +1,48 @@
1
+ module Momento
2
+ # Validates and represents a time-to-live.
3
+ #
4
+ # @private
5
+ class Ttl
6
+ class << self
7
+ # Create a Momento::Ttl object.
8
+ #
9
+ # If given a Momento::Ttl it will return it.
10
+ # This means you don't have to check if it's already a Momento::Ttl
11
+ #
12
+ # @example
13
+ # ttl = Momento::Ttl.to_ttl(ttl)
14
+ # @param ttl [Numeric,Momento::Ttl] the TTL in seconds, or an existing Ttl object
15
+ # @return [Momento::Ttl]
16
+ def to_ttl(ttl)
17
+ return ttl if ttl.is_a?(self)
18
+
19
+ raise ArgumentError, "ttl '#{ttl}' is not Numeric" unless ttl.is_a?(Numeric)
20
+ raise ArgumentError, "ttl #{ttl} is less than 0" if ttl.negative?
21
+
22
+ new(ttl)
23
+ end
24
+ end
25
+
26
+ # @return [Numeric] the TTL in seconds
27
+ attr_reader :seconds
28
+
29
+ def initialize(ttl)
30
+ @seconds = ttl
31
+ end
32
+
33
+ # @return [Numeric] the TTL in milliseconds
34
+ def milliseconds
35
+ @seconds * 1_000
36
+ end
37
+
38
+ def ==(other)
39
+ return false unless other.is_a?(Momento::Ttl)
40
+
41
+ milliseconds == other.milliseconds
42
+ end
43
+
44
+ def to_s
45
+ "#{seconds} seconds"
46
+ end
47
+ end
48
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Momento
4
- VERSION = "0.1.0"
4
+ # This gem's version.
5
+ VERSION = "0.2.0"
5
6
  end
data/momento.gemspec CHANGED
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_development_dependency 'rubocop-rake', '~> 0.6.0'
39
39
  spec.add_development_dependency 'rubocop-rspec', '~> 2.15'
40
40
  spec.add_development_dependency 'simplecov', '~> 0.21'
41
+ spec.add_development_dependency 'yard', '~> 0.9'
41
42
 
42
43
  spec.add_dependency "grpc", '~> 1'
43
44
  spec.add_dependency 'jwt', '~> 2'
@@ -0,0 +1,15 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "changelog-path": "CHANGELOG.md",
5
+ "release-type": "ruby",
6
+ "bump-minor-pre-major": true,
7
+ "bump-patch-for-minor-pre-major": false,
8
+ "draft": false,
9
+ "prerelease": false,
10
+ "package-name": "momento",
11
+ "bootstrap-sha": "ddb1cf2711fc1c7a29c0b48fe7f3aa12527c528c"
12
+ }
13
+ },
14
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
15
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: momento
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Momento
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-21 00:00:00.000000000 Z
11
+ date: 2022-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0.21'
139
+ - !ruby/object:Gem::Dependency
140
+ name: yard
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.9'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.9'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: grpc
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -172,16 +186,25 @@ extensions: []
172
186
  extra_rdoc_files: []
173
187
  files:
174
188
  - ".editorconfig"
189
+ - ".release-please-manifest.json"
175
190
  - ".rspec"
176
191
  - ".rubocop.yml"
177
192
  - ".ruby-version"
193
+ - ".yardopts"
194
+ - CHANGELOG.md
178
195
  - CONTRIBUTING.md
179
196
  - Gemfile
180
197
  - Gemfile.lock
181
198
  - LICENSE.txt
182
199
  - README.md
200
+ - README.template.md
183
201
  - Rakefile
184
- - examples/basic.rb
202
+ - examples/.gitignore
203
+ - examples/Gemfile
204
+ - examples/README.md
205
+ - examples/compact.rb
206
+ - examples/example.rb
207
+ - examples/file.rb
185
208
  - lib/README-generating-pb.txt
186
209
  - lib/momento.rb
187
210
  - lib/momento/cacheclient_pb.rb
@@ -189,16 +212,31 @@ files:
189
212
  - lib/momento/controlclient_pb.rb
190
213
  - lib/momento/controlclient_services_pb.rb
191
214
  - lib/momento/create_cache_response.rb
215
+ - lib/momento/create_cache_response_builder.rb
192
216
  - lib/momento/delete_cache_response.rb
217
+ - lib/momento/delete_cache_response_builder.rb
193
218
  - lib/momento/delete_response.rb
219
+ - lib/momento/delete_response_builder.rb
220
+ - lib/momento/error.rb
221
+ - lib/momento/error/grpc_details.rb
222
+ - lib/momento/error/transport_details.rb
223
+ - lib/momento/error/types.rb
224
+ - lib/momento/error_builder.rb
225
+ - lib/momento/exceptions.rb
194
226
  - lib/momento/get_response.rb
227
+ - lib/momento/get_response_builder.rb
195
228
  - lib/momento/list_caches_response.rb
229
+ - lib/momento/list_caches_response_builder.rb
196
230
  - lib/momento/response.rb
197
231
  - lib/momento/response/error.rb
232
+ - lib/momento/response_builder.rb
198
233
  - lib/momento/set_response.rb
234
+ - lib/momento/set_response_builder.rb
199
235
  - lib/momento/simple_cache_client.rb
236
+ - lib/momento/ttl.rb
200
237
  - lib/momento/version.rb
201
238
  - momento.gemspec
239
+ - release-please-config.json
202
240
  - sig/momento/client.rbs
203
241
  homepage: https://github.com/momentohq/client-sdk-ruby
204
242
  licenses:
@@ -207,7 +245,7 @@ metadata:
207
245
  homepage_uri: https://github.com/momentohq/client-sdk-ruby
208
246
  rubygems_mfa_required: 'true'
209
247
  source_code_uri: https://github.com/momentohq/client-sdk-ruby
210
- post_install_message:
248
+ post_install_message:
211
249
  rdoc_options: []
212
250
  require_paths:
213
251
  - lib
@@ -223,7 +261,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
261
  version: '0'
224
262
  requirements: []
225
263
  rubygems_version: 3.3.7
226
- signing_key:
264
+ signing_key:
227
265
  specification_version: 4
228
266
  summary: Client for Momento Serverless Cache
229
267
  test_files: []
data/examples/basic.rb DELETED
@@ -1,45 +0,0 @@
1
- require 'momento'
2
-
3
- # Get your Momento token from an environment variable.
4
- token = ENV.fetch('MOMENTO_AUTH_TOKEN')
5
-
6
- # Cached items will be deleted after 30 seconds.
7
- ttl = 30_000
8
-
9
- # Instantiate a Momento client.
10
- client = Momento::SimpleCacheClient.new(
11
- auth_token: token,
12
- default_ttl: ttl
13
- )
14
-
15
- # Create a cache named "test_cache" to play with.
16
- response = client.create_cache("test_cache")
17
- if response.success? || response.already_exists?
18
- puts "Created the cache, or it already exists."
19
- elsif response.error?
20
- raise "Couldn't create a cache: #{response}"
21
- else
22
- raise
23
- end
24
-
25
- # Put an item in the cache.
26
- response = client.set("test_cache", "key", "You cached something!")
27
- if response.success?
28
- puts "Set an item in the cache."
29
- elsif response.error?
30
- raise "Couldn't set an item in the cache: #{response}"
31
- else
32
- raise
33
- end
34
-
35
- # And get it back.
36
- response = client.get("test_cache", "key")
37
- if response.hit?
38
- puts "Cache returned: #{response.value}"
39
- elsif response.miss?
40
- puts "The item wasn't found in the cache."
41
- elsif response.error?
42
- raise "Couldn't get an item from the cache: #{response}"
43
- else
44
- raise
45
- end