response_bank 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/README.md +91 -89
- data/lib/response_bank/middleware.rb +7 -8
- data/lib/response_bank/response_cache_handler.rb +74 -58
- data/lib/response_bank/version.rb +1 -1
- data/lib/response_bank.rb +4 -4
- metadata +10 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75476fbf8271b8744fdca486f31ddfff585ab0680e29c600e25393d24b6b6fbb
|
4
|
+
data.tar.gz: 6092909983069ec5689e4e6ada0d241e632331fd6c97fb94e4ce5029c4cf228a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccade0feb4bcc259e6bd0d3435799960014f204031b564ca88892e5c80afce36997f266b91acb0eb2af1e64f80488af49166fe4a99ab2d31874cac9827e37eaa
|
7
|
+
data.tar.gz: 5e74d777bfd30f8eed957d5ed07d11ffab8e6d4d462194277b7a38c28b70728445d0c598a26173020fc1d26c3c11410aabc6cf7f79f5a06cd806546a687cff7e
|
data/README.md
CHANGED
@@ -1,126 +1,128 @@
|
|
1
|
-
# ResponseBank [](http://travis-ci.org/Shopify/response_bank)
|
1
|
+
# ResponseBank [](http://travis-ci.org/Shopify/response_bank) [](https://github.com/Shopify/response_bank/actions/workflows/ci.yml)
|
2
2
|
|
3
|
-
|
3
|
+
## Features
|
4
4
|
|
5
5
|
* Serve gzip'd content
|
6
6
|
* Add ETag and 304 Not Modified headers
|
7
7
|
* Generational caching
|
8
8
|
* No explicit expiry
|
9
9
|
|
10
|
-
|
10
|
+
## Support
|
11
11
|
|
12
12
|
This gem supports the following versions of Ruby and Rails:
|
13
13
|
|
14
|
-
* Ruby 2.
|
15
|
-
* Rails
|
14
|
+
* Ruby 2.7.0+
|
15
|
+
* Rails 6.0.0+
|
16
16
|
|
17
|
-
|
17
|
+
## Usage
|
18
18
|
|
19
19
|
1. include the gem in your Gemfile
|
20
20
|
|
21
|
-
```ruby
|
22
|
-
gem 'response_bank'
|
23
|
-
```
|
21
|
+
```ruby
|
22
|
+
gem 'response_bank'
|
23
|
+
```
|
24
24
|
|
25
25
|
2. add an initializer file. We need to configure the `acquire_lock` method, set the cache store and the logger
|
26
26
|
|
27
|
-
```ruby
|
28
|
-
require 'response_bank'
|
27
|
+
```ruby
|
28
|
+
require 'response_bank'
|
29
29
|
|
30
|
-
module ResponseBank
|
31
|
-
|
30
|
+
module ResponseBank
|
31
|
+
LOCK_TTL = 90
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
class << self
|
34
|
+
def acquire_lock(cache_key)
|
35
|
+
cache_store.write("#{cache_key}:lock", '1', unless_exist: true, expires_in: LOCK_TTL, raw: true)
|
36
|
+
end
|
37
|
+
end
|
36
38
|
end
|
37
|
-
end
|
38
|
-
end
|
39
39
|
|
40
|
-
ResponseBank.cache_store = ActiveSupport::Cache.lookup_store(Rails.configuration.cache_store)
|
41
|
-
ResponseBank.logger = Rails.logger
|
40
|
+
ResponseBank.cache_store = ActiveSupport::Cache.lookup_store(Rails.configuration.cache_store)
|
41
|
+
ResponseBank.logger = Rails.logger
|
42
42
|
|
43
|
-
```
|
43
|
+
```
|
44
44
|
|
45
45
|
3. enables caching on your application
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
config.action_controller.perform_caching = true
|
49
|
+
```
|
49
50
|
|
50
51
|
4. use `#response_cache` method to any desired controller's action
|
51
52
|
|
52
|
-
```ruby
|
53
|
-
class PostsController < ApplicationController
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
```ruby
|
54
|
+
class PostsController < ApplicationController
|
55
|
+
def show
|
56
|
+
response_cache do
|
57
|
+
@post = @shop.posts.find(params[:id])
|
58
|
+
respond_with(@post)
|
59
|
+
end
|
60
|
+
end
|
58
61
|
end
|
59
|
-
|
60
|
-
end
|
61
|
-
```
|
62
|
+
```
|
62
63
|
|
63
64
|
5. **(optional)** set a custom TTL for the cache by overriding the `write_to_backing_cache_store` method in your initializer file
|
64
|
-
```ruby
|
65
|
-
module ResponseBank
|
66
|
-
CACHE_TTL = 30.minutes
|
67
|
-
def write_to_backing_cache_store(_env, key, payload, expires_in: CACHE_TTL)
|
68
|
-
cache_store.write(key, payload, raw: true, expires_in: expires_in)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
```
|
72
65
|
|
73
|
-
|
66
|
+
```ruby
|
67
|
+
module ResponseBank
|
68
|
+
CACHE_TTL = 30.minutes
|
69
|
+
def write_to_backing_cache_store(_env, key, payload, expires_in: nil)
|
70
|
+
cache_store.write(key, payload, raw: true, expires_in: expires_in || CACHE_TTL)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
74
|
|
75
|
-
|
76
|
-
class PostsController < ApplicationController
|
77
|
-
before_action :set_shop
|
75
|
+
6. **(optional)** override custom cache key data. For default, cache key is defined by URL and query string
|
78
76
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
77
|
+
```ruby
|
78
|
+
class PostsController < ApplicationController
|
79
|
+
before_action :set_shop
|
80
|
+
|
81
|
+
def index
|
82
|
+
response_cache do
|
83
|
+
@post = @shop.posts
|
84
|
+
respond_with(@post)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def show
|
89
|
+
response_cache do
|
90
|
+
@post = @shop.posts.find(params[:id])
|
91
|
+
respond_with(@post)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def another_action
|
96
|
+
# custom cache key data
|
97
|
+
cache_key = {
|
98
|
+
action: action_name,
|
99
|
+
format: request.format,
|
100
|
+
shop_updated_at: @shop.updated_at
|
101
|
+
# you may add more keys here
|
102
|
+
}
|
103
|
+
response_cache cache_key do
|
104
|
+
@post = @shop.posts.find(params[:id])
|
105
|
+
respond_with(@post)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# override default cache key data globally per class
|
110
|
+
def cache_key_data
|
111
|
+
{
|
112
|
+
action: action_name,
|
113
|
+
format: request.format,
|
114
|
+
params: params.slice(:id),
|
115
|
+
shop_version: @shop.version
|
116
|
+
# you may add more keys here
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_shop
|
121
|
+
# @shop = ...
|
122
|
+
end
|
83
123
|
end
|
84
|
-
|
124
|
+
```
|
85
125
|
|
86
|
-
|
87
|
-
response_cache do
|
88
|
-
@post = @shop.posts.find(params[:id])
|
89
|
-
respond_with(@post)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def another_action
|
94
|
-
# custom cache key data
|
95
|
-
cache_key = {
|
96
|
-
action: action_name,
|
97
|
-
format: request.format,
|
98
|
-
shop_updated_at: @shop.updated_at
|
99
|
-
# you may add more keys here
|
100
|
-
}
|
101
|
-
response_cache cache_key do
|
102
|
-
@post = @shop.posts.find(params[:id])
|
103
|
-
respond_with(@post)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# override default cache key data globally per class
|
108
|
-
def cache_key_data
|
109
|
-
{
|
110
|
-
action: action_name,
|
111
|
-
format: request.format,
|
112
|
-
params: params.slice(:id),
|
113
|
-
shop_version: @shop.version
|
114
|
-
# you may add more keys here
|
115
|
-
}
|
116
|
-
end
|
117
|
-
|
118
|
-
def set_shop
|
119
|
-
# @shop = ...
|
120
|
-
end
|
121
|
-
end
|
122
|
-
```
|
123
|
-
|
124
|
-
### License
|
126
|
+
## License
|
125
127
|
|
126
128
|
ResponseBank is released under the [MIT License](LICENSE.txt).
|
@@ -3,6 +3,10 @@ require 'useragent'
|
|
3
3
|
|
4
4
|
module ResponseBank
|
5
5
|
class Middleware
|
6
|
+
# Limit the cached headers
|
7
|
+
# TODO: Make this lowercase/case-insentitive as per rfc2616 §4.2
|
8
|
+
CACHEABLE_HEADERS = ["Location", "Content-Type", "ETag", "Content-Encoding", "Last-Modified", "Cache-Control", "Expires", "Surrogate-Keys", "Cache-Tags"].freeze
|
9
|
+
|
6
10
|
REQUESTED_WITH = "HTTP_X_REQUESTED_WITH"
|
7
11
|
ACCEPT = "HTTP_ACCEPT"
|
8
12
|
USER_AGENT = "HTTP_USER_AGENT"
|
@@ -20,7 +24,6 @@ module ResponseBank
|
|
20
24
|
if env['cacheable.cache']
|
21
25
|
if [200, 404, 301, 304].include?(status)
|
22
26
|
headers['ETag'] = env['cacheable.key']
|
23
|
-
headers['X-Alternate-Cache-Key'] = env['cacheable.unversioned-key']
|
24
27
|
|
25
28
|
if ie_ajax_request?(env)
|
26
29
|
headers["Expires"] = "-1"
|
@@ -38,22 +41,18 @@ module ResponseBank
|
|
38
41
|
|
39
42
|
body_gz = ResponseBank.compress(body_string)
|
40
43
|
|
44
|
+
cached_headers = headers.slice(*CACHEABLE_HEADERS)
|
41
45
|
# Store result
|
42
|
-
cache_data = [status,
|
43
|
-
cache_data << headers['Location'] if status == 301
|
46
|
+
cache_data = [status, cached_headers, body_gz, timestamp]
|
44
47
|
|
45
48
|
ResponseBank.write_to_cache(env['cacheable.key']) do
|
46
49
|
payload = MessagePack.dump(cache_data)
|
47
50
|
ResponseBank.write_to_backing_cache_store(
|
48
51
|
env,
|
49
|
-
env['cacheable.key'],
|
52
|
+
env['cacheable.unversioned-key'],
|
50
53
|
payload,
|
51
54
|
expires_in: env['cacheable.versioned-cache-expiry'],
|
52
55
|
)
|
53
|
-
|
54
|
-
if env['cacheable.unversioned-key']
|
55
|
-
ResponseBank.write_to_backing_cache_store(env, env['cacheable.unversioned-key'], payload)
|
56
|
-
end
|
57
56
|
end
|
58
57
|
|
59
58
|
# since we had to generate the gz version above already we may
|
@@ -3,6 +3,8 @@ require 'digest/md5'
|
|
3
3
|
|
4
4
|
module ResponseBank
|
5
5
|
class ResponseCacheHandler
|
6
|
+
CACHE_KEY_SCHEMA_VERSION = 1
|
7
|
+
|
6
8
|
def initialize(
|
7
9
|
key_data:,
|
8
10
|
version_data:,
|
@@ -25,12 +27,13 @@ module ResponseBank
|
|
25
27
|
@force_refill_cache = force_refill_cache
|
26
28
|
@cache_store = cache_store
|
27
29
|
@headers = headers || {}
|
30
|
+
@key_schema_version = @env.key?('cacheable.key_version') ? @env.key['cacheable.key_version'] : CACHE_KEY_SCHEMA_VERSION
|
28
31
|
end
|
29
32
|
|
30
33
|
def run!
|
31
34
|
@env['cacheable.cache'] = true
|
32
|
-
@env['cacheable.key'] =
|
33
|
-
@env['cacheable.unversioned-key'] =
|
35
|
+
@env['cacheable.key'] = entity_tag_hash
|
36
|
+
@env['cacheable.unversioned-key'] = cache_key_hash
|
34
37
|
|
35
38
|
ResponseBank.log(cacheable_info_dump)
|
36
39
|
|
@@ -41,32 +44,32 @@ module ResponseBank
|
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
44
|
-
def
|
45
|
-
@
|
47
|
+
def entity_tag_hash
|
48
|
+
@entity_tag_hash ||= hash(entity_tag)
|
46
49
|
end
|
47
50
|
|
48
|
-
def
|
49
|
-
@
|
51
|
+
def cache_key_hash
|
52
|
+
@cache_key_hash ||= hash(cache_key)
|
50
53
|
end
|
51
54
|
|
52
55
|
private
|
53
56
|
|
54
|
-
def
|
57
|
+
def hash(key)
|
55
58
|
"cacheable:#{Digest::MD5.hexdigest(key)}"
|
56
59
|
end
|
57
60
|
|
58
|
-
def
|
59
|
-
@
|
61
|
+
def entity_tag
|
62
|
+
@entity_tag ||= ResponseBank.cache_key_for(key: @key_data, version: @version_data, key_schema_version: @key_schema_version)
|
60
63
|
end
|
61
64
|
|
62
|
-
def
|
63
|
-
@
|
65
|
+
def cache_key
|
66
|
+
@cache_key ||= ResponseBank.cache_key_for(key: @key_data, key_schema_version: @key_schema_version)
|
64
67
|
end
|
65
68
|
|
66
69
|
def cacheable_info_dump
|
67
70
|
log_info = [
|
68
|
-
"Raw cacheable.key: #{
|
69
|
-
"cacheable.key: #{
|
71
|
+
"Raw cacheable.key: #{entity_tag}",
|
72
|
+
"cacheable.key: #{entity_tag_hash}",
|
70
73
|
]
|
71
74
|
|
72
75
|
if @env['HTTP_IF_NONE_MATCH']
|
@@ -78,42 +81,19 @@ module ResponseBank
|
|
78
81
|
|
79
82
|
def try_to_serve_from_cache
|
80
83
|
# Etag
|
81
|
-
response = serve_from_browser_cache(
|
82
|
-
|
84
|
+
response = serve_from_browser_cache(entity_tag_hash, @env['HTTP_IF_NONE_MATCH'])
|
83
85
|
return response if response
|
84
86
|
|
85
|
-
|
86
|
-
response = if @serve_unversioned
|
87
|
-
serve_from_cache(unversioned_key_hash, "Cache hit: server (unversioned)")
|
88
|
-
else
|
89
|
-
serve_from_cache(versioned_key_hash, "Cache hit: server")
|
90
|
-
end
|
91
|
-
|
87
|
+
response = serve_from_cache(cache_key_hash, entity_tag_hash, @cache_age_tolerance)
|
92
88
|
return response if response
|
93
89
|
|
94
|
-
@env['cacheable.locked'] ||= false
|
95
|
-
|
96
|
-
if @env['cacheable.locked'] || ResponseBank.acquire_lock(versioned_key_hash)
|
97
|
-
# execute if we can get the lock
|
98
|
-
@env['cacheable.locked'] = true
|
99
|
-
elsif serving_from_noncurrent_but_recent_version_acceptable?
|
100
|
-
# serve a stale version
|
101
|
-
response = serve_from_cache(unversioned_key_hash, "Cache hit: server (recent)", @cache_age_tolerance)
|
102
|
-
|
103
|
-
return response if response
|
104
|
-
end
|
105
|
-
|
106
90
|
# No cache hit; this request cannot be handled from cache.
|
107
91
|
# Yield to the controller and mark for writing into cache.
|
108
92
|
refill_cache
|
109
93
|
end
|
110
94
|
|
111
|
-
def
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
def serve_from_browser_cache(cache_key_hash)
|
116
|
-
if @env["HTTP_IF_NONE_MATCH"] == cache_key_hash
|
95
|
+
def serve_from_browser_cache(entity_tag, if_none_match)
|
96
|
+
if etag_matches?(entity_tag, if_none_match)
|
117
97
|
@env['cacheable.miss'] = false
|
118
98
|
@env['cacheable.store'] = 'client'
|
119
99
|
|
@@ -126,7 +106,7 @@ module ResponseBank
|
|
126
106
|
end
|
127
107
|
end
|
128
108
|
|
129
|
-
def serve_from_cache(cache_key_hash,
|
109
|
+
def serve_from_cache(cache_key_hash, match_entity_tag = "*", cache_age_tolerance = nil)
|
130
110
|
raw = ResponseBank.read_from_backing_cache_store(@env, cache_key_hash, backing_cache_store: @cache_store)
|
131
111
|
|
132
112
|
if raw
|
@@ -135,37 +115,73 @@ module ResponseBank
|
|
135
115
|
@env['cacheable.miss'] = false
|
136
116
|
@env['cacheable.store'] = 'server'
|
137
117
|
|
138
|
-
status,
|
118
|
+
status, headers, body, timestamp = hit
|
139
119
|
|
140
|
-
|
141
|
-
ResponseBank.log("Found an unversioned cache entry, but it was too old (#{timestamp})")
|
120
|
+
@env['cacheable.locked'] ||= false
|
142
121
|
|
143
|
-
|
122
|
+
# to preserve the unversioned/versioned logging messages from past releases we split the match_entity_tag test
|
123
|
+
if match_entity_tag == "*"
|
124
|
+
ResponseBank.log("Cache hit: server (unversioned)")
|
125
|
+
# page tolerance only applies for versioned + etag mismatch
|
126
|
+
elsif etag_matches?(headers['ETag'], match_entity_tag)
|
127
|
+
ResponseBank.log("Cache hit: server")
|
144
128
|
else
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
129
|
+
# cache miss; check to see if any parallel requests already are regenerating the cache
|
130
|
+
if ResponseBank.acquire_lock(match_entity_tag)
|
131
|
+
# execute if we can get the lock
|
132
|
+
@env['cacheable.locked'] = true
|
133
|
+
return
|
134
|
+
elsif stale_while_revalidate?(timestamp, cache_age_tolerance)
|
135
|
+
# cache is being regenerated, can we avoid piling on and use a stale version in the interim?
|
136
|
+
ResponseBank.log("Cache hit: server (recent)")
|
151
137
|
else
|
152
|
-
|
153
|
-
|
154
|
-
body = ResponseBank.decompress(body)
|
138
|
+
ResponseBank.log("Found an unversioned cache entry, but it was too old (#{timestamp})")
|
139
|
+
return
|
155
140
|
end
|
141
|
+
end
|
156
142
|
|
157
|
-
|
143
|
+
# version check
|
144
|
+
# unversioned but tolerance threshold
|
145
|
+
# regen
|
146
|
+
@headers = @headers.merge(headers)
|
158
147
|
|
159
|
-
|
148
|
+
if @env["gzip"]
|
149
|
+
@headers['Content-Encoding'] = "gzip"
|
150
|
+
else
|
151
|
+
# we have to uncompress because the client doesn't support gzip
|
152
|
+
ResponseBank.log("uncompressing for client without gzip")
|
153
|
+
body = ResponseBank.decompress(body)
|
160
154
|
end
|
155
|
+
[status, @headers, [body]]
|
161
156
|
end
|
162
157
|
end
|
163
158
|
|
164
|
-
def
|
165
|
-
|
159
|
+
def etag_matches?(entity_tag, if_none_match)
|
160
|
+
# Support for Etag variations including:
|
161
|
+
# If-None-Match: abc
|
162
|
+
# If-None-Match: "abc"
|
163
|
+
# If-None-Match: W/"abc"
|
164
|
+
# If-None-Match: "abc", "def"
|
165
|
+
# If-None-Match: "*"
|
166
|
+
return false unless entity_tag
|
167
|
+
return false unless if_none_match
|
168
|
+
|
169
|
+
# strictly speaking an unquoted etag is not valid, yet common
|
170
|
+
# to avoid unintended greedy matches in we check for naked entity then includes with quoted entity values
|
171
|
+
if_none_match == "*" || if_none_match == entity_tag || if_none_match.include?(%{"#{entity_tag}"})
|
172
|
+
end
|
173
|
+
|
174
|
+
def stale_while_revalidate?(timestamp, cache_age_tolerance)
|
175
|
+
return false if !cache_age_tolerance
|
176
|
+
return false if !timestamp
|
177
|
+
|
178
|
+
timestamp >= (Time.now.to_i - cache_age_tolerance)
|
166
179
|
end
|
167
180
|
|
168
181
|
def refill_cache
|
182
|
+
# non cache hits do not yet have the lock
|
183
|
+
ResponseBank.acquire_lock(entity_tag_hash) unless @env['cacheable.locked']
|
184
|
+
@env['cacheable.locked'] = true
|
169
185
|
@env['cacheable.miss'] = true
|
170
186
|
|
171
187
|
ResponseBank.log("Refilling cache")
|
data/lib/response_bank.rb
CHANGED
@@ -49,17 +49,17 @@ module ResponseBank
|
|
49
49
|
|
50
50
|
key = hash_value_str(data[:key])
|
51
51
|
|
52
|
-
|
52
|
+
key = %{#{data[:key_schema_version]}:#{key}} if data[:key_schema_version]
|
53
53
|
|
54
|
-
|
54
|
+
key = %{#{key}:#{hash_value_str(data[:version])}} if data[:version]
|
55
55
|
|
56
|
-
|
56
|
+
key
|
57
57
|
when Array
|
58
58
|
data.inspect
|
59
59
|
when Time, DateTime
|
60
60
|
data.to_i
|
61
61
|
when Date
|
62
|
-
data.
|
62
|
+
data.to_s # Date#to_i does not support timezones, using iso8601 instead
|
63
63
|
when true, false, Integer, Symbol, String
|
64
64
|
data.inspect
|
65
65
|
else
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: response_bank
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tobias Lütke
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-03-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: useragent
|
@@ -45,28 +45,28 @@ dependencies:
|
|
45
45
|
requirements:
|
46
46
|
- - ">="
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: 5.
|
48
|
+
version: 5.18.0
|
49
49
|
type: :development
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: 5.
|
55
|
+
version: 5.18.0
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: mocha
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - ">="
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version:
|
62
|
+
version: 2.0.0
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
69
|
+
version: 2.0.0
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: rake
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,28 +87,14 @@ dependencies:
|
|
87
87
|
requirements:
|
88
88
|
- - ">="
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version: '
|
90
|
+
version: '6.1'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
95
|
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
98
|
-
- !ruby/object:Gem::Dependency
|
99
|
-
name: tzinfo-data
|
100
|
-
requirement: !ruby/object:Gem::Requirement
|
101
|
-
requirements:
|
102
|
-
- - ">="
|
103
|
-
- !ruby/object:Gem::Version
|
104
|
-
version: 1.2019.3
|
105
|
-
type: :development
|
106
|
-
prerelease: false
|
107
|
-
version_requirements: !ruby/object:Gem::Requirement
|
108
|
-
requirements:
|
109
|
-
- - ">="
|
110
|
-
- !ruby/object:Gem::Version
|
111
|
-
version: 1.2019.3
|
97
|
+
version: '6.1'
|
112
98
|
description:
|
113
99
|
email:
|
114
100
|
- tobi@shopify.com
|
@@ -139,14 +125,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
125
|
requirements:
|
140
126
|
- - ">="
|
141
127
|
- !ruby/object:Gem::Version
|
142
|
-
version: 2.
|
128
|
+
version: 2.7.0
|
143
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
130
|
requirements:
|
145
131
|
- - ">="
|
146
132
|
- !ruby/object:Gem::Version
|
147
133
|
version: '0'
|
148
134
|
requirements: []
|
149
|
-
rubygems_version: 3.
|
135
|
+
rubygems_version: 3.4.9
|
150
136
|
signing_key:
|
151
137
|
specification_version: 4
|
152
138
|
summary: Simple response caching for Ruby applications
|