request_store_rails 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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