rails_4_session_flash_backport 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rails_4_session_flash_backport.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "rspec"
8
+ gem "actionpack", ENV["ACTIONPACK_VERSION"] || "3.2.9"
9
+ gem "pry"
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Envato, Lucas Parry, Jack Chen (chendo)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Rails4SessionFlashBackport
2
+
3
+ Different versions of Rails have stored flash messages in different objects in
4
+ the session, making it a pain to upgrade without nuking everyones session. The
5
+ good ol' `ActionDispatch::Session::SessionRestoreError` making life difficult.
6
+
7
+ This gem was created because we wanted to be able to keep our users Rails 2
8
+ sessions working on Rails 3, and we figured as long as we're going to be doing
9
+ crazy stuff we might as well go and use the far more sensible practice from
10
+ Rails 4 of storing the flash as basic ruby types.
11
+
12
+ When using this gem on a Rails 2 or 3 app:
13
+
14
+ - Flash messages are stored as basic objects in the Rails 4 style.
15
+ - Flash messages in the Rails 2 format can be successfully decoded.
16
+ - Flash messages in the Rails 3 format can be successfully decoded.
17
+ - Flash messages in the Rails 4 format can be successfully decoded.
18
+
19
+ Additionally, on Rails 2 we include some patches for the SessionHash and
20
+ CookieStore, in order to make them act more like their
21
+ HashWithIndifferentAccess versions on Rails 3, so that your session_id can
22
+ survive a trip to Rails 3 and back.
23
+
24
+ This actually makes it possible to bounce requests from a Rails 2 server, to a
25
+ Rails 3 server and back again so long as both servers are using this gem. Very
26
+ helpful when you're doing a big Rails 2 => 3 upgrade and want to run a few
27
+ Rails 3 servers concurrently with your Rails 2 cluster to verify everything is
28
+ fine and performance is acceptable without
29
+ having to do the all-in switch.
30
+
31
+ ## Installation
32
+
33
+ Add this line to your application's Gemfile:
34
+
35
+ gem 'rails_4_session_flash_backport'
36
+
37
+ And then execute:
38
+
39
+ $ bundle
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install rails_4_session_flash_backport
44
+
45
+ Copyright
46
+ ---------
47
+
48
+ Copyright (c) 2012 [Envato](http://envato.com), [Lucas Parry](http://github.com/lparry), [chendo](http://github.com/chendo). See LICENSE.txt for further details.
49
+
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ namespace :spec do
4
+ task :rails3 do
5
+ system "export ACTIONPACK_VERSION=3.2.9 && bundle check > /dev/null || bundle update"
6
+ puts "Running Rails 3 specs"
7
+ system "export ACTIONPACK_VERSION=3.2.9 && bundle exec rspec --color spec/rails3"
8
+ puts ""
9
+ end
10
+
11
+ task :rails2 do
12
+ system "export ACTIONPACK_VERSION=2.3.14 && bundle check > /dev/null || bundle update"
13
+ puts "Running Rails 2 specs"
14
+ system "export ACTIONPACK_VERSION=2.3.14 && bundle exec rspec --color spec/rails2"
15
+ puts ""
16
+ end
17
+ end
18
+
19
+ task :spec => ["spec:rails2", "spec:rails3"]
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+ require 'action_controller/flash'
3
+ require 'action_controller/test_process'
4
+ # Backport Rails 4 style storing the flash as basic ruby types to Rails 2
5
+ module ActionController #:nodoc:
6
+ module Flash
7
+ class FlashHash
8
+ def self.from_session_value(value)
9
+ flash = case value
10
+ when FlashHash # Rails 2.3
11
+ value
12
+ when ::ActionDispatch::Flash::FlashHash # Rails 3.2
13
+ flashes = value.instance_variable_get(:@flashes) || {}
14
+ used_set = value.instance_variable_get(:@used) || []
15
+ used = Hash[flashes.keys.map{|k| [k, used_set.include?(k)] }]
16
+ new_from_values(flashes, used)
17
+ when Hash # Rails 4.0
18
+ flashes = value['flashes'] || {}
19
+ discard = value['discard']
20
+ used = Hash[flashes.keys.map{|k| [k, discard.include?(k)] }]
21
+
22
+ new_from_values(flashes, used)
23
+ else
24
+ new
25
+ end
26
+ flash
27
+ end
28
+
29
+ def to_session_value
30
+ return nil if empty?
31
+ rails_3_discard_list = @used.map{|k,v| k if v}.compact
32
+ {'discard' => rails_3_discard_list, 'flashes' => Hash[to_a]}
33
+ end
34
+
35
+ def store(session, key = "flash")
36
+ session[key] = to_session_value
37
+ end
38
+
39
+ private
40
+
41
+ def self.new_from_values(flashes, used)
42
+ new.tap do |flash_hash|
43
+ flashes.each do |k, v|
44
+ flash_hash[k] = v
45
+ end
46
+ flash_hash.instance_variable_set("@used", used)
47
+ end
48
+ end
49
+ end
50
+
51
+ module InstanceMethods #:nodoc:
52
+ protected
53
+ def flash #:doc:
54
+ if !defined?(@_flash)
55
+ @_flash = Flash::FlashHash.from_session_value(session["flash"])
56
+ @_flash.sweep
57
+ end
58
+
59
+ @_flash
60
+ end
61
+ end
62
+ end
63
+ module TestResponseBehavior #:nodoc:
64
+ # A shortcut to the flash. Returns an empty hash if no session flash exists.
65
+ def flash
66
+ ActionController::Flash::FlashHash.from_session_value(session["flash"]) || {}
67
+ end
68
+ end
69
+ end
70
+
71
+ # This magic here allows us to unmarshal a Rails 3.2 ActionDispatch::Flash::FlashHash
72
+ module ActionDispatch
73
+ class Flash
74
+ class FlashHash
75
+ def self._load(args)
76
+ {}
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,65 @@
1
+ # The SessionHash is a HashWithIndifferentAccess on Rails 3, which causes
2
+ # problems with the session_id among other things. On Rails 2 the session_id is
3
+ # stored as session[:session_id], if we take that session to Rails 3 it becomes
4
+ # session["session_id"], and if we bring it back to Rails 2 it's still
5
+ # session["session_id"], but Rails checks session[:session_id], finds nothing
6
+ # and generates a new one. This is unacceptable.
7
+ #
8
+ # We've patched the SessionHash to #to_s keys before it stores them, and to
9
+ # fall back from the string key to the symbol if needed.
10
+ module ActionController::Session
11
+ class AbstractStore
12
+ class SessionHash < Hash
13
+ def [](key)
14
+ load! unless @loaded
15
+ super(key.to_s) || super(key)
16
+ end
17
+
18
+ def has_key?(key)
19
+ load! unless @loaded
20
+ super(key.to_s) || super(key)
21
+ end
22
+
23
+ def []=(key, value)
24
+ load! unless @loaded
25
+ super(key.to_s, value)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ # We've also had to patch the cookie store, to make it use for the string
32
+ # version of "session_id", again falling back to the symbol if needed.
33
+ module ActionController
34
+ module Session
35
+ class CookieStore
36
+ def load_session(env)
37
+ data = unpacked_cookie_data(env)
38
+ data = persistent_session_id!(data)
39
+ [data["session_id"] || data[:session_id], data]
40
+ end
41
+
42
+ def extract_session_id(env)
43
+ if data = unpacked_cookie_data(env)
44
+ persistent_session_id!(data) unless data.empty?
45
+ data["session_id"] || data[:session_id]
46
+ else
47
+ nil
48
+ end
49
+ end
50
+
51
+ def inject_persistent_session_id(data)
52
+ requires_session_id?(data) ? { "session_id" => generate_sid } : {}
53
+ end
54
+
55
+ def requires_session_id?(data)
56
+ if data
57
+ data.respond_to?(:key?) && !(data.key?("session_id") || data.key?(:session_id))
58
+ else
59
+ true
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+ require 'action_dispatch/middleware/flash'
3
+ # Backport Rails 4 style storing the flash as basic ruby types to Rails 3
4
+ module ActionDispatch
5
+ class Request < Rack::Request
6
+ def flash
7
+ @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"]).tap(&:sweep)
8
+ end
9
+ end
10
+ class Flash
11
+ class FlashHash
12
+
13
+ def self.from_session_value(value)
14
+ case value
15
+ when ::ActionController::Flash::FlashHash # Rails 2.x
16
+ new(value, value.instance_variable_get(:@used).select{|a,b| b}.keys)
17
+ when ::ActionDispatch::Flash::FlashHash # Rails 3.1, 3.2
18
+ new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
19
+ when Hash # Rails 4.0, we backported to 2.3 too
20
+ new(value['flashes'], value['discard'])
21
+ else
22
+ new
23
+ end
24
+ end
25
+
26
+ def to_session_value
27
+ return nil if empty?
28
+ {'discard' => @used.to_a, 'flashes' => @flashes}
29
+ end
30
+
31
+ def initialize(flashes = {}, discard = []) #:nodoc:
32
+ @used = Set.new(discard)
33
+ @closed = false
34
+ @flashes = flashes
35
+ @now = nil
36
+ end
37
+
38
+ end
39
+
40
+ def call(env)
41
+ @app.call(env)
42
+ ensure
43
+ session = env['rack.session'] || {}
44
+ flash_hash = env[KEY]
45
+
46
+ if flash_hash
47
+ if !flash_hash.empty? || session.key?('flash')
48
+ session["flash"] = flash_hash.to_session_value
49
+ new_hash = flash_hash.dup
50
+ else
51
+ new_hash = flash_hash
52
+ end
53
+
54
+ env[KEY] = new_hash
55
+ end
56
+
57
+ if session.key?('flash') && session['flash'].nil?
58
+ session.delete('flash')
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # This magic here allows us to unmarshal the old Rails 2.x ActionController::Flash::FlashHash
65
+ module ActionController
66
+ module Flash
67
+ class FlashHash < Hash
68
+
69
+ def self._load(args)
70
+ {}
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module Rails4SessionFlashBackport
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ require "rails_4_session_flash_backport/version"
3
+
4
+ case Rails.version.to_i
5
+ when 2
6
+ require 'rails_4_session_flash_backport/rails2/flash_hash'
7
+ require 'rails_4_session_flash_backport/rails2/session_with_indifferent_access'
8
+ when 3
9
+ require 'rails_4_session_flash_backport/rails3/flash_hash'
10
+ when 4
11
+ Rails.logger.warn "You're on Rails 4 so it's probably safe to remove the rails_4_session_flash_backport gem!"
12
+ else
13
+ Rails.logger.warn "rails_4_session_flash_backport doesnt yet do anything on Rails #{Rails.version}"
14
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rails_4_session_flash_backport/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rails_4_session_flash_backport"
8
+ gem.version = Rails4SessionFlashBackport::VERSION
9
+ gem.authors = ["Lucas Parry"]
10
+ gem.email = ["lparry@gmail.com"]
11
+ gem.description = %q{Store flash in the session in Rails 4 style on Rails 2/3}
12
+ gem.summary = %q{Backport of the way Rails 4 stores flash messages in the session to Rails 2 & 3, so you can safely take a session betweens Rails versions without things exploding.}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,45 @@
1
+ require 'action_controller'
2
+ require 'rails_4_session_flash_backport/rails2/flash_hash'
3
+
4
+ describe ActionController::Flash::FlashHash, "hax" do
5
+
6
+ let(:rails_2_marshaled) { "\x04\bIC:'ActionController::Flash::FlashHash{\a:\vnoticeI\"\x11I'm a notice\x06:\x06ET:\nerrorI\"\x11I'm an error\x06;\aT\x06:\n@used{\a;\x06T;\bF" }
7
+ let(:rails_3_marshaled) { "\x04\bo:%ActionDispatch::Flash::FlashHash\t:\n@usedo:\bSet\x06:\n@hash{\x06:\vnoticeT:\f@closedF:\r@flashes{\a;\tI\"\x11I'm a notice\x06:\x06EF:\nerrorI\"\x11I'm an error\x06;\fF:\t@now0" }
8
+ let(:rails_2_vanilla) { Marshal.load(rails_2_marshaled) }
9
+ let(:rails_3_vanilla) { Marshal.load(rails_3_marshaled) }
10
+ let(:rails_4_style) { {'flashes' => {:notice => "I'm a notice", :error => "I'm an error"}, 'discard' => [:notice]} }
11
+
12
+ it "happily unmarshals a Rails 3 session without exploding" do
13
+ Marshal.load(rails_3_marshaled).should be_a(ActionDispatch::Flash::FlashHash)
14
+ end
15
+
16
+ context "#to_session_value" do
17
+ it "dumps to basic objects like rails 4" do
18
+ rails_2_vanilla.to_session_value.should be_a(Hash)
19
+ rails_2_vanilla.to_session_value.should == rails_4_style
20
+ end
21
+ end
22
+
23
+ context "#from_session_value" do
24
+ def this_is_the_flash_hash_were_looking_for(flash_hash)
25
+ flash_hash.should be_a(described_class)
26
+ flash_hash[:notice].should == "I'm a notice"
27
+ flash_hash[:error].should == "I'm an error"
28
+ flash_hash.sweep
29
+ flash_hash[:notice].should be_nil
30
+ flash_hash[:error].should == "I'm an error"
31
+ end
32
+
33
+ it "decodes rails 2 style to an empty FlashHash" do
34
+ this_is_the_flash_hash_were_looking_for(ActionController::Flash::FlashHash.from_session_value(rails_2_vanilla))
35
+ end
36
+
37
+ it "decodes rails 3 style to a FlashHash" do
38
+ this_is_the_flash_hash_were_looking_for(ActionController::Flash::FlashHash.from_session_value(rails_3_vanilla))
39
+ end
40
+
41
+ it "decodes rails 4 style to a FlashHash" do
42
+ this_is_the_flash_hash_were_looking_for(ActionController::Flash::FlashHash.from_session_value(rails_4_style))
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ require 'action_dispatch'
2
+ require 'rails_4_session_flash_backport/rails3/flash_hash'
3
+
4
+ describe ActionDispatch::Flash::FlashHash, "hax" do
5
+
6
+ let(:rails_2_marshaled) { "\x04\bIC:'ActionController::Flash::FlashHash{\a:\vnoticeI\"\x11I'm a notice\x06:\x06ET:\nerrorI\"\x11I'm an error\x06;\aT\x06:\n@used{\a;\x06T;\bF" }
7
+ let(:rails_3_marshaled) { "\x04\bo:%ActionDispatch::Flash::FlashHash\t:\n@usedo:\bSet\x06:\n@hash{\x06:\vnoticeT:\f@closedF:\r@flashes{\a;\tI\"\x11I'm a notice\x06:\x06EF:\nerrorI\"\x11I'm an error\x06;\fF:\t@now0" }
8
+ let(:rails_2_vanilla) { Marshal.load(rails_2_marshaled) }
9
+ let(:rails_3_vanilla) { Marshal.load(rails_3_marshaled) }
10
+ let(:rails_4_style) { {'flashes' => {:notice => "I'm a notice", :error => "I'm an error"}, 'discard' => [:notice]} }
11
+ let(:rails_2_marshaled) { "\x04\bIC:'ActionController::Flash::FlashHash{\a:\vnoticeI\"\x11I'm a notice\x06:\x06ET:\nerrorI\"\x11I'm an error\x06;\aT\x06:\n@used{\a;\x06T;\bF" }
12
+ let(:rails_2_vanilla) { Marshal.load(rails_2_marshaled) }
13
+
14
+ it "happily unmarshals a Rails 2 session without exploding" do
15
+ Marshal.load(rails_2_marshaled).should be_a(ActionController::Flash::FlashHash)
16
+ end
17
+
18
+ context "#from_session_value" do
19
+ def this_is_the_flash_hash_were_looking_for(flash_hash)
20
+ flash_hash.should be_a(described_class)
21
+ flash_hash[:notice].should == "I'm a notice"
22
+ flash_hash[:error].should == "I'm an error"
23
+ flash_hash.sweep
24
+ flash_hash[:notice].should be_nil
25
+ flash_hash[:error].should == "I'm an error"
26
+ end
27
+
28
+ it "decodes rails 2 style to an empty FlashHash" do
29
+ this_is_the_flash_hash_were_looking_for(ActionDispatch::Flash::FlashHash.from_session_value(rails_2_vanilla))
30
+ end
31
+
32
+ it "decodes rails 3 style to a FlashHash" do
33
+ this_is_the_flash_hash_were_looking_for(ActionDispatch::Flash::FlashHash.from_session_value(rails_3_vanilla))
34
+ end
35
+
36
+ it "decodes rails 4 style to a FlashHash" do
37
+ this_is_the_flash_hash_were_looking_for(ActionDispatch::Flash::FlashHash.from_session_value(rails_4_style))
38
+ end
39
+ end
40
+
41
+ context "#to_session_value" do
42
+ it "dumps to basic objects like rails 4" do
43
+ rails_3_vanilla.to_session_value.should be_a(Hash)
44
+ rails_3_vanilla.to_session_value.should == rails_4_style
45
+ end
46
+ end
47
+
48
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_4_session_flash_backport
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Lucas Parry
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-16 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Store flash in the session in Rails 4 style on Rails 2/3
15
+ email:
16
+ - lparry@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - lib/rails_4_session_flash_backport.rb
27
+ - lib/rails_4_session_flash_backport/rails2/flash_hash.rb
28
+ - lib/rails_4_session_flash_backport/rails2/session_with_indifferent_access.rb
29
+ - lib/rails_4_session_flash_backport/rails3/flash_hash.rb
30
+ - lib/rails_4_session_flash_backport/version.rb
31
+ - rails_4_session_flash_backport.gemspec
32
+ - spec/rails2/flash_hash_spec.rb
33
+ - spec/rails3/flash_hash_spec.rb
34
+ homepage: ''
35
+ licenses: []
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 1.8.23
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: Backport of the way Rails 4 stores flash messages in the session to Rails
58
+ 2 & 3, so you can safely take a session betweens Rails versions without things exploding.
59
+ test_files:
60
+ - spec/rails2/flash_hash_spec.rb
61
+ - spec/rails3/flash_hash_spec.rb