aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md3
-rw-r--r--activerecord/lib/active_record.rb11
-rw-r--r--activerecord/lib/active_record/railties/databases.rake15
-rw-r--r--activerecord/lib/active_record/session_store.rb365
-rw-r--r--activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb24
-rw-r--r--activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb12
-rw-r--r--activerecord/test/cases/session_store/session_test.rb81
-rw-r--r--activerecord/test/cases/session_store/sql_bypass_test.rb75
8 files changed, 3 insertions, 583 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c5ef39b9d2..18b10f1d81 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,8 @@
## Rails 4.0.0 (unreleased) ##
+* ActiveRecord::SessionStore has been extracted from Active Record as `activerecord-session_store`
+ gem. Please read the `README.md` file on the gem for the usage. *Prem Sichanugrist*
+
* Fix `reset_counters` when there are multiple `belongs_to` association with the
same foreign key and one of them have a counter cache.
Fixes #5200.
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index fa94f6a941..1675127ab0 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -53,17 +53,6 @@ module ActiveRecord
autoload :ReadonlyAttributes
autoload :Reflection
autoload :Sanitization
-
- # ActiveRecord::SessionStore depends on the abstract store in Action Pack.
- # Eager loading this class would break client code that eager loads Active
- # Record standalone.
- #
- # Note that the Rails application generator creates an initializer specific
- # for setting the session store. Thus, albeit in theory this autoload would
- # not be thread-safe, in practice it is because if the application uses this
- # session store its autoload happens at boot time.
- autoload :SessionStore
-
autoload :Schema
autoload :SchemaDumper
autoload :SchemaMigration
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 4e5ec4f739..ae24542521 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -408,21 +408,6 @@ db_namespace = namespace :db do
end
end
end
-
- namespace :sessions do
- # desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
- task :create => [:environment, :load_config] do
- raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations?
- Rails.application.load_generators
- require 'rails/generators/rails/session_migration/session_migration_generator'
- Rails::Generators::SessionMigrationGenerator.start [ ENV['MIGRATION'] || 'add_sessions_table' ]
- end
-
- # desc "Clear the sessions table"
- task :clear => [:environment, :load_config] do
- ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::SessionStore::Session.table_name}"
- end
- end
end
namespace :railties do
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
deleted file mode 100644
index 58e1dab508..0000000000
--- a/activerecord/lib/active_record/session_store.rb
+++ /dev/null
@@ -1,365 +0,0 @@
-require 'action_dispatch/middleware/session/abstract_store'
-
-module ActiveRecord
- # = Active Record Session Store
- #
- # A session store backed by an Active Record class. A default class is
- # provided, but any object duck-typing to an Active Record Session class
- # with text +session_id+ and +data+ attributes is sufficient.
- #
- # The default assumes a +sessions+ tables with columns:
- # +id+ (numeric primary key),
- # +session_id+ (string, usually varchar; maximum length is 255), and
- # +data+ (text or longtext; careful if your session data exceeds 65KB).
- #
- # The +session_id+ column should always be indexed for speedy lookups.
- # Session data is marshaled to the +data+ column in Base64 format.
- # If the data you write is larger than the column's size limit,
- # ActionController::SessionOverflowError will be raised.
- #
- # You may configure the table name, primary key, and data column.
- # For example, at the end of <tt>config/application.rb</tt>:
- #
- # ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
- # ActiveRecord::SessionStore::Session.primary_key = 'session_id'
- # ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
- #
- # Note that setting the primary key to the +session_id+ frees you from
- # having a separate +id+ column if you don't want it. However, you must
- # set <tt>session.model.id = session.session_id</tt> by hand! A before filter
- # on ApplicationController is a good place.
- #
- # Since the default class is a simple Active Record, you get timestamps
- # for free if you add +created_at+ and +updated_at+ datetime columns to
- # the +sessions+ table, making periodic session expiration a snap.
- #
- # You may provide your own session class implementation, whether a
- # feature-packed Active Record or a bare-metal high-performance SQL
- # store, by setting
- #
- # ActiveRecord::SessionStore.session_class = MySessionClass
- #
- # You must implement these methods:
- #
- # self.find_by_session_id(session_id)
- # initialize(hash_of_session_id_and_data, options_hash = {})
- # attr_reader :session_id
- # attr_accessor :data
- # save
- # destroy
- #
- # The example SqlBypass class is a generic SQL session store. You may
- # use it as a basis for high-performance database-specific stores.
- class SessionStore < ActionDispatch::Session::AbstractStore
- module ClassMethods # :nodoc:
- def marshal(data)
- ::Base64.encode64(Marshal.dump(data)) if data
- end
-
- def unmarshal(data)
- Marshal.load(::Base64.decode64(data)) if data
- end
-
- def drop_table!
- connection.schema_cache.clear_table_cache!(table_name)
- connection.drop_table table_name
- end
-
- def create_table!
- connection.schema_cache.clear_table_cache!(table_name)
- connection.create_table(table_name) do |t|
- t.string session_id_column, :limit => 255
- t.text data_column_name
- end
- connection.add_index table_name, session_id_column, :unique => true
- end
- end
-
- # The default Active Record class.
- class Session < ActiveRecord::Base
- extend ClassMethods
-
- ##
- # :singleton-method:
- # Customizable data column name. Defaults to 'data'.
- cattr_accessor :data_column_name
- self.data_column_name = 'data'
-
- attr_accessible :session_id, :data, :marshaled_data
-
- before_save :marshal_data!
- before_save :raise_on_session_data_overflow!
-
- class << self
- def data_column_size_limit
- @data_column_size_limit ||= columns_hash[data_column_name].limit
- end
-
- # Hook to set up sessid compatibility.
- def find_by_session_id(session_id)
- setup_sessid_compatibility!
- find_by_session_id(session_id)
- end
-
- private
- def session_id_column
- 'session_id'
- end
-
- # Compatibility with tables using sessid instead of session_id.
- def setup_sessid_compatibility!
- # Reset column info since it may be stale.
- reset_column_information
- if columns_hash['sessid']
- def self.find_by_session_id(*args)
- find_by_sessid(*args)
- end
-
- define_method(:session_id) { sessid }
- define_method(:session_id=) { |session_id| self.sessid = session_id }
- else
- class << self; remove_possible_method :find_by_session_id; end
-
- def self.find_by_session_id(session_id)
- where(session_id: session_id).first
- end
- end
- end
- end
-
- def initialize(attributes = nil, options = {})
- @data = nil
- super
- end
-
- # Lazy-unmarshal session state.
- def data
- @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
- end
-
- attr_writer :data
-
- # Has the session been loaded yet?
- def loaded?
- @data
- end
-
- private
- def marshal_data!
- return false unless loaded?
- write_attribute(@@data_column_name, self.class.marshal(data))
- end
-
- # Ensures that the data about to be stored in the database is not
- # larger than the data storage column. Raises
- # ActionController::SessionOverflowError.
- def raise_on_session_data_overflow!
- return false unless loaded?
- limit = self.class.data_column_size_limit
- if limit and read_attribute(@@data_column_name).size > limit
- raise ActionController::SessionOverflowError
- end
- end
- end
-
- # A barebones session store which duck-types with the default session
- # store but bypasses Active Record and issues SQL directly. This is
- # an example session model class meant as a basis for your own classes.
- #
- # The database connection, table name, and session id and data columns
- # are configurable class attributes. Marshaling and unmarshaling
- # are implemented as class methods that you may override. By default,
- # marshaling data is
- #
- # ::Base64.encode64(Marshal.dump(data))
- #
- # and unmarshaling data is
- #
- # Marshal.load(::Base64.decode64(data))
- #
- # This marshaling behavior is intended to store the widest range of
- # binary session data in a +text+ column. For higher performance,
- # store in a +blob+ column instead and forgo the Base64 encoding.
- class SqlBypass
- extend ClassMethods
-
- ##
- # :singleton-method:
- # The table name defaults to 'sessions'.
- cattr_accessor :table_name
- @@table_name = 'sessions'
-
- ##
- # :singleton-method:
- # The session id field defaults to 'session_id'.
- cattr_accessor :session_id_column
- @@session_id_column = 'session_id'
-
- ##
- # :singleton-method:
- # The data field defaults to 'data'.
- cattr_accessor :data_column
- @@data_column = 'data'
-
- class << self
- alias :data_column_name :data_column
-
- # Use the ActiveRecord::Base.connection by default.
- attr_writer :connection
-
- # Use the ActiveRecord::Base.connection_pool by default.
- attr_writer :connection_pool
-
- def connection
- @connection ||= ActiveRecord::Base.connection
- end
-
- def connection_pool
- @connection_pool ||= ActiveRecord::Base.connection_pool
- end
-
- # Look up a session by id and unmarshal its data if found.
- def find_by_session_id(session_id)
- 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)}")
- new(:session_id => session_id, :marshaled_data => record['data'])
- end
- end
- end
-
- delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self
-
- attr_reader :session_id, :new_record
- alias :new_record? :new_record
-
- attr_writer :data
-
- # Look for normal and marshaled data, self.find_by_session_id's way of
- # telling us to postpone unmarshaling until the data is requested.
- # We need to handle a normal data attribute in case of a new record.
- def initialize(attributes)
- @session_id = attributes[:session_id]
- @data = attributes[:data]
- @marshaled_data = attributes[:marshaled_data]
- @new_record = @marshaled_data.nil?
- end
-
- # Returns true if the record is persisted, i.e. it's not a new record
- def persisted?
- !@new_record
- end
-
- # Lazy-unmarshal session state.
- def data
- unless @data
- if @marshaled_data
- @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
- else
- @data = {}
- end
- end
- @data
- end
-
- def loaded?
- @data
- end
-
- def save
- return false unless loaded?
- marshaled_data = self.class.marshal(data)
- connect = connection
-
- if @new_record
- @new_record = false
- connect.update <<-end_sql, 'Create session'
- INSERT INTO #{table_name} (
- #{connect.quote_column_name(session_id_column)},
- #{connect.quote_column_name(data_column)} )
- VALUES (
- #{connect.quote(session_id)},
- #{connect.quote(marshaled_data)} )
- end_sql
- else
- connect.update <<-end_sql, 'Update session'
- UPDATE #{table_name}
- SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)}
- WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
- end_sql
- end
- end
-
- def destroy
- return if @new_record
-
- connect = connection
- connect.delete <<-end_sql, 'Destroy session'
- DELETE FROM #{table_name}
- WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id.to_s)}
- end_sql
- end
- end
-
- # The class used for session storage. Defaults to
- # ActiveRecord::SessionStore::Session
- cattr_accessor :session_class
- self.session_class = Session
-
- SESSION_RECORD_KEY = 'rack.session.record'
- ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY
-
- private
- def get_session(env, sid)
- Base.silence do
- unless sid and session = @@session_class.find_by_session_id(sid)
- # If the sid was nil or if there is no pre-existing session under the sid,
- # force the generation of a new sid and associate a new session associated with the new sid
- sid = generate_sid
- session = @@session_class.new(:session_id => sid, :data => {})
- end
- env[SESSION_RECORD_KEY] = session
- [sid, session.data]
- end
- end
-
- def set_session(env, sid, session_data, options)
- Base.silence do
- record = get_session_model(env, sid)
- record.data = session_data
- return false unless record.save
-
- session_data = record.data
- if session_data && session_data.respond_to?(:each_value)
- session_data.each_value do |obj|
- obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
- end
- end
- end
-
- sid
- end
-
- def destroy_session(env, session_id, options)
- if sid = current_session_id(env)
- Base.silence do
- get_session_model(env, sid).destroy
- env[SESSION_RECORD_KEY] = nil
- end
- end
-
- generate_sid unless options[:drop]
- end
-
- def get_session_model(env, sid)
- if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
- env[SESSION_RECORD_KEY] = find_session(sid)
- else
- env[SESSION_RECORD_KEY] ||= find_session(sid)
- end
- end
-
- def find_session(id)
- @@session_class.find_by_session_id(id) ||
- @@session_class.new(:session_id => id, :data => {})
- end
- end
-end
diff --git a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
deleted file mode 100644
index 75aee4f408..0000000000
--- a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'rails/generators/active_record'
-
-module ActiveRecord
- module Generators
- class SessionMigrationGenerator < Base
- argument :name, :type => :string, :default => "add_sessions_table"
-
- def create_migration_file
- migration_template "migration.rb", "db/migrate/#{file_name}.rb"
- end
-
- protected
-
- def session_table_name
- current_table_name = ActiveRecord::SessionStore::Session.table_name
- if current_table_name == 'session' || current_table_name == 'sessions'
- current_table_name = ActiveRecord::Base.pluralize_table_names ? 'sessions' : 'session'
- end
- current_table_name
- end
-
- end
- end
-end
diff --git a/activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb
deleted file mode 100644
index 9ea3248513..0000000000
--- a/activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class <%= migration_class_name %> < ActiveRecord::Migration
- def change
- create_table :<%= session_table_name %> do |t|
- t.string :session_id, :null => false
- t.text :data
- t.timestamps
- end
-
- add_index :<%= session_table_name %>, :session_id
- add_index :<%= session_table_name %>, :updated_at
- end
-end
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
deleted file mode 100644
index a3b8ab74d9..0000000000
--- a/activerecord/test/cases/session_store/session_test.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-require 'cases/helper'
-require 'action_dispatch'
-require 'active_record/session_store'
-
-module ActiveRecord
- class SessionStore
- class SessionTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
-
- attr_reader :session_klass
-
- def setup
- super
- ActiveRecord::Base.connection.schema_cache.clear!
- Session.drop_table! if Session.table_exists?
- @session_klass = Class.new(Session)
- end
-
- def test_data_column_name
- # default column name is 'data'
- assert_equal 'data', Session.data_column_name
- end
-
- def test_table_name
- assert_equal 'sessions', Session.table_name
- end
-
- def test_accessible_attributes
- assert Session.accessible_attributes.include?(:session_id)
- assert Session.accessible_attributes.include?(:data)
- assert Session.accessible_attributes.include?(:marshaled_data)
- end
-
- def test_create_table!
- assert !Session.table_exists?
- Session.create_table!
- assert Session.table_exists?
- Session.drop_table!
- assert !Session.table_exists?
- end
-
- def test_find_by_sess_id_compat
- Session.reset_column_information
- klass = Class.new(Session) do
- def self.session_id_column
- 'sessid'
- end
- end
- klass.create_table!
-
- assert klass.columns_hash['sessid'], 'sessid column exists'
- session = klass.new(:data => 'hello')
- session.sessid = "100"
- session.save!
-
- found = klass.find_by_session_id("100")
- assert_equal session, found
- assert_equal session.sessid, found.session_id
- ensure
- klass.drop_table!
- Session.reset_column_information
- end
-
- def test_find_by_session_id
- Session.create_table!
- session_id = "10"
- s = session_klass.create!(:data => 'world', :session_id => session_id)
- t = session_klass.find_by_session_id(session_id)
- assert_equal s, t
- assert_equal s.data, t.data
- Session.drop_table!
- end
-
- def test_loaded?
- Session.create_table!
- s = Session.new
- assert !s.loaded?, 'session is not loaded'
- end
- end
- end
-end
diff --git a/activerecord/test/cases/session_store/sql_bypass_test.rb b/activerecord/test/cases/session_store/sql_bypass_test.rb
deleted file mode 100644
index b8cf4cf2cc..0000000000
--- a/activerecord/test/cases/session_store/sql_bypass_test.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'cases/helper'
-require 'action_dispatch'
-require 'active_record/session_store'
-
-module ActiveRecord
- class SessionStore
- class SqlBypassTest < ActiveRecord::TestCase
- def setup
- super
- Session.drop_table! if Session.table_exists?
- end
-
- def test_create_table
- assert !Session.table_exists?
- SqlBypass.create_table!
- assert Session.table_exists?
- SqlBypass.drop_table!
- assert !Session.table_exists?
- end
-
- def test_new_record?
- s = SqlBypass.new :data => 'foo', :session_id => 10
- assert s.new_record?, 'this is a new record!'
- end
-
- def test_persisted?
- s = SqlBypass.new :data => 'foo', :session_id => 10
- assert !s.persisted?, 'this is a new record!'
- end
-
- def test_not_loaded?
- s = SqlBypass.new({})
- assert !s.loaded?, 'it is not loaded'
- end
-
- def test_loaded?
- s = SqlBypass.new :data => 'hello'
- assert s.loaded?, 'it is loaded'
- end
-
- def test_save
- SqlBypass.create_table! unless Session.table_exists?
- session_id = 20
- s = SqlBypass.new :data => 'hello', :session_id => session_id
- s.save
- t = SqlBypass.find_by_session_id session_id
- assert_equal s.session_id, t.session_id
- assert_equal s.data, t.data
- end
-
- def test_destroy
- SqlBypass.create_table! unless Session.table_exists?
- session_id = 20
- s = SqlBypass.new :data => 'hello', :session_id => session_id
- s.save
- s.destroy
- assert_nil SqlBypass.find_by_session_id session_id
- end
-
- def test_data_column
- SqlBypass.drop_table! if exists = Session.table_exists?
- old, SqlBypass.data_column = SqlBypass.data_column, 'foo'
- SqlBypass.create_table!
-
- session_id = 20
- SqlBypass.new(:data => 'hello', :session_id => session_id).save
- assert_equal 'hello', SqlBypass.find_by_session_id(session_id).data
- ensure
- SqlBypass.drop_table!
- SqlBypass.data_column = old
- SqlBypass.create_table! if exists
- end
- end
- end
-end