Skip to content
This repository
Browse code

Merge pull request #7436 from sikachu/master-remove-active_record-ses…

…sion_store

Extract ActiveRecord::SessionStore from Rails
  • Loading branch information...
commit 73c02220f83cfb181ccf99db0451983324e28804 2 parents 2c571b3 + 1807384
José Valim authored August 24, 2012

Showing 25 changed files with 41 additions and 976 deletions. Show diff stats Hide diff stats

  1. 4  actionpack/CHANGELOG.md
  2. 9  actionpack/lib/action_controller/base.rb
  3. 288  actionpack/test/activerecord/active_record_store_test.rb
  4. 3  activerecord/CHANGELOG.md
  5. 11  activerecord/lib/active_record.rb
  6. 15  activerecord/lib/active_record/railties/databases.rake
  7. 365  activerecord/lib/active_record/session_store.rb
  8. 24  activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
  9. 12  activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb
  10. 81  activerecord/test/cases/session_store/session_test.rb
  11. 75  activerecord/test/cases/session_store/sql_bypass_test.rb
  12. 5  guides/code/getting_started/config/initializers/session_store.rb
  13. 2  guides/source/4_0_release_notes.textile
  14. 4  guides/source/action_controller_overview.textile
  15. 10  guides/source/active_support_core_extensions.textile
  16. 10  guides/source/configuring.textile
  17. 4  guides/source/security.textile
  18. 7  railties/lib/rails/application/configuration.rb
  19. 1  railties/lib/rails/generators.rb
  20. 5  railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
  21. 8  railties/lib/rails/generators/rails/session_migration/USAGE
  22. 8  railties/lib/rails/generators/rails/session_migration/session_migration_generator.rb
  23. 19  railties/test/application/configuration_test.rb
  24. 20  railties/test/application/initializers/frameworks_test.rb
  25. 27  railties/test/generators/session_migration_generator_test.rb
4  actionpack/CHANGELOG.md
Source Rendered
... ...
@@ -1,5 +1,9 @@
1 1
 ## Rails 4.0.0 (unreleased) ##
2 2
 
  3
+*   `ActiveRecord::SessionStore` is extracted out of Rails into a gem `activerecord-session_store`.
  4
+    Setting `config.session_store` to `:active_record_store` will no longer work and will break
  5
+    if the `activerecord-session_store` gem isn't available. *Prem Sichanugrist*
  6
+
3 7
 *   Fix select_tag when option_tags is nil.
4 8
     Fixes #7404.
5 9
 
9  actionpack/lib/action_controller/base.rb
@@ -88,15 +88,6 @@ module ActionController
88 88
   #
89 89
   # Do not put secret information in cookie-based sessions!
90 90
   #
91  
-  # Other options for session storage:
92  
-  #
93  
-  # * ActiveRecord::SessionStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
94  
-  #   unlike CookieStore, hides your session contents from the user. To use ActiveRecord::SessionStore, set
95  
-  #
96  
-  #     MyApplication::Application.config.session_store :active_record_store
97  
-  #
98  
-  #   in your <tt>config/initializers/session_store.rb</tt> and run <tt>script/rails g session_migration</tt>.
99  
-  #
100 91
   # == Responses
101 92
   #
102 93
   # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
288  actionpack/test/activerecord/active_record_store_test.rb
... ...
@@ -1,288 +0,0 @@
1  
-require 'active_record_unit'
2  
-
3  
-class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
4  
-  class TestController < ActionController::Base
5  
-    def no_session_access
6  
-      head :ok
7  
-    end
8  
-
9  
-    def set_session_value
10  
-      raise "missing session!" unless session
11  
-      session[:foo] = params[:foo] || "bar"
12  
-      head :ok
13  
-    end
14  
-
15  
-    def get_session_value
16  
-      render :text => "foo: #{session[:foo].inspect}"
17  
-    end
18  
-
19  
-    def get_session_id
20  
-      render :text => "#{request.session_options[:id]}"
21  
-    end
22  
-
23  
-    def call_reset_session
24  
-      session[:foo]
25  
-      reset_session
26  
-      reset_session if params[:twice]
27  
-      session[:foo] = "baz"
28  
-      head :ok
29  
-    end
30  
-
31  
-    def renew
32  
-      env["rack.session.options"][:renew] = true
33  
-      session[:foo] = "baz"
34  
-      head :ok
35  
-    end
36  
-  end
37  
-
38  
-  def setup
39  
-    ActiveRecord::SessionStore.session_class.create_table!
40  
-  end
41  
-
42  
-  def teardown
43  
-    ActiveRecord::SessionStore.session_class.drop_table!
44  
-  end
45  
-
46  
-  %w{ session sql_bypass }.each do |class_name|
47  
-    define_method("test_setting_and_getting_session_value_with_#{class_name}_store") do
48  
-      with_store class_name do
49  
-        with_test_route_set do
50  
-          get '/set_session_value'
51  
-          assert_response :success
52  
-          assert cookies['_session_id']
53  
-
54  
-          get '/get_session_value'
55  
-          assert_response :success
56  
-          assert_equal 'foo: "bar"', response.body
57  
-
58  
-          get '/set_session_value', :foo => "baz"
59  
-          assert_response :success
60  
-          assert cookies['_session_id']
61  
-
62  
-          get '/get_session_value'
63  
-          assert_response :success
64  
-          assert_equal 'foo: "baz"', response.body
65  
-
66  
-          get '/call_reset_session'
67  
-          assert_response :success
68  
-          assert_not_equal [], headers['Set-Cookie']
69  
-        end
70  
-      end
71  
-    end
72  
-
73  
-    define_method("test_renewing_with_#{class_name}_store") do
74  
-      with_store class_name do
75  
-        with_test_route_set do
76  
-          get '/set_session_value'
77  
-          assert_response :success
78  
-          assert cookies['_session_id']
79  
-
80  
-          get '/renew'
81  
-          assert_response :success
82  
-          assert_not_equal [], headers['Set-Cookie']
83  
-        end
84  
-      end
85  
-    end
86  
-  end
87  
-
88  
-  def test_getting_nil_session_value
89  
-    with_test_route_set do
90  
-      get '/get_session_value'
91  
-      assert_response :success
92  
-      assert_equal 'foo: nil', response.body
93  
-    end
94  
-  end
95  
-
96  
-  def test_calling_reset_session_twice_does_not_raise_errors
97  
-    with_test_route_set do
98  
-      get '/call_reset_session', :twice => "true"
99  
-      assert_response :success
100  
-
101  
-      get '/get_session_value'
102  
-      assert_response :success
103  
-      assert_equal 'foo: "baz"', response.body
104  
-    end
105  
-  end
106  
-
107  
-  def test_setting_session_value_after_session_reset
108  
-    with_test_route_set do
109  
-      get '/set_session_value'
110  
-      assert_response :success
111  
-      assert cookies['_session_id']
112  
-      session_id = cookies['_session_id']
113  
-
114  
-      get '/call_reset_session'
115  
-      assert_response :success
116  
-      assert_not_equal [], headers['Set-Cookie']
117  
-
118  
-      get '/get_session_value'
119  
-      assert_response :success
120  
-      assert_equal 'foo: "baz"', response.body
121  
-
122  
-      get '/get_session_id'
123  
-      assert_response :success
124  
-      assert_not_equal session_id, response.body
125  
-    end
126  
-  end
127  
-
128  
-  def test_getting_session_value_after_session_reset
129  
-    with_test_route_set do
130  
-      get '/set_session_value'
131  
-      assert_response :success
132  
-      assert cookies['_session_id']
133  
-      session_cookie = cookies.send(:hash_for)['_session_id']
134  
-
135  
-      get '/call_reset_session'
136  
-      assert_response :success
137  
-      assert_not_equal [], headers['Set-Cookie']
138  
-
139  
-      cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
140  
-
141  
-      get '/get_session_value'
142  
-      assert_response :success
143  
-      assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from the database"
144  
-    end
145  
-  end
146  
-
147  
-  def test_getting_from_nonexistent_session
148  
-    with_test_route_set do
149  
-      get '/get_session_value'
150  
-      assert_response :success
151  
-      assert_equal 'foo: nil', response.body
152  
-      assert_nil cookies['_session_id'], "should only create session on write, not read"
153  
-    end
154  
-  end
155  
-
156  
-  def test_getting_session_id
157  
-    with_test_route_set do
158  
-      get '/set_session_value'
159  
-      assert_response :success
160  
-      assert cookies['_session_id']
161  
-      session_id = cookies['_session_id']
162  
-
163  
-      get '/get_session_id'
164  
-      assert_response :success
165  
-      assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
166  
-    end
167  
-  end
168  
-
169  
-  def test_doesnt_write_session_cookie_if_session_id_is_already_exists
170  
-    with_test_route_set do
171  
-      get '/set_session_value'
172  
-      assert_response :success
173  
-      assert cookies['_session_id']
174  
-
175  
-      get '/get_session_value'
176  
-      assert_response :success
177  
-      assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
178  
-    end
179  
-  end
180  
-
181  
-  def test_prevents_session_fixation
182  
-    with_test_route_set do
183  
-      get '/set_session_value'
184  
-      assert_response :success
185  
-      assert cookies['_session_id']
186  
-
187  
-      get '/get_session_value'
188  
-      assert_response :success
189  
-      assert_equal 'foo: "bar"', response.body
190  
-      session_id = cookies['_session_id']
191  
-      assert session_id
192  
-
193  
-      reset!
194  
-
195  
-      get '/get_session_value', :_session_id => session_id
196  
-      assert_response :success
197  
-      assert_equal 'foo: nil', response.body
198  
-      assert_not_equal session_id, cookies['_session_id']
199  
-    end
200  
-  end
201  
-
202  
-  def test_allows_session_fixation
203  
-    with_test_route_set(:cookie_only => false) do
204  
-      get '/set_session_value'
205  
-      assert_response :success
206  
-      assert cookies['_session_id']
207  
-
208  
-      get '/get_session_value'
209  
-      assert_response :success
210  
-      assert_equal 'foo: "bar"', response.body
211  
-      session_id = cookies['_session_id']
212  
-      assert session_id
213  
-
214  
-      reset!
215  
-
216  
-      get '/set_session_value', :_session_id => session_id, :foo => "baz"
217  
-      assert_response :success
218  
-      assert_equal session_id, cookies['_session_id']
219  
-
220  
-      get '/get_session_value', :_session_id => session_id
221  
-      assert_response :success
222  
-      assert_equal 'foo: "baz"', response.body
223  
-      assert_equal session_id, cookies['_session_id']
224  
-    end
225  
-  end
226  
-
227  
-  def test_incoming_invalid_session_id_via_cookie_should_be_ignored
228  
-    with_test_route_set do
229  
-      open_session do |sess|
230  
-        sess.cookies['_session_id'] = 'INVALID'
231  
-
232  
-        sess.get '/set_session_value'
233  
-        new_session_id = sess.cookies['_session_id']
234  
-        assert_not_equal 'INVALID', new_session_id
235  
-
236  
-        sess.get '/get_session_value'
237  
-        new_session_id_2 = sess.cookies['_session_id']
238  
-        assert_equal new_session_id, new_session_id_2
239  
-      end
240  
-    end
241  
-  end
242  
-
243  
-  def test_incoming_invalid_session_id_via_parameter_should_be_ignored
244  
-    with_test_route_set(:cookie_only => false) do
245  
-      open_session do |sess|
246  
-        sess.get '/set_session_value', :_session_id => 'INVALID'
247  
-        new_session_id = sess.cookies['_session_id']
248  
-        assert_not_equal 'INVALID', new_session_id
249  
-
250  
-        sess.get '/get_session_value'
251  
-        new_session_id_2 = sess.cookies['_session_id']
252  
-        assert_equal new_session_id, new_session_id_2
253  
-      end
254  
-    end
255  
-  end
256  
-
257  
-  def test_session_store_with_all_domains
258  
-    with_test_route_set(:domain => :all) do
259  
-      get '/set_session_value'
260  
-      assert_response :success
261  
-    end
262  
-  end
263  
-
264  
-  private
265  
-
266  
-    def with_test_route_set(options = {})
267  
-      with_routing do |set|
268  
-        set.draw do
269  
-          get ':action', :to => 'active_record_store_test/test'
270  
-        end
271  
-
272  
-        @app = self.class.build_app(set) do |middleware|
273  
-          middleware.use ActiveRecord::SessionStore, options.reverse_merge(:key => '_session_id')
274  
-          middleware.delete "ActionDispatch::ShowExceptions"
275  
-        end
276  
-
277  
-        yield
278  
-      end
279  
-    end
280  
-
281  
-    def with_store(class_name)
282  
-      session_class, ActiveRecord::SessionStore.session_class =
283  
-        ActiveRecord::SessionStore.session_class, "ActiveRecord::SessionStore::#{class_name.camelize}".constantize
284  
-      yield
285  
-    ensure
286  
-      ActiveRecord::SessionStore.session_class = session_class
287  
-    end
288  
-end
3  activerecord/CHANGELOG.md
Source Rendered
... ...
@@ -1,5 +1,8 @@
1 1
 ## Rails 4.0.0 (unreleased) ##
2 2
 
  3
+*   ActiveRecord::SessionStore has been extracted from Active Record as `activerecord-session_store`
  4
+    gem. Please read the `README.md` file on the gem for the usage. *Prem Sichanugrist*
  5
+
3 6
 *   Fix `reset_counters` when there are multiple `belongs_to` association with the
4 7
     same foreign key and one of them have a counter cache.
5 8
     Fixes #5200.
11  activerecord/lib/active_record.rb
@@ -53,17 +53,6 @@ module ActiveRecord
53 53
   autoload :ReadonlyAttributes
54 54
   autoload :Reflection
55 55
   autoload :Sanitization
56  
-
57  
-  # ActiveRecord::SessionStore depends on the abstract store in Action Pack.
58  
-  # Eager loading this class would break client code that eager loads Active
59  
-  # Record standalone.
60  
-  #
61  
-  # Note that the Rails application generator creates an initializer specific
62  
-  # for setting the session store. Thus, albeit in theory this autoload would
63  
-  # not be thread-safe, in practice it is because if the application uses this
64  
-  # session store its autoload happens at boot time.
65  
-  autoload :SessionStore
66  
-
67 56
   autoload :Schema
68 57
   autoload :SchemaDumper
69 58
   autoload :SchemaMigration
15  activerecord/lib/active_record/railties/databases.rake
@@ -408,21 +408,6 @@ db_namespace = namespace :db do
408 408
       end
409 409
     end
410 410
   end
411  
-
412  
-  namespace :sessions do
413  
-    # desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
414  
-    task :create => [:environment, :load_config] do
415  
-      raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations?
416  
-      Rails.application.load_generators
417  
-      require 'rails/generators/rails/session_migration/session_migration_generator'
418  
-      Rails::Generators::SessionMigrationGenerator.start [ ENV['MIGRATION'] || 'add_sessions_table' ]
419  
-    end
420  
-
421  
-    # desc "Clear the sessions table"
422  
-    task :clear => [:environment, :load_config] do
423  
-      ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::SessionStore::Session.table_name}"
424  
-    end
425  
-  end
426 411
 end
427 412
 
428 413
 namespace :railties do
365  activerecord/lib/active_record/session_store.rb
... ...
@@ -1,365 +0,0 @@
1  
-require 'action_dispatch/middleware/session/abstract_store'
2  
-
3  
-module ActiveRecord
4  
-  # = Active Record Session Store
5  
-  #
6  
-  # A session store backed by an Active Record class. A default class is
7  
-  # provided, but any object duck-typing to an Active Record Session class
8  
-  # with text +session_id+ and +data+ attributes is sufficient.
9  
-  #
10  
-  # The default assumes a +sessions+ tables with columns:
11  
-  #   +id+ (numeric primary key),
12  
-  #   +session_id+ (string, usually varchar; maximum length is 255), and
13  
-  #   +data+ (text or longtext; careful if your session data exceeds 65KB).
14  
-  #
15  
-  # The +session_id+ column should always be indexed for speedy lookups.
16  
-  # Session data is marshaled to the +data+ column in Base64 format.
17  
-  # If the data you write is larger than the column's size limit,
18  
-  # ActionController::SessionOverflowError will be raised.
19  
-  #
20  
-  # You may configure the table name, primary key, and data column.
21  
-  # For example, at the end of <tt>config/application.rb</tt>:
22  
-  #
23  
-  #   ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
24  
-  #   ActiveRecord::SessionStore::Session.primary_key = 'session_id'
25  
-  #   ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
26  
-  #
27  
-  # Note that setting the primary key to the +session_id+ frees you from
28  
-  # having a separate +id+ column if you don't want it. However, you must
29  
-  # set <tt>session.model.id = session.session_id</tt> by hand!  A before filter
30  
-  # on ApplicationController is a good place.
31  
-  #
32  
-  # Since the default class is a simple Active Record, you get timestamps
33  
-  # for free if you add +created_at+ and +updated_at+ datetime columns to
34  
-  # the +sessions+ table, making periodic session expiration a snap.
35  
-  #
36  
-  # You may provide your own session class implementation, whether a
37  
-  # feature-packed Active Record or a bare-metal high-performance SQL
38  
-  # store, by setting
39  
-  #
40  
-  #   ActiveRecord::SessionStore.session_class = MySessionClass
41  
-  #
42  
-  # You must implement these methods:
43  
-  #
44  
-  #   self.find_by_session_id(session_id)
45  
-  #   initialize(hash_of_session_id_and_data, options_hash = {})
46  
-  #   attr_reader :session_id
47  
-  #   attr_accessor :data
48  
-  #   save
49  
-  #   destroy
50  
-  #
51  
-  # The example SqlBypass class is a generic SQL session store. You may
52  
-  # use it as a basis for high-performance database-specific stores.
53  
-  class SessionStore < ActionDispatch::Session::AbstractStore
54  
-    module ClassMethods # :nodoc:
55  
-      def marshal(data)
56  
-        ::Base64.encode64(Marshal.dump(data)) if data
57  
-      end
58  
-
59  
-      def unmarshal(data)
60  
-        Marshal.load(::Base64.decode64(data)) if data
61  
-      end
62  
-
63  
-      def drop_table!
64  
-        connection.schema_cache.clear_table_cache!(table_name)
65  
-        connection.drop_table table_name
66  
-      end
67  
-
68  
-      def create_table!
69  
-        connection.schema_cache.clear_table_cache!(table_name)
70  
-        connection.create_table(table_name) do |t|
71  
-          t.string session_id_column, :limit => 255
72  
-          t.text data_column_name
73  
-        end
74  
-        connection.add_index table_name, session_id_column, :unique => true
75  
-      end
76  
-    end
77  
-
78  
-    # The default Active Record class.
79  
-    class Session < ActiveRecord::Base
80  
-      extend ClassMethods
81  
-
82  
-      ##
83  
-      # :singleton-method:
84  
-      # Customizable data column name. Defaults to 'data'.
85  
-      cattr_accessor :data_column_name
86  
-      self.data_column_name = 'data'
87  
-
88  
-      attr_accessible :session_id, :data, :marshaled_data
89  
-
90  
-      before_save :marshal_data!
91  
-      before_save :raise_on_session_data_overflow!
92  
-
93  
-      class << self
94  
-        def data_column_size_limit
95  
-          @data_column_size_limit ||= columns_hash[data_column_name].limit
96  
-        end
97  
-
98  
-        # Hook to set up sessid compatibility.
99  
-        def find_by_session_id(session_id)
100  
-          setup_sessid_compatibility!
101  
-          find_by_session_id(session_id)
102  
-        end
103  
-
104  
-        private
105  
-          def session_id_column
106  
-            'session_id'
107  
-          end
108  
-
109  
-          # Compatibility with tables using sessid instead of session_id.
110  
-          def setup_sessid_compatibility!
111  
-            # Reset column info since it may be stale.
112  
-            reset_column_information
113  
-            if columns_hash['sessid']
114  
-              def self.find_by_session_id(*args)
115  
-                find_by_sessid(*args)
116  
-              end
117  
-
118  
-              define_method(:session_id)  { sessid }
119  
-              define_method(:session_id=) { |session_id| self.sessid = session_id }
120  
-            else
121  
-              class << self; remove_possible_method :find_by_session_id; end
122  
-
123  
-              def self.find_by_session_id(session_id)
124  
-                where(session_id: session_id).first
125  
-              end
126  
-            end
127  
-          end
128  
-      end
129  
-
130  
-      def initialize(attributes = nil, options = {})
131  
-        @data = nil
132  
-        super
133  
-      end
134  
-
135  
-      # Lazy-unmarshal session state.
136  
-      def data
137  
-        @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
138  
-      end
139  
-
140  
-      attr_writer :data
141  
-
142  
-      # Has the session been loaded yet?
143  
-      def loaded?
144  
-        @data
145  
-      end
146  
-
147  
-      private
148  
-        def marshal_data!
149  
-          return false unless loaded?
150  
-          write_attribute(@@data_column_name, self.class.marshal(data))
151  
-        end
152  
-
153  
-        # Ensures that the data about to be stored in the database is not
154  
-        # larger than the data storage column. Raises
155  
-        # ActionController::SessionOverflowError.
156  
-        def raise_on_session_data_overflow!
157  
-          return false unless loaded?
158  
-          limit = self.class.data_column_size_limit
159  
-          if limit and read_attribute(@@data_column_name).size > limit
160  
-            raise ActionController::SessionOverflowError
161  
-          end
162  
-        end
163  
-    end
164  
-
165  
-    # A barebones session store which duck-types with the default session
166  
-    # store but bypasses Active Record and issues SQL directly. This is
167  
-    # an example session model class meant as a basis for your own classes.
168  
-    #
169  
-    # The database connection, table name, and session id and data columns
170  
-    # are configurable class attributes. Marshaling and unmarshaling
171  
-    # are implemented as class methods that you may override. By default,
172  
-    # marshaling data is
173  
-    #
174  
-    #   ::Base64.encode64(Marshal.dump(data))
175  
-    #
176  
-    # and unmarshaling data is
177  
-    #
178  
-    #   Marshal.load(::Base64.decode64(data))
179  
-    #
180  
-    # This marshaling behavior is intended to store the widest range of
181  
-    # binary session data in a +text+ column. For higher performance,
182  
-    # store in a +blob+ column instead and forgo the Base64 encoding.
183  
-    class SqlBypass
184  
-      extend ClassMethods
185  
-
186  
-      ##
187  
-      # :singleton-method:
188  
-      # The table name defaults to 'sessions'.
189  
-      cattr_accessor :table_name
190  
-      @@table_name = 'sessions'
191  
-
192  
-      ##
193  
-      # :singleton-method:
194  
-      # The session id field defaults to 'session_id'.
195  
-      cattr_accessor :session_id_column
196  
-      @@session_id_column = 'session_id'
197  
-
198  
-      ##
199  
-      # :singleton-method:
200  
-      # The data field defaults to 'data'.
201  
-      cattr_accessor :data_column
202  
-      @@data_column = 'data'
203  
-
204  
-      class << self
205  
-        alias :data_column_name :data_column
206  
-
207  
-        # Use the ActiveRecord::Base.connection by default.
208  
-        attr_writer :connection
209  
-
210  
-        # Use the ActiveRecord::Base.connection_pool by default.
211  
-        attr_writer :connection_pool
212  
-
213  
-        def connection
214  
-          @connection ||= ActiveRecord::Base.connection
215  
-        end
216  
-
217  
-        def connection_pool
218  
-          @connection_pool ||= ActiveRecord::Base.connection_pool
219  
-        end
220  
-
221  
-        # Look up a session by id and unmarshal its data if found.
222  
-        def find_by_session_id(session_id)
223  
-          if record = connection.select_one("SELECT #{connection.quote_column_name(data_column)} AS data FROM #{@@table_name} WHERE #{connection.quote_column_name(@@session_id_column)}=#{connection.quote(session_id.to_s)}")
224  
-            new(:session_id => session_id, :marshaled_data => record['data'])
225  
-          end
226  
-        end
227  
-      end
228  
-
229  
-      delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self
230  
-
231  
-      attr_reader :session_id, :new_record
232  
-      alias :new_record? :new_record
233  
-
234  
-      attr_writer :data
235  
-
236  
-      # Look for normal and marshaled data, self.find_by_session_id's way of
237  
-      # telling us to postpone unmarshaling until the data is requested.
238  
-      # We need to handle a normal data attribute in case of a new record.
239  
-      def initialize(attributes)
240  
-        @session_id     = attributes[:session_id]
241  
-        @data           = attributes[:data]
242  
-        @marshaled_data = attributes[:marshaled_data]
243  
-        @new_record     = @marshaled_data.nil?
244  
-      end
245  
-
246  
-      # Returns true if the record is persisted, i.e. it's not a new record
247  
-      def persisted?
248  
-        !@new_record
249  
-      end
250  
-
251  
-      # Lazy-unmarshal session state.
252  
-      def data
253  
-        unless @data
254  
-          if @marshaled_data
255  
-            @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
256  
-          else
257  
-            @data = {}
258  
-          end
259  
-        end
260  
-        @data
261  
-      end
262  
-
263  
-      def loaded?
264  
-        @data
265  
-      end
266  
-
267  
-      def save
268  
-        return false unless loaded?
269  
-        marshaled_data = self.class.marshal(data)
270  
-        connect        = connection
271  
-
272  
-        if @new_record
273  
-          @new_record = false
274  
-          connect.update <<-end_sql, 'Create session'
275  
-            INSERT INTO #{table_name} (
276  
-              #{connect.quote_column_name(session_id_column)},
277  
-              #{connect.quote_column_name(data_column)} )
278  
-            VALUES (
279  
-              #{connect.quote(session_id)},
280  
-              #{connect.quote(marshaled_data)} )
281  
-          end_sql
282  
-        else
283  
-          connect.update <<-end_sql, 'Update session'
284  
-            UPDATE #{table_name}
285  
-            SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)}
286  
-            WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
287  
-          end_sql
288  
-        end
289  
-      end
290  
-
291  
-      def destroy
292  
-        return if @new_record
293  
-
294  
-        connect = connection
295  
-        connect.delete <<-end_sql, 'Destroy session'
296  
-          DELETE FROM #{table_name}
297  
-          WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id.to_s)}
298  
-        end_sql
299  
-      end
300  
-    end
301  
-
302  
-    # The class used for session storage. Defaults to
303  
-    # ActiveRecord::SessionStore::Session
304  
-    cattr_accessor :session_class
305  
-    self.session_class = Session
306  
-
307  
-    SESSION_RECORD_KEY = 'rack.session.record'
308  
-    ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY
309  
-
310  
-    private
311  
-      def get_session(env, sid)
312  
-        Base.silence do
313  
-          unless sid and session = @@session_class.find_by_session_id(sid)
314  
-            # If the sid was nil or if there is no pre-existing session under the sid,
315  
-            # force the generation of a new sid and associate a new session associated with the new sid
316  
-            sid = generate_sid
317  
-            session = @@session_class.new(:session_id => sid, :data => {})
318  
-          end
319  
-          env[SESSION_RECORD_KEY] = session
320  
-          [sid, session.data]
321  
-        end
322  
-      end
323  
-
324  
-      def set_session(env, sid, session_data, options)
325  
-        Base.silence do
326  
-          record = get_session_model(env, sid)
327  
-          record.data = session_data
328  
-          return false unless record.save
329  
-
330  
-          session_data = record.data
331  
-          if session_data && session_data.respond_to?(:each_value)
332  
-            session_data.each_value do |obj|
333  
-              obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
334  
-            end
335  
-          end
336  
-        end
337  
-
338  
-        sid
339  
-      end
340  
-
341  
-      def destroy_session(env, session_id, options)
342  
-        if sid = current_session_id(env)
343  
-          Base.silence do
344  
-            get_session_model(env, sid).destroy
345  
-            env[SESSION_RECORD_KEY] = nil
346  
-          end
347  
-        end
348  
-
349  
-        generate_sid unless options[:drop]
350  
-      end
351  
-
352  
-      def get_session_model(env, sid)
353  
-        if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
354  
-          env[SESSION_RECORD_KEY] = find_session(sid)
355  
-        else
356  
-          env[SESSION_RECORD_KEY] ||= find_session(sid)
357  
-        end
358  
-      end
359  
-
360  
-      def find_session(id)
361  
-        @@session_class.find_by_session_id(id) ||
362  
-          @@session_class.new(:session_id => id, :data => {})
363  
-      end
364  
-  end
365  
-end
24  activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
... ...
@@ -1,24 +0,0 @@
1  
-require 'rails/generators/active_record'
2  
-
3  
-module ActiveRecord
4  
-  module Generators
5  
-    class SessionMigrationGenerator < Base
6  
-      argument :name, :type => :string, :default => "add_sessions_table"
7  
-
8  
-      def create_migration_file
9  
-        migration_template "migration.rb", "db/migrate/#{file_name}.rb"
10  
-      end
11  
-
12  
-      protected
13  
-
14  
-        def session_table_name
15  
-          current_table_name = ActiveRecord::SessionStore::Session.table_name
16  
-          if current_table_name == 'session' || current_table_name == 'sessions'
17  
-            current_table_name = ActiveRecord::Base.pluralize_table_names ? 'sessions' : 'session'
18  
-          end
19  
-          current_table_name
20  
-        end
21  
-
22  
-    end
23  
-  end
24  
-end
12  activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb
... ...
@@ -1,12 +0,0 @@
1  
-class <%= migration_class_name %> < ActiveRecord::Migration
2  
-  def change
3  
-    create_table :<%= session_table_name %> do |t|
4  
-      t.string :session_id, :null => false
5  
-      t.text :data
6  
-      t.timestamps
7  
-    end
8  
-
9  
-    add_index :<%= session_table_name %>, :session_id
10  
-    add_index :<%= session_table_name %>, :updated_at
11  
-  end
12  
-end
81  activerecord/test/cases/session_store/session_test.rb
... ...
@@ -1,81 +0,0 @@
1  
-require 'cases/helper'
2  
-require 'action_dispatch'
3  
-require 'active_record/session_store'
4  
-
5  
-module ActiveRecord
6  
-  class SessionStore
7  
-    class SessionTest < ActiveRecord::TestCase
8  
-      self.use_transactional_fixtures = false
9  
-
10  
-      attr_reader :session_klass
11  
-
12  
-      def setup
13  
-        super
14  
-        ActiveRecord::Base.connection.schema_cache.clear!
15  
-        Session.drop_table! if Session.table_exists?
16  
-        @session_klass = Class.new(Session)
17  
-      end
18  
-
19  
-      def test_data_column_name
20  
-        # default column name is 'data'
21  
-        assert_equal 'data', Session.data_column_name
22  
-      end
23  
-
24  
-      def test_table_name
25  
-        assert_equal 'sessions', Session.table_name
26  
-      end
27  
-
28  
-      def test_accessible_attributes
29  
-        assert Session.accessible_attributes.include?(:session_id)
30  
-        assert Session.accessible_attributes.include?(:data)
31  
-        assert Session.accessible_attributes.include?(:marshaled_data)
32  
-      end
33  
-
34  
-      def test_create_table!
35  
-        assert !Session.table_exists?
36  
-        Session.create_table!
37  
-        assert Session.table_exists?
38  
-        Session.drop_table!
39  
-        assert !Session.table_exists?
40  
-      end
41  
-
42  
-      def test_find_by_sess_id_compat
43  
-        Session.reset_column_information
44  
-        klass = Class.new(Session) do
45  
-          def self.session_id_column
46  
-            'sessid'
47  
-          end
48  
-        end
49  
-        klass.create_table!
50  
-
51  
-        assert klass.columns_hash['sessid'], 'sessid column exists'
52  
-        session = klass.new(:data => 'hello')
53  
-        session.sessid = "100"
54  
-        session.save!
55  
-
56  
-        found = klass.find_by_session_id("100")
57  
-        assert_equal session, found
58  
-        assert_equal session.sessid, found.session_id
59  
-      ensure
60  
-        klass.drop_table!
61  
-        Session.reset_column_information
62  
-      end
63  
-
64  
-      def test_find_by_session_id
65  
-        Session.create_table!
66  
-        session_id = "10"
67  
-        s = session_klass.create!(:data => 'world', :session_id => session_id)
68  
-        t = session_klass.find_by_session_id(session_id)
69  
-        assert_equal s, t
70  
-        assert_equal s.data, t.data
71  
-        Session.drop_table!
72  
-      end
73  
-
74  
-      def test_loaded?
75  
-        Session.create_table!
76  
-        s = Session.new
77  
-        assert !s.loaded?, 'session is not loaded'
78  
-      end
79  
-    end
80  
-  end
81  
-end
75  activerecord/test/cases/session_store/sql_bypass_test.rb
... ...
@@ -1,75 +0,0 @@
1  
-require 'cases/helper'
2  
-require 'action_dispatch'
3  
-require 'active_record/session_store'
4  
-
5  
-module ActiveRecord
6  
-  class SessionStore
7  
-    class SqlBypassTest < ActiveRecord::TestCase
8  
-      def setup
9  
-        super
10  
-        Session.drop_table! if Session.table_exists?
11  
-      end
12  
-
13  
-      def test_create_table
14  
-        assert !Session.table_exists?
15  
-        SqlBypass.create_table!
16  
-        assert Session.table_exists?
17  
-        SqlBypass.drop_table!
18  
-        assert !Session.table_exists?
19  
-      end
20  
-
21  
-      def test_new_record?
22  
-        s = SqlBypass.new :data => 'foo', :session_id => 10
23  
-        assert s.new_record?, 'this is a new record!'
24  
-      end
25  
-
26  
-      def test_persisted?
27  
-        s = SqlBypass.new :data => 'foo', :session_id => 10
28  
-        assert !s.persisted?, 'this is a new record!'
29  
-      end
30  
-
31  
-      def test_not_loaded?
32  
-        s = SqlBypass.new({})
33  
-        assert !s.loaded?, 'it is not loaded'
34  
-      end
35  
-
36  
-      def test_loaded?
37  
-        s = SqlBypass.new :data => 'hello'
38  
-        assert s.loaded?, 'it is loaded'
39  
-      end
40  
-
41  
-      def test_save
42  
-        SqlBypass.create_table! unless Session.table_exists?
43  
-        session_id = 20
44  
-        s = SqlBypass.new :data => 'hello', :session_id => session_id
45  
-        s.save
46  
-        t = SqlBypass.find_by_session_id session_id
47  
-        assert_equal s.session_id, t.session_id
48  
-        assert_equal s.data, t.data
49  
-      end
50  
-
51  
-      def test_destroy
52  
-        SqlBypass.create_table! unless Session.table_exists?
53  
-        session_id = 20
54  
-        s = SqlBypass.new :data => 'hello', :session_id => session_id
55  
-        s.save
56  
-        s.destroy
57  
-        assert_nil SqlBypass.find_by_session_id session_id
58  
-      end
59  
-
60  
-      def test_data_column
61  
-        SqlBypass.drop_table! if exists = Session.table_exists?
62  
-        old, SqlBypass.data_column = SqlBypass.data_column, 'foo'
63  
-        SqlBypass.create_table!
64  
-
65  
-        session_id = 20
66  
-        SqlBypass.new(:data => 'hello', :session_id => session_id).save
67  
-        assert_equal 'hello', SqlBypass.find_by_session_id(session_id).data
68  
-      ensure
69  
-        SqlBypass.drop_table!
70  
-        SqlBypass.data_column = old
71  
-        SqlBypass.create_table! if exists
72  
-      end
73  
-    end
74  
-  end
75  
-end
5  guides/code/getting_started/config/initializers/session_store.rb
... ...
@@ -1,8 +1,3 @@
1 1
 # Be sure to restart your server when you modify this file.
2 2
 
3 3
 Blog::Application.config.session_store :cookie_store, key: '_blog_session'
4  
-
5  
-# Use the database for sessions instead of the cookie-based default,
6  
-# which shouldn't be used to store highly confidential information
7  
-# (create the session table with "rails generate session_migration")
8  
-# Blog::Application.config.session_store :active_record_store
2  guides/source/4_0_release_notes.textile
Source Rendered
@@ -730,6 +730,8 @@ where(...).remove_conditions # => still has conditions
730 730
 
731 731
 * The migration generator now creates a join table with (commented) indexes every time the migration name contains the word "join_table".
732 732
 
  733
+* <tt>ActiveRecord::SessionStore</tt> is removed from Rails 4.0 and is now a separate "gem":https://github.com/rails/activerecord-session_store.
  734
+
733 735
 h3. Active Model
734 736
 
735 737
 * Changed <tt>AM::Serializers::JSON.include_root_in_json</tt> default value to false. Now, AM Serializers and AR objects have the same default behaviour.
4  guides/source/action_controller_overview.textile
Source Rendered
@@ -168,8 +168,8 @@ h3. Session
168 168
 Your application has a session for each user in which you can store small amounts of data that will be persisted between requests. The session is only available in the controller and the view and can use one of a number of different storage mechanisms:
169 169
 
170 170
 * ActionDispatch::Session::CookieStore - Stores everything on the client.
171  
-* ActiveRecord::SessionStore - Stores the data in a database using Active Record.
172 171
 * ActionDispatch::Session::CacheStore - Stores the data in the Rails cache.
  172
+* ActionDispatch::Session::ActiveRecordStore - Stores the data in a database using Active Record. (require `activerecord-session_store` gem).
173 173
 * ActionDispatch::Session::MemCacheStore - Stores the data in a memcached cluster (this is a legacy implementation; consider using CacheStore instead).
174 174
 
175 175
 All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure).
@@ -187,7 +187,7 @@ If you need a different session storage mechanism, you can change it in the +con
187 187
 <ruby>
188 188
 # Use the database for sessions instead of the cookie-based default,
189 189
 # which shouldn't be used to store highly confidential information
190  
-# (create the session table with "script/rails g session_migration")
  190
+# (create the session table with "script/rails g active_record:session_migration")
191 191
 # YourApp::Application.config.session_store :active_record_store
192 192
 </ruby>
193 193
 
10  guides/source/active_support_core_extensions.textile
Source Rendered
@@ -1491,13 +1491,9 @@ For example, Action Pack uses this method to load the class that provides a cert
1491 1491
 <ruby>
1492 1492
 # action_controller/metal/session_management.rb
1493 1493
 def session_store=(store)
1494  
-  if store == :active_record_store
1495  
-    self.session_store = ActiveRecord::SessionStore
1496  
-  else
1497  
-    @@session_store = store.is_a?(Symbol) ?
1498  
-      ActionDispatch::Session.const_get(store.to_s.camelize) :
1499  
-      store
1500  
-  end
  1494
+  @@session_store = store.is_a?(Symbol) ?
  1495
+    ActionDispatch::Session.const_get(store.to_s.camelize) :
  1496
+    store
1501 1497
 end
1502 1498
 </ruby>
1503 1499
 
10  guides/source/configuring.textile
Source Rendered
@@ -127,7 +127,7 @@ end
127 127
 config.session_store :my_custom_store
128 128
 </ruby>
129 129
 
130  
-This custom store must be defined as +ActionDispatch::Session::MyCustomStore+. In addition to symbols, they can also be objects implementing a certain API, like +ActiveRecord::SessionStore+, in which case no special namespace is required.
  130
+This custom store must be defined as +ActionDispatch::Session::MyCustomStore+.
131 131
 
132 132
 * +config.time_zone+ sets the default time zone for the application and enables time zone awareness for Active Record.
133 133
 
@@ -322,14 +322,6 @@ The caching code adds two additional settings:
322 322
 
323 323
 * +ActionController::Base.page_cache_extension+ sets the extension to be used when generating pages for the cache (this is ignored if the incoming request already has an extension). The default is +.html+.
324 324
 
325  
-The Active Record session store can also be configured:
326  
-
327  
-* +ActiveRecord::SessionStore::Session.table_name+ sets the name of the table used to store sessions. Defaults to +sessions+.
328  
-
329  
-* +ActiveRecord::SessionStore::Session.primary_key+ sets the name of the ID column used in the sessions table. Defaults to +session_id+.
330  
-
331  
-* +ActiveRecord::SessionStore::Session.data_column_name+ sets the name of the column which stores marshaled session data. Defaults to +data+.
332  
-
333 325
 h4. Configuring Action Dispatch
334 326
 
335 327
 * +config.action_dispatch.session_store+ sets the name of the store for session data. The default is +:cookie_store+; other valid options include +:active_record_store+, +:mem_cache_store+ or the name of your own custom class.
4  guides/source/security.textile
Source Rendered
@@ -81,9 +81,7 @@ This will also be a good idea, if you modify the structure of an object and old
81 81
 
82 82
 h4. Session Storage
83 83
 
84  
-NOTE: _Rails provides several storage mechanisms for the session hashes. The most important are +ActiveRecord::SessionStore+ and +ActionDispatch::Session::CookieStore+._
85  
-
86  
-There are a number of session storages, i.e. where Rails saves the session hash and session id. Most real-live applications choose ActiveRecord::SessionStore (or one of its derivatives) over file storage due to performance and maintenance reasons. ActiveRecord::SessionStore keeps the session id and hash in a database table and saves and retrieves the hash on every request.
  84
+NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is +ActionDispatch::Session::CookieStore+._
87 85
 
88 86
 Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session id. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it:
89 87
 
7  railties/lib/rails/application/configuration.rb
@@ -126,7 +126,12 @@ def session_store(*args)
126 126
           when :disabled
127 127
             nil
128 128
           when :active_record_store
129  
-            ActiveRecord::SessionStore
  129
+            begin
  130
+              ActionDispatch::Session::ActiveRecordStore
  131
+            rescue NameError
  132
+              raise "`ActiveRecord::SessionStore` is extracted out of Rails into a gem. " \
  133
+                "Please add `activerecord-session_store` to your Gemfile to use it."
  134
+            end
130 135
           when Symbol
131 136
             ActionDispatch::Session.const_get(@session_store.to_s.camelize)
132 137
           else
1  railties/lib/rails/generators.rb
@@ -173,7 +173,6 @@ def self.hidden_namespaces
173 173
           "#{orm}:migration",
174 174
           "#{orm}:model",
175 175
           "#{orm}:observer",
176  
-          "#{orm}:session_migration",
177 176
           "#{test}:controller",
178 177