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 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: []