request_store_rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +265 -0
- data/lib/request_locals.rb +87 -0
- data/lib/request_store_rails/middleware.rb +29 -0
- data/lib/request_store_rails/railtie.rb +14 -0
- data/lib/request_store_rails/version.rb +5 -0
- data/lib/request_store_rails.rb +5 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b4179f342e1c92035d2daf543887e02c5b2e5620
|
4
|
+
data.tar.gz: ae71e457d209160ae4ccc736f93cb252769640ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 480eee450b8cc4f6d9fb105ceaf9ef1520123cb8b9298466b53a22b7ebd8fadcb459349c3700668407787fcee6e6497a5037b5ccf0d5b0876e9b2aaf0ead3c09
|
7
|
+
data.tar.gz: e8cfc0b07cd322fd4dffa9209f9743a88ac1021a9506e913e7be7e4c3c4305a49c6992ffe3455a79745052249cb8eca154c237a8618fa49d6598da3bf5f9d866
|
data/README.md
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
Queryable
|
2
|
+
=====================
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/queryable.svg)](http://badge.fury.io/rb/queryable)
|
4
|
+
[![Build Status](https://travis-ci.org/ElMassimo/queryable.svg)](https://travis-ci.org/ElMassimo/queryable)
|
5
|
+
[![Test Coverage](https://codeclimate.com/github/ElMassimo/queryable/badges/coverage.svg)](https://codeclimate.com/github/ElMassimo/queryable)
|
6
|
+
[![Code Climate](https://codeclimate.com/github/ElMassimo/queryable.png)](https://codeclimate.com/github/ElMassimo/queryable)
|
7
|
+
[![Inline docs](http://inch-ci.org/github/ElMassimo/queryable.svg)](http://inch-ci.org/github/ElMassimo/queryable)
|
8
|
+
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ElMassimo/queryable/blob/master/LICENSE.txt)
|
9
|
+
<!-- [![Coverage Status](https://coveralls.io/repos/ElMassimo/queryable/badge.png)](https://coveralls.io/r/ElMassimo/queryable) -->
|
10
|
+
|
11
|
+
Queryable is a mixin that allows you to easily define query objects with chainable scopes.
|
12
|
+
|
13
|
+
### Scopes
|
14
|
+
|
15
|
+
Scopes serve to encapsulate reusable business rules, a method is defined with
|
16
|
+
the selected name and block (or proc)
|
17
|
+
```ruby
|
18
|
+
class CustomersQuery
|
19
|
+
include Queryable
|
20
|
+
|
21
|
+
scope(:recent) { desc(:logged_in_at) }
|
22
|
+
|
23
|
+
scope :active, ->{ where(status: 'active') }
|
24
|
+
|
25
|
+
scope :favourite_brand do |product, brand|
|
26
|
+
where("favourites.#{product}": brand)
|
27
|
+
end
|
28
|
+
|
29
|
+
def current
|
30
|
+
recent.active
|
31
|
+
end
|
32
|
+
|
33
|
+
def miller_fans
|
34
|
+
favourite_brand(:beer, :Miller)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
CustomerQuery.new(shop.customers).miller_fans
|
40
|
+
```
|
41
|
+
|
42
|
+
### Delegation
|
43
|
+
|
44
|
+
By default most Array methods are delegated to the internal query. It's possible
|
45
|
+
to delegate extra methods to the query by calling `delegate`.
|
46
|
+
```ruby
|
47
|
+
class CustomersQuery
|
48
|
+
include Queryable
|
49
|
+
|
50
|
+
delegate :update_all, :destroy_all, :exists?
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
### Delegate and Chain
|
55
|
+
|
56
|
+
Sometimes you want to delegate a method to the internal query, but continue
|
57
|
+
working with the query object like if you were calling scopes.
|
58
|
+
|
59
|
+
You can achieve that using `delegate_and_chain`, which will delegate the method
|
60
|
+
call, assign the return value as the internal query, and return the query object.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
class CustomersQuery
|
64
|
+
include Queryable
|
65
|
+
|
66
|
+
delegate_and_chain :where, :order_by
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
## Advantages
|
71
|
+
|
72
|
+
* Query objects are easy to understand.
|
73
|
+
* You can inherit, mixin, and chain queries in a very natural way.
|
74
|
+
* Increased testability, pretty close to being ORM/ODM agnostic.
|
75
|
+
|
76
|
+
## Basic Usage
|
77
|
+
|
78
|
+
If you are using Mongoid or ActiveRecord, you might want to try the
|
79
|
+
`Queryable::Mongoid` and `Queryable::ActiveRecord` modules that already take
|
80
|
+
care of delegating and chaining most of the methods in the underlying queries.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class CustomersQuery
|
84
|
+
include Queryable::Mongoid
|
85
|
+
end
|
86
|
+
|
87
|
+
CustomersQuery.new.where(:amount_purchased.gt => 2).active.asc(:logged_in_at)
|
88
|
+
```
|
89
|
+
|
90
|
+
This modules also include all the optional modules. If you would like to opt-out
|
91
|
+
of the other modules you can follow the approach in the [Notes](https://github.com/ElMassimo/queryable#notes) section.
|
92
|
+
|
93
|
+
## Advanced Usage
|
94
|
+
There are three opt-in modules that can help you when creating query objects.
|
95
|
+
These modules would need to be manually required during app initialization or
|
96
|
+
wherever necessary (in Rails, config/initializers).
|
97
|
+
|
98
|
+
### DefaultQuery
|
99
|
+
Provides default initialization for query objects, by attempting to infer the
|
100
|
+
class name of the default collection for the query, and it also provides a
|
101
|
+
`queryable` method to specify it.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
require 'queryable/default_query'
|
105
|
+
|
106
|
+
def CustomersQuery
|
107
|
+
include Queryable
|
108
|
+
include Queryable::DefaultQuery
|
109
|
+
end
|
110
|
+
|
111
|
+
def OldCustomersQuery < CustomersQuery
|
112
|
+
queryable ArchivedCustomers
|
113
|
+
end
|
114
|
+
|
115
|
+
CustomersQuery.new.queryable == Customer.all
|
116
|
+
OldCustomersQuery.new.queryable == ArchivedCustomers.all
|
117
|
+
```
|
118
|
+
If you want to use common base objects for your queries, you may want want to
|
119
|
+
delay the automatic inference:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
class BaseQuery
|
123
|
+
include Queryable
|
124
|
+
include Queryable::DefaultQuery
|
125
|
+
|
126
|
+
queryable false
|
127
|
+
end
|
128
|
+
|
129
|
+
class CustomersQuery < BaseQuery
|
130
|
+
end
|
131
|
+
|
132
|
+
CustomersQuery.new.queryable == Customer.all
|
133
|
+
```
|
134
|
+
|
135
|
+
### DefaultScope
|
136
|
+
Allows to define default scopes in query objects, and inherit them in query
|
137
|
+
object subclasses.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
require 'queryable/default_scope'
|
141
|
+
|
142
|
+
def CustomersQuery
|
143
|
+
include Queryable
|
144
|
+
include Queryable::DefaultScope
|
145
|
+
include Queryable::DefaultQuery
|
146
|
+
|
147
|
+
default_scope :active
|
148
|
+
scope :active, -> { where(:last_purchase.gt => 7.days.ago) }
|
149
|
+
end
|
150
|
+
|
151
|
+
def BigCustomersQuery < CustomersQuery
|
152
|
+
default_scope :big_spender
|
153
|
+
scope :big_spender, -> { where(:total_expense.gt => 9999999) }
|
154
|
+
end
|
155
|
+
|
156
|
+
CustomersQuery.new.queryable == Customer.where(:last_purchase.gt => 7.days.ago)
|
157
|
+
|
158
|
+
BigCustomersQuery.new.queryable ==
|
159
|
+
Customer.where(:last_purchase.gt => 7.days.ago, :total_expense.gt => 9999999)
|
160
|
+
```
|
161
|
+
|
162
|
+
### Chainable
|
163
|
+
|
164
|
+
While scopes are great because of their terseness, they can be limiting because
|
165
|
+
the block executes in the context of the internal query, so methods, constants,
|
166
|
+
and variables of the Queryable are not accessible.
|
167
|
+
|
168
|
+
For those cases, you can use a normal method, and then `chain` it. Chainable
|
169
|
+
will take care of setting the return value of the method as the internal query,
|
170
|
+
and return `self` at the end to make the method chainable.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
class CustomersQuery
|
174
|
+
include Queryable
|
175
|
+
include Queryable::Chainable
|
176
|
+
|
177
|
+
chain :active, :recent
|
178
|
+
|
179
|
+
def active
|
180
|
+
where(status: 'active')
|
181
|
+
end
|
182
|
+
|
183
|
+
def recent
|
184
|
+
queryable.desc(:logged_in_at)
|
185
|
+
end
|
186
|
+
|
187
|
+
chain def search(field_values)
|
188
|
+
field_values.inject(queryable) { |query, (field, value)|
|
189
|
+
query.where(field => /#{value}/i)
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
def search_in_active(field_values)
|
194
|
+
search(field_values).active
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
CustomerQuery.new(shop.customers).miller_fans.search_in_current(last_name: 'M')
|
200
|
+
```
|
201
|
+
|
202
|
+
### Notes
|
203
|
+
To avoid repetition, it's a good idea to create a `BaseQuery` object
|
204
|
+
to contain both the modules inclusion, and common scopes you may reuse.
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
require 'queryable/chainable'
|
208
|
+
require 'queryable/default_scope'
|
209
|
+
require 'queryable/default_query'
|
210
|
+
|
211
|
+
def BaseQuery
|
212
|
+
include Queryable
|
213
|
+
include Queryable::Chainable
|
214
|
+
include Queryable::DefaultScope
|
215
|
+
include Queryable::DefaultQuery
|
216
|
+
|
217
|
+
# If you want to be concise:
|
218
|
+
include Queryable::DefaultQuery, Queryable::DefaultScope, Queryable::Chainable, Queryable
|
219
|
+
|
220
|
+
queryable false
|
221
|
+
|
222
|
+
scope :recent, ->{ where(:created_at.gt => 1.week.ago) }
|
223
|
+
end
|
224
|
+
|
225
|
+
def CustomersQuery < BaseQuery
|
226
|
+
...
|
227
|
+
end
|
228
|
+
```
|
229
|
+
|
230
|
+
## Testing
|
231
|
+
|
232
|
+
You can check the [specs](https://github.com/ElMassimo/queryable/tree/master/spec) of the project
|
233
|
+
to check how to test query objects without even having to require the ORM/ODM, or
|
234
|
+
you can test by requiring your ORM/ODM and executing queries as usual.
|
235
|
+
|
236
|
+
## RDocs
|
237
|
+
|
238
|
+
You can view the **Queryable** documentation in RDoc format here:
|
239
|
+
|
240
|
+
http://rubydoc.info/github/ElMassimo/queryable/master/frames
|
241
|
+
|
242
|
+
|
243
|
+
License
|
244
|
+
--------
|
245
|
+
|
246
|
+
Copyright (c) 2014 Máximo Mussini
|
247
|
+
|
248
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
249
|
+
a copy of this software and associated documentation files (the
|
250
|
+
"Software"), to deal in the Software without restriction, including
|
251
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
252
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
253
|
+
permit persons to whom the Software is furnished to do so, subject to
|
254
|
+
the following conditions:
|
255
|
+
|
256
|
+
The above copyright notice and this permission notice shall be
|
257
|
+
included in all copies or substantial portions of the Software.
|
258
|
+
|
259
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
260
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
261
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
262
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
263
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
264
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
265
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'thread_safe'
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
4
|
+
|
5
|
+
# Public: Provides per-request global storage, by offering an interface that is
|
6
|
+
# very similar to Rails.cache, or Hash.
|
7
|
+
#
|
8
|
+
# The store may be shared between threads, as long as the :request_id
|
9
|
+
# thread-local variable is set.
|
10
|
+
#
|
11
|
+
# Intended to work in collaboration with the RequestStoreRails middleware, that
|
12
|
+
# will clear the request local variables after each request.
|
13
|
+
class RequestLocals
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
# Public: Public methods of RequestLocals, they are delegated to the Singleton instance.
|
19
|
+
delegate :clear!, :clear_all!, :[], :[]=, :fetch, :delete, :exist?, :empty?, to: :instance
|
20
|
+
|
21
|
+
# Public: There is no accounting for taste, RequestLocals[:foo] vs RequestLocals.store[:foo]
|
22
|
+
alias_method :store, :instance
|
23
|
+
|
24
|
+
# Internal: We don't want to expose the Singleton implementation details.
|
25
|
+
private :instance
|
26
|
+
end
|
27
|
+
|
28
|
+
# Internal: Methods of the RequestLocals instance, delegated to the request-local structure.
|
29
|
+
delegate :[], :[]=, :delete, :empty?, to: :store
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@cache = ThreadSafe::Cache.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Removes all the request-local variables.
|
36
|
+
#
|
37
|
+
# Returns nothing.
|
38
|
+
def clear!
|
39
|
+
@cache.delete(current_request_id)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Public: Clears all the request-local variable stores.
|
43
|
+
#
|
44
|
+
# Returns nothing.
|
45
|
+
def clear_all!
|
46
|
+
@cache = ThreadSafe::Cache.new
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Checks if a value was stored for the given key.
|
50
|
+
#
|
51
|
+
# Returns true if there is a value stored for the key.
|
52
|
+
def exist?(key)
|
53
|
+
store.key?(key)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Implements fetch in a consistent way with Rails.cache, persisting
|
57
|
+
# the value yielded by the block if the key was not found.
|
58
|
+
#
|
59
|
+
# Returns an existing value for the key is found, otherwise it returns the
|
60
|
+
# value yielded by the block.
|
61
|
+
def fetch(key, &block)
|
62
|
+
store.fetch_or_store(key, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
# Internal: Returns the structure that holds the request-local variables for
|
68
|
+
# the current request.
|
69
|
+
#
|
70
|
+
# Returns a ThreadSafe::Cache.
|
71
|
+
def store
|
72
|
+
@cache.fetch_or_store(current_request_id) { new_store }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Internal: The current request is inferred from the current thread. It's very
|
76
|
+
# important to pass on the :request_id thread local variable when spawning new
|
77
|
+
# threads within a single request.
|
78
|
+
def current_request_id
|
79
|
+
Thread.current[:request_id]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Internal: Returns a new empty structure where the request-local variables
|
83
|
+
# will be stored.
|
84
|
+
def new_store
|
85
|
+
ThreadSafe::Cache.new
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
# Public: Middleware that takes care of setting the thread-local variable
|
4
|
+
# :request_id, which enables RequestLocals to associate threads and requests.
|
5
|
+
module RequestStoreRails
|
6
|
+
class Middleware
|
7
|
+
|
8
|
+
def initialize(app)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
# Internal: Assigns the :request_id thread-local variable, and cleans up all
|
13
|
+
# the request-local variables after the request.
|
14
|
+
def call(env)
|
15
|
+
Thread.current[:request_id] = extract_request_id(env)
|
16
|
+
@app.call(env)
|
17
|
+
ensure
|
18
|
+
RequestLocals.clear!
|
19
|
+
Thread.current[:request_id] = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
# Internal: Extracts the request id from the environment, or generates one.
|
25
|
+
def extract_request_id(env)
|
26
|
+
env['action_dispatch.request_id'] || env['HTTP_X_REQUEST_ID'] || SecureRandom.hex(16)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Internal: Inserts the middleware to manage request_ids and cleaning up after
|
2
|
+
# the request is complete.
|
3
|
+
module RequestStoreRails
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
|
6
|
+
initializer 'request_store_rails.insert_middleware' do |app|
|
7
|
+
app.config.middleware.insert_after ActionDispatch::RequestId, RequestStoreRails::Middleware
|
8
|
+
|
9
|
+
ActionDispatch::Reloader.to_cleanup do
|
10
|
+
RequestLocals.clear_all!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: request_store_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Máximo Mussini
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thread_safe
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.3'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 0.3.5
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0.3'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 0.3.5
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rails
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.2'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.2'
|
75
|
+
description: RequestLocals gives you per-request global storage in Rails
|
76
|
+
email:
|
77
|
+
- maximomussini@gmail.com
|
78
|
+
executables: []
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files:
|
81
|
+
- README.md
|
82
|
+
files:
|
83
|
+
- README.md
|
84
|
+
- lib/request_locals.rb
|
85
|
+
- lib/request_store_rails.rb
|
86
|
+
- lib/request_store_rails/middleware.rb
|
87
|
+
- lib/request_store_rails/railtie.rb
|
88
|
+
- lib/request_store_rails/version.rb
|
89
|
+
homepage: https://github.com/ElMassimo/request_store_rails
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options:
|
95
|
+
- "--charset=UTF-8"
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.9.3
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.4.3
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Per-request global storage for Rails
|
114
|
+
test_files: []
|