request_store_rails 0.0.1

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 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
@@ -0,0 +1,5 @@
1
+ # Public: Version of the gem.
2
+ module RequestStoreRails
3
+
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'request_store_rails/version'
2
+
3
+ require 'request_locals'
4
+ require 'request_store_rails/middleware'
5
+ require 'request_store_rails/railtie' if defined?(Rails::Railtie)
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: []