request_store-1.5.0/0000755000004100000410000000000013617311101014443 5ustar www-datawww-datarequest_store-1.5.0/.travis.yml0000644000004100000410000000037413617311101016560 0ustar www-datawww-datalanguage: ruby sudo: false 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 matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head request_store-1.5.0/test/0000755000004100000410000000000013617311101015422 5ustar www-datawww-datarequest_store-1.5.0/test/middleware_test.rb0000644000004100000410000000255513617311101021132 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 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_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 request_store-1.5.0/test/request_store_test.rb0000644000004100000410000000362213617311101021715 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.0/test/test_helper.rb0000644000004100000410000000046513617311101020272 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 request_store-1.5.0/README.md0000644000004100000410000000756013617311101015732 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. ### 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.0/.gitignore0000644000004100000410000000025313617311101016433 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.0/Rakefile0000644000004100000410000000036513617311101016114 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.0/lib/0000755000004100000410000000000013617311101015211 5ustar www-datawww-datarequest_store-1.5.0/lib/request_store.rb0000644000004100000410000000202313617311101020437 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.0/lib/request_store/0000755000004100000410000000000013617311101020115 5ustar www-datawww-datarequest_store-1.5.0/lib/request_store/version.rb0000644000004100000410000000005713617311101022131 0ustar www-datawww-datamodule RequestStore VERSION = "1.5.0" end request_store-1.5.0/lib/request_store/railtie.rb0000644000004100000410000000144613617311101022100 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.0/lib/request_store/middleware.rb0000644000004100000410000000127113617311101022560 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! response = @app.call(env) returned = response << Rack::BodyProxy.new(response.pop) do RequestStore.end! RequestStore.clear! end ensure unless returned RequestStore.end! RequestStore.clear! end end end end request_store-1.5.0/Gemfile0000644000004100000410000000014613617311101015737 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in request_store.gemspec gemspec request_store-1.5.0/LICENSE.txt0000644000004100000410000000210213617311101016261 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.0/request_store.gemspec0000644000004100000410000000175713617311101020726 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 = "http://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