request_store-1.5.1/0000755000004100000410000000000014173305310014447 5ustar www-datawww-datarequest_store-1.5.1/.travis.yml0000644000004100000410000000040514173305310016557 0ustar www-datawww-datalanguage: ruby rvm: - 1.9.3 - 2.0.0 - 2.1.0 - 2.2.3 - 2.4.9 - 2.5.7 - 2.6.5 - jruby-18mode - jruby-19mode - ruby-head - jruby-head - truffleruby-head matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head request_store-1.5.1/test/0000755000004100000410000000000014173305310015426 5ustar www-datawww-datarequest_store-1.5.1/test/middleware_test.rb0000644000004100000410000000442714173305310021136 0ustar www-datawww-datarequire 'minitest/test' require 'minitest/autorun' require 'request_store' class MiddlewareTest < Minitest::Test def setup @app = RackApp.new @middleware = RequestStore::Middleware.new(@app) end def call_middleware(opts = {}) _, _, proxy = @middleware.call(opts) proxy.close proxy end def test_middleware_resets_store 2.times do call_middleware end assert_equal 1, @app.last_value assert_equal({}, RequestStore.store) end def test_middleware_does_not_mutate_response_and_does_not_overflow_stack 10000.times do call_middleware end resp = call_middleware assert resp.is_a?(::Rack::BodyProxy) assert_equal ["response"], resp.to_a assert_equal ["response"], resp.instance_variable_get(:@body) end def test_middleware_resets_store_on_error e = assert_raises RuntimeError do call_middleware({:error => true}) end assert_equal 'FAIL', e.message assert_equal({}, RequestStore.store) end def test_middleware_begins_store call_middleware assert_equal true, @app.store_active end def test_middleware_ends_store call_middleware assert_equal false, RequestStore.active? end def test_middleware_ends_store_on_error assert_raises RuntimeError do call_middleware({:error => true}) end assert_equal false, RequestStore.active? end def test_middleware_stores_until_proxy_closes _, _, proxy = @middleware.call({}) assert_equal 1, @app.last_value assert RequestStore.active? proxy.close refute RequestStore.active? refute RequestStore.store[:foo] end end class MiddlewareWithConstResponseTest < Minitest::Test def setup @app = RackAppWithConstResponse.new @middleware = RequestStore::Middleware.new(@app) end def call_middleware(opts = {}) _, _, proxy = @middleware.call(opts) proxy.close proxy end def test_middleware_does_not_mutate_response_and_does_not_overflow_stack 10000.times do call_middleware end resp = call_middleware assert resp.is_a?(::Rack::BodyProxy) assert_equal ["response"], resp.to_a assert_equal ["response"], resp.instance_variable_get(:@body) end end request_store-1.5.1/test/request_store_test.rb0000644000004100000410000000362214173305310021721 0ustar www-datawww-datarequire 'minitest/autorun' require 'request_store' class RequestStoreTest < Minitest::Test def setup RequestStore.clear! end def teardown RequestStore.clear! end def test_initial_state Thread.current[:request_store] = nil assert_equal RequestStore.store, Hash.new end def test_init_with_hash assert_equal Hash.new, RequestStore.store end def test_assign_store store_obj = { test_key: 'test' } RequestStore.store = store_obj assert_equal 'test', RequestStore.store[:test_key] assert_equal store_obj, RequestStore.store end def test_clear RequestStore.store[:foo] = 1 RequestStore.clear! assert_equal Hash.new, RequestStore.store end def test_quacks_like_hash RequestStore.store[:foo] = 1 assert_equal 1, RequestStore.store[:foo] assert_equal 1, RequestStore.store.fetch(:foo) end def test_read RequestStore.store[:foo] = 1 assert_equal 1, RequestStore.read(:foo) assert_equal 1, RequestStore[:foo] end def test_write RequestStore.write(:foo, 1) assert_equal 1, RequestStore.store[:foo] RequestStore[:foo] = 2 assert_equal 2, RequestStore.store[:foo] end def test_fetch assert_equal 2, RequestStore.fetch(:foo) { 1 + 1 } assert_equal 2, RequestStore.fetch(:foo) { 2 + 2 } end def test_delete assert_equal 2, RequestStore.fetch(:foo) { 1 + 1 } assert_equal 2, RequestStore.delete(:foo) { 2 + 2 } assert_equal 4, RequestStore.delete(:foo) { 2 + 2 } end def test_delegates_to_thread RequestStore.store[:foo] = 1 assert_equal 1, Thread.current[:request_store][:foo] end def test_active_state assert_equal false, RequestStore.active? RequestStore.begin! assert_equal true, RequestStore.active? RequestStore.end! assert_equal false, RequestStore.active? end end request_store-1.5.1/test/test_helper.rb0000644000004100000410000000122614173305310020272 0ustar www-datawww-dataclass RackApp attr_reader :last_value, :store_active def call(env) RequestStore.store[:foo] ||= 0 RequestStore.store[:foo] += 1 @last_value = RequestStore.store[:foo] @store_active = RequestStore.active? raise 'FAIL' if env[:error] [200, {}, ["response"]] end end class RackAppWithConstResponse RESPONSE = [200, {}, ["response"]] attr_reader :last_value, :store_active def call(env) RequestStore.store[:foo] ||= 0 RequestStore.store[:foo] += 1 @last_value = RequestStore.store[:foo] @store_active = RequestStore.active? raise 'FAIL' if env[:error] RESPONSE end end request_store-1.5.1/README.md0000644000004100000410000001016714173305310015733 0ustar www-datawww-data# RequestStore [![build status](https://travis-ci.org/steveklabnik/request_store.svg?branch=master)](https://travis-ci.org/steveklabnik/request_store) [![Code Climate](https://codeclimate.com/github/steveklabnik/request_store.svg)](https://codeclimate.com/github/steveklabnik/request_store) Ever needed to use a global variable in Rails? Ugh, that's the worst. If you need global state, you've probably reached for `Thread.current`. Like this: ```ruby def self.foo Thread.current[:foo] ||= 0 end def self.foo=(value) Thread.current[:foo] = value end ``` Ugh! I hate it. But you gotta do what you gotta do... ### The problem Everyone's worrying about concurrency these days. So people are using those fancy threaded web servers, like Thin or Puma. But if you use `Thread.current`, and you use one of those servers, watch out! Values can stick around longer than you'd expect, and this can cause bugs. For example, if we had this in our controller: ```ruby def index Thread.current[:counter] ||= 0 Thread.current[:counter] += 1 render :text => Thread.current[:counter] end ``` If we ran this on MRI with Webrick, you'd get `1` as output, every time. But if you run it with Thin, you get `1`, then `2`, then `3`... ### The solution Add this line to your application's Gemfile: ```ruby gem 'request_store' ``` And change the code to this: ```ruby def index RequestStore.store[:foo] ||= 0 RequestStore.store[:foo] += 1 render :text => RequestStore.store[:foo] end ``` Yep, everywhere you used `Thread.current` just change it to `RequestStore.store`. Now no matter what server you use, you'll get `1` every time: the storage is local to that request. ### API The `fetch` method returns the stored value if it already exists. If no stored value exists, it uses the provided block to add a new stored value. ```ruby top_posts = RequestStore.fetch(:top_posts) do # code to obtain the top posts end ``` ### Rails 2 compatibility The gem includes a Railtie that will configure everything properly for Rails 3+ apps, but if your app is tied to an older (2.x) version, you will have to manually add the middleware yourself. Typically this should just be a matter of adding: ```ruby config.middleware.use RequestStore::Middleware ``` into your config/environment.rb. ### No Rails? No Problem! A Railtie is added that configures the Middleware for you, but if you're not using Rails, no biggie! Just use the Middleware yourself, however you need. You'll probably have to shove this somewhere: ```ruby use RequestStore::Middleware ``` #### No Rails + Rack::Test In order to have `RequestStore` storage cleared between requests, add it to the `app`: ```ruby # spec_helper.rb def app Rack::Builder.new do use RequestStore::Middleware run MyApp end end ``` ## Using with Sidekiq This gem uses a Rack middleware to clear the store object after every request, but that doesn't translate well to background processing with [Sidekiq](https://github.com/mperham/sidekiq). A companion library, [request_store-sidekiq](https://rubygems.org/gems/request_store-sidekiq) creates a Sidekiq middleware that will ensure the store is cleared after each job is processed, for security and consistency with how this is done in Rack. ## Semantic Versioning This project conforms to [semver](http://semver.org/). As a result of this policy, you can (and should) specify a dependency on this gem using the [Pessimistic Version Constraint](http://guides.rubygems.org/patterns/) with two digits of precision. For example: ```ruby spec.add_dependency 'request_store', '~> 1.0' ``` This means your project is compatible with request_store 1.0 up until 2.0. You can also set a higher minimum version: ```ruby spec.add_dependency 'request_store', '~> 1.1' ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request Don't forget to run the tests with `rake`. request_store-1.5.1/.gitignore0000644000004100000410000000025314173305310016437 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp request_store-1.5.1/Rakefile0000644000004100000410000000036514173305310016120 0ustar www-datawww-datarequire "bundler/gem_tasks" require 'rake/testtask' Rake::TestTask.new do |t| t.libs << "lib" t.test_files = FileList['test/*_test.rb'] t.ruby_opts = ['-r./test/test_helper.rb'] t.verbose = true end task :default => :test request_store-1.5.1/lib/0000755000004100000410000000000014173305310015215 5ustar www-datawww-datarequest_store-1.5.1/lib/request_store.rb0000644000004100000410000000202314173305310020443 0ustar www-datawww-datarequire "request_store/version" require "request_store/middleware" require "request_store/railtie" if defined?(Rails::Railtie) module RequestStore def self.store Thread.current[:request_store] ||= {} end def self.store=(store) Thread.current[:request_store] = store end def self.clear! Thread.current[:request_store] = {} end def self.begin! Thread.current[:request_store_active] = true end def self.end! Thread.current[:request_store_active] = false end def self.active? Thread.current[:request_store_active] || false end def self.read(key) store[key] end def self.[](key) store[key] end def self.write(key, value) store[key] = value end def self.[]=(key, value) store[key] = value end def self.exist?(key) store.key?(key) end def self.fetch(key) store[key] = yield unless exist?(key) store[key] end def self.delete(key, &block) store.delete(key, &block) end end request_store-1.5.1/lib/request_store/0000755000004100000410000000000014173305310020121 5ustar www-datawww-datarequest_store-1.5.1/lib/request_store/version.rb0000644000004100000410000000005714173305310022135 0ustar www-datawww-datamodule RequestStore VERSION = "1.5.1" end request_store-1.5.1/lib/request_store/railtie.rb0000644000004100000410000000144614173305310022104 0ustar www-datawww-datamodule RequestStore class Railtie < ::Rails::Railtie initializer "request_store.insert_middleware" do |app| if ActionDispatch.const_defined? :RequestId app.config.middleware.insert_after ActionDispatch::RequestId, RequestStore::Middleware else app.config.middleware.insert_after Rack::MethodOverride, RequestStore::Middleware end if ActiveSupport.const_defined?(:Reloader) && ActiveSupport::Reloader.respond_to?(:to_complete) ActiveSupport::Reloader.to_complete do RequestStore.clear! end elsif ActionDispatch.const_defined?(:Reloader) && ActionDispatch::Reloader.respond_to?(:to_cleanup) ActionDispatch::Reloader.to_cleanup do RequestStore.clear! end end end end end request_store-1.5.1/lib/request_store/middleware.rb0000644000004100000410000000135614173305310022570 0ustar www-datawww-datarequire 'rack/body_proxy' # A middleware that ensures the RequestStore stays around until # the last part of the body is rendered. This is useful when # using streaming. # # Uses Rack::BodyProxy, adapted from Rack::Lock's usage of the # same pattern. module RequestStore class Middleware def initialize(app) @app = app end def call(env) RequestStore.begin! status, headers, body = @app.call(env) body = Rack::BodyProxy.new(body) do RequestStore.end! RequestStore.clear! end returned = true [status, headers, body] ensure unless returned RequestStore.end! RequestStore.clear! end end end end request_store-1.5.1/Gemfile0000644000004100000410000000014614173305310015743 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in request_store.gemspec gemspec request_store-1.5.1/LICENSE.txt0000644000004100000410000000210214173305310016265 0ustar www-datawww-dataCopyright (c) 2012 Steve Klabnik MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.request_store-1.5.1/request_store.gemspec0000644000004100000410000000176014173305310020724 0ustar www-datawww-data# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'request_store/version' Gem::Specification.new do |gem| gem.name = "request_store" gem.version = RequestStore::VERSION gem.authors = ["Steve Klabnik"] gem.email = ["steve@steveklabnik.com"] gem.description = %q{RequestStore gives you per-request global storage.} gem.summary = %q{RequestStore gives you per-request global storage.} gem.homepage = "https://github.com/steveklabnik/request_store" gem.licenses = ["MIT"] gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] gem.add_dependency "rack", ">= 1.4" gem.add_development_dependency "rake", "~> 10.5" gem.add_development_dependency "minitest", "~> 5.0" end