pax_global_header 0000666 0000000 0000000 00000000064 12460531061 0014510 g ustar 00root root 0000000 0000000 52 comment=1cc0daf77d542045da4f6a43cba9ce04c27453dd
activerecord-session_store-0.1.1/ 0000775 0000000 0000000 00000000000 12460531061 0017056 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/.gitignore 0000664 0000000 0000000 00000000224 12460531061 0021044 0 ustar 00root root 0000000 0000000 *.gem
*.rbc
.bundle
.config
.yardoc
*.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
activerecord-session_store-0.1.1/.travis.yml 0000664 0000000 0000000 00000001032 12460531061 0021163 0 ustar 00root root 0000000 0000000 language: ruby
sudo: false
rvm:
- 1.9.3
- 2.0
- 2.1
- 2.2
- ruby-head
gemfile:
- gemfiles/4.0.gemfile
- gemfiles/4.1.gemfile
- gemfiles/4.2.gemfile
- gemfiles/edge.gemfile
notifications:
email: false
before_install:
- gem install bundler
matrix:
fast_finish: true
exclude:
- rvm: 1.9.3
gemfile: gemfiles/edge.gemfile
- rvm: 2.0
gemfile: gemfiles/edge.gemfile
- rvm: 2.1
gemfile: gemfiles/edge.gemfile
allow_failures:
- rvm: ruby-head
- gemfile: gemfiles/edge.gemfile
activerecord-session_store-0.1.1/Appraisals 0000664 0000000 0000000 00000000551 12460531061 0021101 0 ustar 00root root 0000000 0000000 %w(4.0 4.1 4.2).each do |version|
appraise version do
gem "actionpack", "~> #{version}.0"
gem "activerecord", "~> #{version}.0"
gem "railties", "~> #{version}.0"
end
end
appraise "edge" do
git "https://github.com/rails/rails.git" do
gem "actionpack"
gem "activerecord"
gem "railties"
end
gem "arel", github: "rails/arel"
end
activerecord-session_store-0.1.1/Gemfile 0000664 0000000 0000000 00000000047 12460531061 0020352 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org'
gemspec
activerecord-session_store-0.1.1/LICENSE 0000664 0000000 0000000 00000002071 12460531061 0020063 0 ustar 00root root 0000000 0000000 Copyright (c) 2012 David Heinemeier Hansson
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.
activerecord-session_store-0.1.1/README.md 0000664 0000000 0000000 00000004726 12460531061 0020346 0 ustar 00root root 0000000 0000000 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.
Installation
------------
Include this gem into your Gemfile:
```ruby
gem 'activerecord-session_store'
```
Run the migration generator:
rails generate active_record:session_migration
Then, set your session store in `config/initializers/session_store.rb`:
```ruby
MyApp::Application.config.session_store :active_record_store, :key => '_my_app_session'
```
Configuration
--------------
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 `config/application.rb`:
```ruby
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 `session.model.id = session.session_id` 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
```ruby
ActionDispatch::Session::ActiveRecordStore.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.
activerecord-session_store-0.1.1/Rakefile 0000664 0000000 0000000 00000000314 12460531061 0020521 0 ustar 00root root 0000000 0000000 #!/usr/bin/env rake
require "bundler/gem_tasks"
require 'rake/testtask'
Rake::TestTask.new do |t|
t.libs = ["test"]
t.pattern = "test/**/*_test.rb"
t.ruby_opts = ['-w']
end
task :default => :test
activerecord-session_store-0.1.1/activerecord-session_store.gemspec 0000664 0000000 0000000 00000001546 12460531061 0026000 0 ustar 00root root 0000000 0000000 Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'activerecord-session_store'
s.version = '0.1.1'
s.summary = 'An Action Dispatch session store backed by an Active Record class.'
s.required_ruby_version = '>= 1.9.3'
s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.md', 'lib/**/*']
s.require_path = 'lib'
s.extra_rdoc_files = %w( README.md )
s.rdoc_options.concat ['--main', 'README.md']
s.add_dependency('activerecord', '>= 4.0.0', '< 5')
s.add_dependency('actionpack', '>= 4.0.0', '< 5')
s.add_dependency('railties', '>= 4.0.0', '< 5')
s.add_development_dependency('sqlite3')
s.add_development_dependency('appraisal')
end
activerecord-session_store-0.1.1/gemfiles/ 0000775 0000000 0000000 00000000000 12460531061 0020651 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/gemfiles/4.0.gemfile 0000664 0000000 0000000 00000000266 12460531061 0022510 0 ustar 00root root 0000000 0000000 # This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 4.0.0"
gem "activerecord", "~> 4.0.0"
gem "railties", "~> 4.0.0"
gemspec :path => "../"
activerecord-session_store-0.1.1/gemfiles/4.1.gemfile 0000664 0000000 0000000 00000000266 12460531061 0022511 0 ustar 00root root 0000000 0000000 # This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 4.1.0"
gem "activerecord", "~> 4.1.0"
gem "railties", "~> 4.1.0"
gemspec :path => "../"
activerecord-session_store-0.1.1/gemfiles/4.2.gemfile 0000664 0000000 0000000 00000000266 12460531061 0022512 0 ustar 00root root 0000000 0000000 # This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 4.2.0"
gem "activerecord", "~> 4.2.0"
gem "railties", "~> 4.2.0"
gemspec :path => "../"
activerecord-session_store-0.1.1/gemfiles/edge.gemfile 0000664 0000000 0000000 00000000355 12460531061 0023112 0 ustar 00root root 0000000 0000000 # This file was generated by Appraisal
source "https://rubygems.org"
git "https://github.com/rails/rails.git" do
gem "actionpack"
gem "activerecord"
gem "railties"
end
gem "arel", :github => "rails/arel"
gemspec :path => "../"
activerecord-session_store-0.1.1/lib/ 0000775 0000000 0000000 00000000000 12460531061 0017624 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/action_dispatch/ 0000775 0000000 0000000 00000000000 12460531061 0022760 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/action_dispatch/session/ 0000775 0000000 0000000 00000000000 12460531061 0024443 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/action_dispatch/session/active_record_store.rb 0000664 0000000 0000000 00000011325 12460531061 0031017 0 ustar 00root root 0000000 0000000 require "active_support/core_ext/module/attribute_accessors"
require 'action_dispatch/middleware/session/abstract_store'
module ActionDispatch
module Session
# = 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 config/application.rb:
#
# 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 session.model.id = session.session_id 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
#
# ActionDispatch::Session::ActiveRecordStore.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 ActiveRecordStore < ActionDispatch::Session::AbstractStore
# The class used for session storage. Defaults to
# ActiveRecord::SessionStore::Session
cattr_accessor :session_class
SESSION_RECORD_KEY = 'rack.session.record'
ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY
private
def get_session(env, sid)
logger.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)
logger.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
sid
end
end
def destroy_session(env, session_id, options)
logger.silence do
if sid = current_session_id(env)
get_session_model(env, sid).destroy
env[SESSION_RECORD_KEY] = nil
end
generate_sid unless options[:drop]
end
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
def logger
ActiveRecord::Base.logger || ActiveRecord::SessionStore::NilLogger
end
end
end
end
activerecord-session_store-0.1.1/lib/active_record/ 0000775 0000000 0000000 00000000000 12460531061 0022435 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/active_record/session_store.rb 0000664 0000000 0000000 00000002241 12460531061 0025660 0 ustar 00root root 0000000 0000000 require 'action_dispatch/session/active_record_store'
require "active_record/session_store/extension/logger_silencer"
module ActiveRecord
module SessionStore
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
end
end
require 'active_record/session_store/session'
require 'active_record/session_store/sql_bypass'
require 'active_record/session_store/railtie' if defined?(Rails)
ActionDispatch::Session::ActiveRecordStore.session_class = ActiveRecord::SessionStore::Session
Logger.send :include, ActiveRecord::SessionStore::Extension::LoggerSilencer
activerecord-session_store-0.1.1/lib/active_record/session_store/ 0000775 0000000 0000000 00000000000 12460531061 0025334 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/active_record/session_store/extension/ 0000775 0000000 0000000 00000000000 12460531061 0027350 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/active_record/session_store/extension/logger_silencer.rb 0000664 0000000 0000000 00000003741 12460531061 0033045 0 ustar 00root root 0000000 0000000 require "thread"
require "active_support/core_ext/class/attribute_accessors"
require "active_support/core_ext/module/aliasing"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/concern"
module ActiveRecord
module SessionStore
module Extension
module LoggerSilencer
extend ActiveSupport::Concern
included do
cattr_accessor :silencer
self.silencer = true
alias_method_chain :level, :threadsafety
alias_method_chain :add, :threadsafety
end
def thread_level
Thread.current[thread_hash_level_key]
end
def thread_level=(level)
Thread.current[thread_hash_level_key] = level
end
def level_with_threadsafety
thread_level || level_without_threadsafety
end
def add_with_threadsafety(severity, message = nil, progname = nil, &block)
return true if @logdev.nil? or (severity || UNKNOWN) < level
add_without_threadsafety(severity, message, progname, &block)
end
# Silences the logger for the duration of the block.
def silence(temporary_level = Logger::ERROR)
if silencer
begin
self.thread_level = temporary_level
yield self
ensure
self.thread_level = nil
end
else
yield self
end
end
for severity in Logger::Severity.constants
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{severity.downcase}? # def debug?
Logger::#{severity} >= level # DEBUG >= level
end # end
EOT
end
private
def thread_hash_level_key
@thread_hash_level_key ||= :"ThreadSafeLogger##{object_id}@level"
end
end
end
class NilLogger
def self.silence
yield
end
end
end
end
activerecord-session_store-0.1.1/lib/active_record/session_store/railtie.rb 0000664 0000000 0000000 00000000250 12460531061 0027307 0 ustar 00root root 0000000 0000000 require 'rails/railtie'
module ActiveRecord
module SessionStore
class Railtie < Rails::Railtie
rake_tasks { load "tasks/database.rake" }
end
end
end
activerecord-session_store-0.1.1/lib/active_record/session_store/session.rb 0000664 0000000 0000000 00000005557 12460531061 0027360 0 ustar 00root root 0000000 0000000 require "active_support/core_ext/module/attribute_accessors"
module ActiveRecord
module SessionStore
# 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'
before_save :marshal_data!
before_save :raise_on_session_data_overflow!
# This method is defiend in `protected_attributes` gem. We can't check for
# `attr_accessible` as Rails also define this and raise `RuntimeError`
# telling you to use the gem.
if respond_to?(:accessible_attributes)
attr_accessible :session_id, :data
end
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)
Thread.exclusive { 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)
@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
end
end
activerecord-session_store-0.1.1/lib/active_record/session_store/sql_bypass.rb 0000664 0000000 0000000 00000011042 12460531061 0030037 0 ustar 00root root 0000000 0000000 require "active_support/core_ext/module/attribute_accessors"
module ActiveRecord
module SessionStore
# 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
end
end
activerecord-session_store-0.1.1/lib/activerecord/ 0000775 0000000 0000000 00000000000 12460531061 0022276 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/activerecord/session_store.rb 0000664 0000000 0000000 00000000046 12460531061 0025522 0 ustar 00root root 0000000 0000000 require 'active_record/session_store'
activerecord-session_store-0.1.1/lib/generators/ 0000775 0000000 0000000 00000000000 12460531061 0021775 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/generators/active_record/ 0000775 0000000 0000000 00000000000 12460531061 0024606 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/generators/active_record/session_migration_generator.rb 0000664 0000000 0000000 00000001362 12460531061 0032737 0 ustar 00root root 0000000 0000000 require 'rails/generators/active_record'
module ActiveRecord
module Generators
class SessionMigrationGenerator < Base
source_root File.expand_path("../templates", __FILE__)
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
activerecord-session_store-0.1.1/lib/generators/active_record/templates/ 0000775 0000000 0000000 00000000000 12460531061 0026604 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/generators/active_record/templates/migration.rb 0000664 0000000 0000000 00000000535 12460531061 0031125 0 ustar 00root root 0000000 0000000 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, :unique => true
add_index :<%= session_table_name %>, :updated_at
end
end
activerecord-session_store-0.1.1/lib/tasks/ 0000775 0000000 0000000 00000000000 12460531061 0020751 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/lib/tasks/database.rake 0000664 0000000 0000000 00000001315 12460531061 0023361 0 ustar 00root root 0000000 0000000 namespace 'db:sessions' do
desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
task :create => [:environment, 'db: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, 'db:load_config'] do
ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::SessionStore::Session.table_name}"
end
end
activerecord-session_store-0.1.1/test/ 0000775 0000000 0000000 00000000000 12460531061 0020035 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/test/action_controller_test.rb 0000664 0000000 0000000 00000021262 12460531061 0025144 0 ustar 00root root 0000000 0000000 require 'helper'
require "stringio"
class ActionControllerTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def no_session_access
head :ok
end
def set_session_value
raise "missing session!" unless session
session[:foo] = params[:foo] || "bar"
head :ok
end
def get_session_value
render :text => "foo: #{session[:foo].inspect}"
end
def get_session_id
render :text => "#{request.session_options[:id]}"
end
def call_reset_session
session[:foo]
reset_session
reset_session if params[:twice]
session[:foo] = "baz"
head :ok
end
def renew
env["rack.session.options"][:renew] = true
session[:foo] = "baz"
head :ok
end
end
def setup
ActionDispatch::Session::ActiveRecordStore.session_class.drop_table! rescue nil
ActionDispatch::Session::ActiveRecordStore.session_class.create_table!
end
%w{ session sql_bypass }.each do |class_name|
define_method("test_setting_and_getting_session_value_with_#{class_name}_store") do
with_store class_name do
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
get '/get_session_value'
assert_response :success
assert_equal 'foo: "bar"', response.body
get '/set_session_value', :foo => "baz"
assert_response :success
assert cookies['_session_id']
get '/get_session_value'
assert_response :success
assert_equal 'foo: "baz"', response.body
get '/call_reset_session'
assert_response :success
assert_not_equal [], headers['Set-Cookie']
end
end
end
define_method("test_renewing_with_#{class_name}_store") do
with_store class_name do
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
get '/renew'
assert_response :success
assert_not_equal [], headers['Set-Cookie']
end
end
end
define_method("test_#{class_name}_store_does_not_log_sql") do
with_store class_name do
with_fake_logger do
with_test_route_set do
get "/set_session_value"
get "/get_session_value"
assert_no_match(/INSERT/, fake_logger.string)
assert_no_match(/SELECT/, fake_logger.string)
end
end
end
end
end
def test_getting_nil_session_value
with_test_route_set do
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
end
end
def test_calling_reset_session_twice_does_not_raise_errors
with_test_route_set do
get '/call_reset_session', :twice => "true"
assert_response :success
get '/get_session_value'
assert_response :success
assert_equal 'foo: "baz"', response.body
end
end
def test_setting_session_value_after_session_reset
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
session_id = cookies['_session_id']
get '/call_reset_session'
assert_response :success
assert_not_equal [], headers['Set-Cookie']
get '/get_session_value'
assert_response :success
assert_equal 'foo: "baz"', response.body
get '/get_session_id'
assert_response :success
assert_not_equal session_id, response.body
end
end
def test_getting_session_value_after_session_reset
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
session_cookie = cookies.send(:hash_for)['_session_id']
get '/call_reset_session'
assert_response :success
assert_not_equal [], headers['Set-Cookie']
cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from the database"
end
end
def test_getting_from_nonexistent_session
with_test_route_set do
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
assert_nil cookies['_session_id'], "should only create session on write, not read"
end
end
def test_getting_session_id
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
session_id = cookies['_session_id']
get '/get_session_id'
assert_response :success
assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
end
end
def test_doesnt_write_session_cookie_if_session_id_is_already_exists
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
get '/get_session_value'
assert_response :success
assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
end
end
def test_prevents_session_fixation
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
get '/get_session_value'
assert_response :success
assert_equal 'foo: "bar"', response.body
session_id = cookies['_session_id']
assert session_id
reset!
get '/get_session_value', :_session_id => session_id
assert_response :success
assert_equal 'foo: nil', response.body
assert_not_equal session_id, cookies['_session_id']
end
end
def test_allows_session_fixation
with_test_route_set(:cookie_only => false) do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
get '/get_session_value'
assert_response :success
assert_equal 'foo: "bar"', response.body
session_id = cookies['_session_id']
assert session_id
reset!
get '/set_session_value', :_session_id => session_id, :foo => "baz"
assert_response :success
assert_equal session_id, cookies['_session_id']
get '/get_session_value', :_session_id => session_id
assert_response :success
assert_equal 'foo: "baz"', response.body
assert_equal session_id, cookies['_session_id']
end
end
def test_incoming_invalid_session_id_via_cookie_should_be_ignored
with_test_route_set do
open_session do |sess|
sess.cookies['_session_id'] = 'INVALID'
sess.get '/set_session_value'
new_session_id = sess.cookies['_session_id']
assert_not_equal 'INVALID', new_session_id
sess.get '/get_session_value'
new_session_id_2 = sess.cookies['_session_id']
assert_equal new_session_id, new_session_id_2
end
end
end
def test_incoming_invalid_session_id_via_parameter_should_be_ignored
with_test_route_set(:cookie_only => false) do
open_session do |sess|
sess.get '/set_session_value', :_session_id => 'INVALID'
new_session_id = sess.cookies['_session_id']
assert_not_equal 'INVALID', new_session_id
sess.get '/get_session_value'
new_session_id_2 = sess.cookies['_session_id']
assert_equal new_session_id, new_session_id_2
end
end
end
def test_session_store_with_all_domains
with_test_route_set(:domain => :all) do
get '/set_session_value'
assert_response :success
end
end
private
def with_test_route_set(options = {})
with_routing do |set|
set.draw do
get ':action', :controller => 'action_controller_test/test'
end
@app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::ActiveRecordStore, options.reverse_merge(:key => '_session_id')
middleware.delete "ActionDispatch::ShowExceptions"
end
yield
end
end
def with_store(class_name)
session_class, ActionDispatch::Session::ActiveRecordStore.session_class =
ActionDispatch::Session::ActiveRecordStore.session_class, "ActiveRecord::SessionStore::#{class_name.camelize}".constantize
yield
ensure
ActionDispatch::Session::ActiveRecordStore.session_class = session_class
end
def with_fake_logger
original_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = Logger.new(fake_logger)
yield
ensure
ActiveRecord::Base.logger = original_logger
end
def fake_logger
@fake_logger ||= StringIO.new
end
end
activerecord-session_store-0.1.1/test/generators/ 0000775 0000000 0000000 00000000000 12460531061 0022206 5 ustar 00root root 0000000 0000000 activerecord-session_store-0.1.1/test/generators/session_migration_generator_test.rb 0000664 0000000 0000000 00000002223 12460531061 0031373 0 ustar 00root root 0000000 0000000 require 'helper'
require 'rails/generators/test_case'
require 'active_record/session_store'
require 'generators/active_record/session_migration_generator'
class SessionMigrationGeneratorTest < Rails::Generators::TestCase
tests ActiveRecord::Generators::SessionMigrationGenerator
destination 'tmp'
setup :prepare_destination
def test_session_migration_with_default_name
run_generator
assert_migration "db/migrate/add_sessions_table.rb", /class AddSessionsTable < ActiveRecord::Migration/
end
def test_session_migration_with_given_name
run_generator ["create_session_table"]
assert_migration "db/migrate/create_session_table.rb", /class CreateSessionTable < ActiveRecord::Migration/
end
def test_session_migration_with_custom_table_name
ActiveRecord::SessionStore::Session.table_name = "custom_table_name"
run_generator
assert_migration "db/migrate/add_sessions_table.rb" do |migration|
assert_match(/class AddSessionsTable < ActiveRecord::Migration/, migration)
assert_match(/create_table :custom_table_name/, migration)
end
ensure
ActiveRecord::SessionStore::Session.table_name = "sessions"
end
end
activerecord-session_store-0.1.1/test/helper.rb 0000664 0000000 0000000 00000002463 12460531061 0021646 0 ustar 00root root 0000000 0000000 require 'bundler/setup'
require 'active_record'
require 'action_controller'
require 'action_dispatch'
require 'minitest/autorun'
require 'active_record/session_store'
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
module ActionDispatch
module SharedRoutes
def before_setup
@routes = SharedTestRoutes
super
end
end
end
class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
include ActionDispatch::SharedRoutes
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
middleware.use "ActionDispatch::DebugExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
middleware.use "ActionDispatch::Cookies"
middleware.use "ActionDispatch::Flash"
middleware.use "Rack::Head"
yield(middleware) if block_given?
end
end
end
class RoutedRackApp
attr_reader :routes
def initialize(routes, &blk)
@routes = routes
@stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes)
end
def call(env)
@stack.call(env)
end
end
if ActiveSupport::TestCase.respond_to?(:test_order=)
ActiveSupport::TestCase.test_order = :random
end
activerecord-session_store-0.1.1/test/session_test.rb 0000664 0000000 0000000 00000004102 12460531061 0023101 0 ustar 00root root 0000000 0000000 require 'helper'
require 'active_record/session_store'
module ActiveRecord
module SessionStore
class SessionTest < ActiveSupport::TestCase
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_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
# Force class reload, as we need to redo the meta-programming
ActiveRecord::SessionStore.send(:remove_const, :Session)
load 'active_record/session_store/session.rb'
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
activerecord-session_store-0.1.1/test/sql_bypass_test.rb 0000664 0000000 0000000 00000004142 12460531061 0023602 0 ustar 00root root 0000000 0000000 require 'helper'
require 'action_dispatch'
require 'active_record/session_store'
module ActiveRecord
module SessionStore
class SqlBypassTest < ActiveSupport::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