request_store_rails 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4179f342e1c92035d2daf543887e02c5b2e5620
4
- data.tar.gz: ae71e457d209160ae4ccc736f93cb252769640ff
3
+ metadata.gz: 67dd4d34bad2c45e0df9cc7ddc2f97a9733a65d2
4
+ data.tar.gz: f55c26ea388c9b440c63c2b594f501f3577c1c9a
5
5
  SHA512:
6
- metadata.gz: 480eee450b8cc4f6d9fb105ceaf9ef1520123cb8b9298466b53a22b7ebd8fadcb459349c3700668407787fcee6e6497a5037b5ccf0d5b0876e9b2aaf0ead3c09
7
- data.tar.gz: e8cfc0b07cd322fd4dffa9209f9743a88ac1021a9506e913e7be7e4c3c4305a49c6992ffe3455a79745052249cb8eca154c237a8618fa49d6598da3bf5f9d866
6
+ metadata.gz: 2b85018a8b58bb67f032d234cc7f4390d0b60df2c177ab5bb28c9f063c65bca06a937019ad5e2855b27d577cf5dbc29eca45dad6bb0d11cc198826d6aa06f33b
7
+ data.tar.gz: a4a9b62d4546f8700867489abdf9afe6696f42305dfbeba3ead5a3b9d9c164e05aac0f56765db87f57599ba7e16267a78ff55c4c1cd40490c0f167636c6ec0f2
data/README.md CHANGED
@@ -1,249 +1,145 @@
1
- Queryable
1
+ RequestLocals
2
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) -->
3
+ [![Gem Version](https://badge.fury.io/rb/request_store_rails.svg)](http://badge.fury.io/rb/request_store_rails)
4
+ [![Build Status](https://travis-ci.org/ElMassimo/request_store_rails.svg)](https://travis-ci.org/ElMassimo/request_store_rails)
5
+ [![Test Coverage](https://codeclimate.com/github/ElMassimo/request_store_rails/badges/coverage.svg)](https://codeclimate.com/github/ElMassimo/request_store_rails)
6
+ [![Code Climate](https://codeclimate.com/github/ElMassimo/request_store_rails/badges/gpa.svg)](https://codeclimate.com/github/ElMassimo/request_store_rails)
7
+ [![Inline docs](http://inch-ci.org/github/ElMassimo/request_store_rails.svg)](http://inch-ci.org/github/ElMassimo/request_store_rails)
8
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ElMassimo/request_store_rails/blob/master/LICENSE.txt)
10
9
 
11
- Queryable is a mixin that allows you to easily define query objects with chainable scopes.
10
+ If you have ever needed to use a global variable in Rails, you know it sucks.
12
11
 
13
- ### Scopes
12
+ One of the usual tricks is to go for `Thread.current`, or if you have done your
13
+ homework, to use the awesome [`request_store`](https://github.com/steveklabnik/request_store).
14
14
 
15
- Scopes serve to encapsulate reusable business rules, a method is defined with
16
- the selected name and block (or proc)
17
15
  ```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
16
+ # Using Thread.current
17
+ def self.foo
18
+ Thread.current[:foo] ||= 0
36
19
  end
37
20
 
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?
21
+ def self.foo=(value)
22
+ Thread.current[:foo] = value
51
23
  end
52
- ```
53
24
 
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
25
+ # Using RequestStore
26
+ def self.foo
27
+ RequestStore.fetch(:foo) { 0 }
67
28
  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
29
 
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
30
+ def self.foo=(value)
31
+ RequestStore.store[:foo] = value
85
32
  end
86
-
87
- CustomersQuery.new.where(:amount_purchased.gt => 2).active.asc(:logged_in_at)
88
33
  ```
89
34
 
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.
35
+ ### The problem
92
36
 
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).
37
+ - Using `Thread.current`, values can stick around even after the request is over,
38
+ since some servers have a pool of Threads that they reuse, which [can cause bugs](https://github.com/steveklabnik/request_store#the-problem).
97
39
 
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.
40
+ - Using `request_store`, the storage is _*not actually*_ request local. Variables
41
+ are stored in `Thread.current`, except that the storage is cleared after each
42
+ request. However, this does not work when you need to use multiple threads per
43
+ request, _different_ threads access _different_ stores.
102
44
 
103
- ```ruby
104
- require 'queryable/default_query'
45
+ ### The solution
105
46
 
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:
47
+ Add this line to your Gemfile:
120
48
 
121
49
  ```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
50
+ gem 'request_store_rails'
133
51
  ```
134
52
 
135
- ### DefaultScope
136
- Allows to define default scopes in query objects, and inherit them in query
137
- object subclasses.
53
+ And change the code to this:
138
54
 
139
55
  ```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) }
56
+ def self.foo
57
+ RequestLocals.fetch(:foo) { 0 }
149
58
  end
150
59
 
151
- def BigCustomersQuery < CustomersQuery
152
- default_scope :big_spender
153
- scope :big_spender, -> { where(:total_expense.gt => 9999999) }
60
+ def self.foo=(value)
61
+ RequestLocals.store[:foo] = value
154
62
  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
63
  ```
161
64
 
162
- ### Chainable
65
+ Oh yeah, everywhere you used `Thread.current` or `RequestStore.store` just
66
+ change it to `RequestLocals.store`. Now your variables will actually be stored
67
+ in a true _request-local_ way.
163
68
 
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.
69
+ ### No Rails? No Problem!
167
70
 
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.
71
+ A Railtie is added that configures the Middleware for you, but if you're not
72
+ using Rails, no biggie! Just use the Middleware yourself, however you need.
73
+ You'll probably have to shove this somewhere:
171
74
 
172
75
  ```ruby
173
- class CustomersQuery
174
- include Queryable
175
- include Queryable::Chainable
76
+ use RequestStoreRails::Middleware
77
+ ```
176
78
 
177
- chain :active, :recent
79
+ ## Multi-Threading
80
+ The middleware in the gem sets a thread-local variable `:request_id` in
81
+ `Thread.current` for the main thread that is executing the request.
178
82
 
179
- def active
180
- where(status: 'active')
181
- end
83
+ If you need to spawn threads within a server that is already using thread-based
84
+ concurrency, all you need to do is to make sure that the `:request_id`
85
+ variable is set for your threads, and you will be able to access the
86
+ `RequestLocals` as usual.
182
87
 
183
- def recent
184
- queryable.desc(:logged_in_at)
185
- end
88
+ A good way to apply this pattern is by encapsulating it into a helper class:
186
89
 
187
- chain def search(field_values)
188
- field_values.inject(queryable) { |query, (field, value)|
189
- query.where(field => /#{value}/i)
90
+ ```ruby
91
+ # Public: Custom thread class that allows us to preserve the request context.
92
+ class ThreadWithContext
93
+
94
+ # Public: Returns a new Thread that preserves the context of the current request.
95
+ def ThreadWithContext.new(*args)
96
+ request_id = Thread.current[:request_id]
97
+ Thread.new {
98
+ Thread.current[:request_id] = request_id
99
+ yield *args
190
100
  }
191
101
  end
192
-
193
- def search_in_active(field_values)
194
- search(field_values).active
195
- end
196
102
  end
197
103
 
104
+ RequestLocals[:foo] = 1
198
105
 
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
106
+ ThreadWithContext.new {
107
+ puts RequestLocals[:foo] # => 1
108
+ }
228
109
  ```
110
+ The gem does not provide such construct to avoid name collisions, you are free
111
+ to reuse the snippet above and adjust it to match your use case.
229
112
 
230
- ## Testing
113
+ If you are feeling adventurous, you could try using this [fire and forget script](https://gist.github.com/ElMassimo/e2f99848db6a415f1aaa) and make all of your threads request aware, or
114
+ should I say _prepend and forget_ :smile:? Probably not something to be used in
115
+ a production environment, but whatever floats your boat :boat:
231
116
 
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.
117
+ ### Atomicity
118
+ Have in mind that the `RequestLocals.fetch(:foo) { 'default' }` operation is
119
+ [atomic](https://github.com/ElMassimo/request_store_rails/blob/master/lib/request_locals.rb#L62),
120
+ while `RequestLocal[:foo] ||= 'default'` is not. In most scenarios, there is not
121
+ a lot of difference, but if you are in a concurrent environment make sure to
122
+ use the one that is more suitable for your use case :wink:
235
123
 
236
- ## RDocs
124
+ ## Special Thanks
125
+ The inspiration for this gem, tests, and a big part of the readme were borrowed
126
+ from the really cool [`request_store`](https://github.com/steveklabnik/request_store) gem.
127
+ Thanks [Steve](https://github.com/steveklabnik) :smiley:
237
128
 
238
- You can view the **Queryable** documentation in RDoc format here:
129
+ ## Contributing
239
130
 
240
- http://rubydoc.info/github/ElMassimo/queryable/master/frames
131
+ 1. Fork it
132
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
133
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
134
+ 4. Push to the branch (`git push origin my-new-feature`)
135
+ 5. Create new Pull Request
241
136
 
137
+ Don't forget to run the tests with `rake`.
242
138
 
243
139
  License
244
140
  --------
245
141
 
246
- Copyright (c) 2014 Máximo Mussini
142
+ Copyright (c) 2015 Máximo Mussini
247
143
 
248
144
  Permission is hereby granted, free of charge, to any person obtaining
249
145
  a copy of this software and associated documentation files (the
@@ -59,7 +59,7 @@ class RequestLocals
59
59
  # Returns an existing value for the key is found, otherwise it returns the
60
60
  # value yielded by the block.
61
61
  def fetch(key, &block)
62
- store.fetch_or_store(key, &block)
62
+ store.compute_if_absent(key, &block)
63
63
  end
64
64
 
65
65
  protected
@@ -69,7 +69,7 @@ protected
69
69
  #
70
70
  # Returns a ThreadSafe::Cache.
71
71
  def store
72
- @cache.fetch_or_store(current_request_id) { new_store }
72
+ @cache.compute_if_absent(current_request_id) { new_store }
73
73
  end
74
74
 
75
75
  # Internal: The current request is inferred from the current thread. It's very
@@ -1,8 +1,9 @@
1
1
  require 'securerandom'
2
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
3
  module RequestStoreRails
4
+
5
+ # Public: Middleware that takes care of setting the thread-local variable
6
+ # :request_id, which enables RequestLocals to associate threads and requests.
6
7
  class Middleware
7
8
 
8
9
  def initialize(app)
@@ -1,6 +1,7 @@
1
- # Internal: Inserts the middleware to manage request_ids and cleaning up after
2
- # the request is complete.
3
1
  module RequestStoreRails
2
+
3
+ # Internal: Inserts the middleware to manage request_ids and cleaning up after
4
+ # the request is complete.
4
5
  class Railtie < ::Rails::Railtie
5
6
 
6
7
  initializer 'request_store_rails.insert_middleware' do |app|
@@ -1,5 +1,4 @@
1
- # Public: Version of the gem.
2
1
  module RequestStoreRails
3
2
 
4
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
5
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: request_store_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Máximo Mussini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-11 00:00:00.000000000 Z
11
+ date: 2015-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -58,20 +58,6 @@ dependencies:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
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
61
  description: RequestLocals gives you per-request global storage in Rails
76
62
  email:
77
63
  - maximomussini@gmail.com