pax_global_header00006660000000000000000000000064127163510750014521gustar00rootroot0000000000000052 comment=39e9fb5f8355e6e062ff80850a4939312c8ac28a activerecord-session_store-1.0.0/000077500000000000000000000000001271635107500170665ustar00rootroot00000000000000activerecord-session_store-1.0.0/.gitignore000066400000000000000000000002241271635107500210540ustar00rootroot00000000000000*.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-1.0.0/.travis.yml000066400000000000000000000014231271635107500211770ustar00rootroot00000000000000language: ruby sudo: false cache: bundler rvm: - 1.9.3 - 2.0.0 - 2.1.10 - 2.2.5 - 2.3.1 - ruby-head gemfile: - gemfiles/rails_4.0.gemfile - gemfiles/rails_4.1.gemfile - gemfiles/rails_4.2.gemfile - gemfiles/rails_5.0.gemfile - gemfiles/rails_edge.gemfile matrix: fast_finish: true exclude: - rvm: 1.9.3 gemfile: gemfiles/rails_5.0.gemfile - rvm: 2.0.0 gemfile: gemfiles/rails_5.0.gemfile - rvm: 2.1.10 gemfile: gemfiles/rails_5.0.gemfile - rvm: 1.9.3 gemfile: gemfiles/rails_edge.gemfile - rvm: 2.0.0 gemfile: gemfiles/rails_edge.gemfile - rvm: 2.1.10 gemfile: gemfiles/rails_edge.gemfile allow_failures: - rvm: ruby-head - gemfile: gemfiles/rails_edge.gemfile notifications: email: false activerecord-session_store-1.0.0/Appraisals000066400000000000000000000013771271635107500211200ustar00rootroot00000000000000[ '4.0', '4.1', '4.2' ].each do |ver| appraise "rails-#{ver}" do gem 'actionpack', "~> #{ver}.0" gem 'activerecord', "~> #{ver}.0" gem 'railties', "~> #{ver}.0" gem 'rack', '~> 1.5' end end appraise 'rails-5.0' do gem 'actionpack', '>= 5.0.0.alpha', '< 5.1' gem 'activerecord', '>= 5.0.0.alpha', '< 5.1' gem 'railties', '>= 5.0.0.alpha', '< 5.1' gem 'rack', '>= 2.0.0.alpha', '< 3' end appraise "rails-edge" do git 'https://github.com/rails/rails.git', :branch => 'master' do gem 'actionpack' gem 'activerecord' gem 'railties' end gem 'rack', :git => 'https://github.com/rack/rack.git', :branch => 'master' gem 'arel', :git => 'https://github.com/rails/arel.git', :branch => 'master' end activerecord-session_store-1.0.0/CONTRIBUTING.md000066400000000000000000000056371271635107500213320ustar00rootroot00000000000000Contributing to Active Record Session Store ===================== [![Build Status](https://travis-ci.org/rails/activerecord-session_store.svg?branch=master)](https://travis-ci.org/rails/activerecord-session_store) Active Record Session Store is work of [many contributors](https://github.com/rails/activerecord-session_store/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/rails/activerecord-session_store/pulls), [propose features and discuss issues](https://github.com/rails/activerecord-session_store/issues). #### Fork the Project Fork the [project on Github](https://github.com/rails/activerecord-session_store) and check out your copy. ``` git clone https://github.com/contributor/activerecord-session_store.git cd activerecord-session_store git remote add upstream https://github.com/rails/activerecord-session_store.git ``` #### Create a Topic Branch Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. ``` git checkout master git pull upstream master git checkout -b my-feature-branch ``` #### Bundle Install and Test Ensure that you can build the project and run tests. ``` bundle install bundle exec rake test ``` #### Write Tests Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [test](test). We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. #### Write Code Implement your feature or bug fix. Make sure that `bundle exec rake test` completes without errors. #### Write Documentation Document any external behavior in the [README](README.md). #### Commit Changes Make sure git knows your name and email address: ``` git config --global user.name "Your Name" git config --global user.email "contributor@example.com" ``` Writing good commit logs is important. A commit log should describe what changed and why. ``` git add ... git commit ``` #### Push ``` git push origin my-feature-branch ``` #### Make a Pull Request Go to your fork at `https://github.com/[your github username]/activerecord-session_store` and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. #### Rebase If you've been working on a change for a while, rebase with upstream/master. ``` git fetch upstream git rebase upstream/master git push origin my-feature-branch -f ``` #### Check on Your Pull Request Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. #### Be Patient It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang in there! #### Thank You Please do know that we really appreciate and value your time and work. We love you, really. activerecord-session_store-1.0.0/Gemfile000066400000000000000000000000471271635107500203620ustar00rootroot00000000000000source 'https://rubygems.org' gemspec activerecord-session_store-1.0.0/MIT-LICENSE000066400000000000000000000020761271635107500205270ustar00rootroot00000000000000Copyright (c) 2012-2016 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-1.0.0/README.md000066400000000000000000000070551271635107500203540ustar00rootroot00000000000000Active 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 Rails.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, data column, and serializer type. 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' ActiveRecord::SessionStore::Session.serializer = :json ``` 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. The serializer may be one of `marshal`, `json`, or `hybrid`. `marshal` is the default and uses the built-in Marshal methods coupled with Base64 encoding. `json` does what it says on the tin, using the `parse()` and `generate()` methods of the JSON module. `hybrid` will read either type but write as JSON. 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. Please note that you will need to manually include the silencer module to your custom logger if you are using a logger other than `Logger` and `Syslog::Logger` and their subclasses: ```ruby MyLogger.send :include, ActiveRecord::SessionStore::Extension::LoggerSilencer ``` This silencer is being used to silence the logger and not leaking private information into the log, and it is required for security reason. ## Contributing to Active Record Session Store Active Record Session Store is work of many contributors. You're encouraged to submit pull requests, propose features and discuss issues. See [CONTRIBUTING](CONTRIBUTING.md). ## License Active Record Session Store is released under the [MIT License](MIT-LICENSE). activerecord-session_store-1.0.0/Rakefile000066400000000000000000000003441271635107500205340ustar00rootroot00000000000000#!/usr/bin/env rake require 'bundler/setup' 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-1.0.0/activerecord-session_store.gemspec000066400000000000000000000021031271635107500257760ustar00rootroot00000000000000require "./lib/active_record/session_store/version" Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'activerecord-session_store' s.version = ActiveRecord::SessionStore::VERSION 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 = 'https://github.com/rails/activerecord-session_store' 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', '< 5.1') s.add_dependency('actionpack', '>= 4.0', '< 5.1') s.add_dependency('railties', '>= 4.0', '< 5.1') s.add_dependency('rack', '>= 1.5.2', '< 3') s.add_dependency('multi_json', '~> 1.11', '>= 1.11.2') s.add_development_dependency('sqlite3') s.add_development_dependency('appraisal', '~> 2.1.0') end activerecord-session_store-1.0.0/gemfiles/000077500000000000000000000000001271635107500206615ustar00rootroot00000000000000activerecord-session_store-1.0.0/gemfiles/rails_4.0.gemfile000066400000000000000000000003131271635107500237030ustar00rootroot00000000000000# 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" gem "rack", "~> 1.5" gemspec :path => "../" activerecord-session_store-1.0.0/gemfiles/rails_4.1.gemfile000066400000000000000000000003131271635107500237040ustar00rootroot00000000000000# 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" gem "rack", "~> 1.5" gemspec :path => "../" activerecord-session_store-1.0.0/gemfiles/rails_4.2.gemfile000066400000000000000000000003131271635107500237050ustar00rootroot00000000000000# 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" gem "rack", "~> 1.5" gemspec :path => "../" activerecord-session_store-1.0.0/gemfiles/rails_5.0.gemfile000066400000000000000000000004071271635107500237100ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "actionpack", ">= 5.0.0.alpha", "< 5.1" gem "activerecord", ">= 5.0.0.alpha", "< 5.1" gem "railties", ">= 5.0.0.alpha", "< 5.1" gem "rack", ">= 2.0.0.alpha", "< 3" gemspec :path => "../" activerecord-session_store-1.0.0/gemfiles/rails_edge.gemfile000066400000000000000000000005671271635107500243210ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" git "https://github.com/rails/rails.git", :branch => "master" do gem "actionpack" gem "activerecord" gem "railties" end gem "rack", :git => "https://github.com/rack/rack.git", :branch => "master" gem "arel", :git => "https://github.com/rails/arel.git", :branch => "master" gemspec :path => "../" activerecord-session_store-1.0.0/lib/000077500000000000000000000000001271635107500176345ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/action_dispatch/000077500000000000000000000000001271635107500227705ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/action_dispatch/session/000077500000000000000000000000001271635107500244535ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/action_dispatch/session/active_record_store.rb000066400000000000000000000131531271635107500310300ustar00rootroot00000000000000require "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' if Rack.const_defined?(:RACK_SESSION_OPTIONS) ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS else ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY end private def get_session(request, sid) logger.silence_logger 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 request.env[SESSION_RECORD_KEY] = session [sid, session.data] end end def write_session(request, sid, session_data, options) logger.silence_logger do record = get_session_model(request, 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 delete_session(request, session_id, options) logger.silence_logger do if sid = current_session_id(request) if model = get_session_model(request, sid) data = model.data model.destroy end end request.env[SESSION_RECORD_KEY] = nil unless options[:drop] new_sid = generate_sid if options[:renew] new_model = @@session_class.new(:session_id => new_sid, :data => data) new_model.save request.env[SESSION_RECORD_KEY] = new_model end new_sid end end end def get_session_model(request, id) logger.silence_logger do model = @@session_class.find_by_session_id(id) if !model id = generate_sid model = @@session_class.new(:session_id => id, :data => {}) model.save end if request.env[ENV_SESSION_OPTIONS_KEY][:id].nil? request.env[SESSION_RECORD_KEY] = model else request.env[SESSION_RECORD_KEY] ||= model end model end end def find_session(request, id) model = get_session_model(request, id) [model.session_id, model.data] end def logger ActiveRecord::Base.logger || ActiveRecord::SessionStore::NilLogger end end end end if ActiveRecord::VERSION::MAJOR == 4 require 'action_dispatch/session/legacy_support' ActionDispatch::Session::ActiveRecordStore.send(:include, ActionDispatch::Session::LegacySupport) end activerecord-session_store-1.0.0/lib/action_dispatch/session/legacy_support.rb000066400000000000000000000027331271635107500300450ustar00rootroot00000000000000module ActionDispatch module Session module LegacySupport EnvWrapper = Struct.new(:env) def self.included(klass) [ :get_session, :get_session_model, :write_session, :delete_session, :find_session ].each do |m| klass.send(:alias_method, "#{m}_rails5".to_sym, m) klass.send(:remove_method, m) end end def get_session(env, sid) request = EnvWrapper.new(env) get_session_rails5(request, sid) end def set_session(env, sid, session_data, options) request = EnvWrapper.new(env) write_session_rails5(request, sid, session_data, options) end def destroy_session(env, session_id, options) request = EnvWrapper.new(env) if sid = current_session_id(request.env) get_session_model(request, sid).destroy request.env[self.class::SESSION_RECORD_KEY] = nil end generate_sid unless options[:drop] end def get_session_model(request, sid) if request.env[self.class::ENV_SESSION_OPTIONS_KEY][:id].nil? request.env[self.class::SESSION_RECORD_KEY] = find_session(sid) else request.env[self.class::SESSION_RECORD_KEY] ||= find_session(sid) end end def find_session(id) self.class.session_class.find_by_session_id(id) || self.class.session_class.new(:session_id => id, :data => {}) end end end end activerecord-session_store-1.0.0/lib/active_record/000077500000000000000000000000001271635107500224455ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/active_record/session_store.rb000066400000000000000000000061061271635107500256740ustar00rootroot00000000000000require 'active_record/session_store/version' require 'action_dispatch/session/active_record_store' require "active_record/session_store/extension/logger_silencer" require 'active_support/core_ext/hash/keys' require 'multi_json' module ActiveRecord module SessionStore module ClassMethods # :nodoc: mattr_accessor :serializer def serialize(data) serializer_class.dump(data) if data end def deserialize(data) serializer_class.load(data) if data end def drop_table! if connection.schema_cache.respond_to?(:clear_data_source_cache!) connection.schema_cache.clear_data_source_cache!(table_name) else connection.schema_cache.clear_table_cache!(table_name) end connection.drop_table table_name end def create_table! if connection.schema_cache.respond_to?(:clear_data_source_cache!) connection.schema_cache.clear_data_source_cache!(table_name) else connection.schema_cache.clear_table_cache!(table_name) end 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 def serializer_class case self.serializer when :marshal, nil then MarshalSerializer when :json then JsonSerializer when :hybrid then HybridSerializer else self.serializer end end # Use Marshal with Base64 encoding class MarshalSerializer def self.load(value) Marshal.load(::Base64.decode64(value)) end def self.dump(value) ::Base64.encode64(Marshal.dump(value)) end end # Uses built-in JSON library to encode/decode session class JsonSerializer def self.load(value) hash = MultiJson.load(value) hash.is_a?(Hash) ? hash.with_indifferent_access[:value] : hash end def self.dump(value) MultiJson.dump(value: value) end end # Transparently migrates existing session values from Marshal to JSON class HybridSerializer < JsonSerializer MARSHAL_SIGNATURE = 'BAh'.freeze def self.load(value) if needs_migration?(value) Marshal.load(::Base64.decode64(value)) else super end end def self.needs_migration?(value) value.start_with?(MARSHAL_SIGNATURE) end 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 begin require "syslog/logger" Syslog::Logger.send :include, ActiveRecord::SessionStore::Extension::LoggerSilencer rescue LoadError; end activerecord-session_store-1.0.0/lib/active_record/session_store/000077500000000000000000000000001271635107500253445ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/active_record/session_store/extension/000077500000000000000000000000001271635107500273605ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/active_record/session_store/extension/logger_silencer.rb000066400000000000000000000042441271635107500330540ustar00rootroot00000000000000require "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 :level_without_threadsafety, :level alias_method :level, :level_with_threadsafety alias_method :add_without_threadsafety, :add alias_method :add, :add_with_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) if (defined?(@logdev) && @logdev.nil?) || (severity || UNKNOWN) < level true else add_without_threadsafety(severity, message, progname, &block) end end # Silences the logger for the duration of the block. def silence_logger(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_logger yield end end end end activerecord-session_store-1.0.0/lib/active_record/session_store/railtie.rb000066400000000000000000000003151271635107500273210ustar00rootroot00000000000000require 'rails/railtie' module ActiveRecord module SessionStore class Railtie < Rails::Railtie rake_tasks { load File.expand_path("../../../tasks/database.rake", __FILE__) } end end end activerecord-session_store-1.0.0/lib/active_record/session_store/session.rb000066400000000000000000000056341271635107500273640ustar00rootroot00000000000000require "active_support/core_ext/module/attribute_accessors" require "thread" module ActiveRecord module SessionStore # The default Active Record class. class Session < ActiveRecord::Base extend ClassMethods SEMAPHORE = Mutex.new ## # :singleton-method: # Customizable data column name. Defaults to 'data'. cattr_accessor :data_column_name self.data_column_name = 'data' before_save :serialize_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) SEMAPHORE.synchronize { 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(*) @data = nil super end # Lazy-deserialize session state. def data @data ||= self.class.deserialize(read_attribute(@@data_column_name)) || {} end attr_writer :data # Has the session been loaded yet? def loaded? @data end private def serialize_data! return false unless loaded? write_attribute(@@data_column_name, self.class.serialize(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-1.0.0/lib/active_record/session_store/sql_bypass.rb000066400000000000000000000110741271635107500300540ustar00rootroot00000000000000require "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. Serializing and deserializeing # are implemented as class methods that you may override. By default, # serializing data is # # ::Base64.encode64(Marshal.dump(data)) # # and deserializing data is # # Marshal.load(::Base64.decode64(data)) # # This serializing 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 deserialize 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, :serialized_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 serialized data, self.find_by_session_id's way of # telling us to postpone deserializing 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] @serialized_data = attributes[:serialized_data] @new_record = @serialized_data.nil? end # Returns true if the record is persisted, i.e. it's not a new record def persisted? !@new_record end # Lazy-deserialize session state. def data unless @data if @serialized_data @data, @serialized_data = self.class.deserialize(@serialized_data) || {}, nil else @data = {} end end @data end def loaded? @data end def save return false unless loaded? serialized_data = self.class.serialize(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(serialized_data)} ) end_sql else connect.update <<-end_sql, 'Update session' UPDATE #{table_name} SET #{connect.quote_column_name(data_column)}=#{connect.quote(serialized_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-1.0.0/lib/active_record/session_store/version.rb000066400000000000000000000001121271635107500273500ustar00rootroot00000000000000module ActiveRecord module SessionStore VERSION = '1.0.0' end end activerecord-session_store-1.0.0/lib/activerecord/000077500000000000000000000000001271635107500223065ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/activerecord/session_store.rb000066400000000000000000000000461271635107500255320ustar00rootroot00000000000000require 'active_record/session_store' activerecord-session_store-1.0.0/lib/generators/000077500000000000000000000000001271635107500220055ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/generators/active_record/000077500000000000000000000000001271635107500246165ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/generators/active_record/session_migration_generator.rb000066400000000000000000000013621271635107500327470ustar00rootroot00000000000000require '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-1.0.0/lib/generators/active_record/templates/000077500000000000000000000000001271635107500266145ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/generators/active_record/templates/migration.rb000066400000000000000000000005351271635107500311350ustar00rootroot00000000000000class <%= 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-1.0.0/lib/tasks/000077500000000000000000000000001271635107500207615ustar00rootroot00000000000000activerecord-session_store-1.0.0/lib/tasks/database.rake000066400000000000000000000017741271635107500234020ustar00rootroot00000000000000namespace '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 desc "Trim old sessions from the table (default: > 30 days)" task :trim => [:environment, 'db:load_config'] do cutoff_period = (ENV['SESSION_DAYS_TRIM_THRESHOLD'] || 30).to_i.days.ago ActiveRecord::SessionStore::Session. where("updated_at < ?", cutoff_period). delete_all end end activerecord-session_store-1.0.0/test/000077500000000000000000000000001271635107500200455ustar00rootroot00000000000000activerecord-session_store-1.0.0/test/action_controller_test.rb000066400000000000000000000202421271635107500251510ustar00rootroot00000000000000require 'helper' class ActionControllerTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base protect_from_forgery 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 if ActiveRecord::VERSION::MAJOR == 4 render :text => "foo: #{session[:foo].inspect}" else render :plain => "foo: #{session[:foo].inspect}" end end def get_session_id if ActiveRecord::VERSION::MAJOR == 4 render :text => "#{request.session.id}" else render :plain => "#{request.session.id}" end end def call_reset_session session[:foo] reset_session reset_session if params[:twice] session[:foo] = "baz" head :ok end def renew request.env["rack.session.options"][:renew] = true 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 if ActiveRecord::VERSION::MAJOR == 4 get '/set_session_value', :foo => "baz" else get '/set_session_value', :params => { :foo => "baz" } end 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 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 if ActiveRecord::VERSION::MAJOR == 4 get '/call_reset_session', :twice => "true" else get '/call_reset_session', :params => { :twice => "true" } end 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! if ActiveRecord::VERSION::MAJOR == 4 get '/get_session_value', :_session_id => session_id else get '/get_session_value', :params => { :_session_id => session_id } end 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! if ActiveRecord::VERSION::MAJOR == 4 get '/set_session_value', :_session_id => session_id, :foo => "baz" else get '/set_session_value', :params => { :_session_id => session_id, :foo => "baz" } end assert_response :success assert_equal session_id, cookies['_session_id'] get '/get_session_value', :params => { :_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| if ActiveRecord::VERSION::MAJOR == 4 sess.get '/set_session_value', :_session_id => 'INVALID' else sess.get '/set_session_value', :params => { :_session_id => 'INVALID' } end 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 end activerecord-session_store-1.0.0/test/destroy_session_test.rb000066400000000000000000000030341271635107500246650ustar00rootroot00000000000000if ActiveRecord::VERSION::MAJOR > 4 require 'action_dispatch/middleware/session/abstract_store' module ActionDispatch class Request class DestroySessionTest < ActiveSupport::TestCase attr_reader :req def setup @req = ActionDispatch::Request.empty ActionDispatch::Session::ActiveRecordStore.session_class.drop_table! rescue nil ActionDispatch::Session::ActiveRecordStore.session_class.create_table! end def record_key ActionDispatch::Session::ActiveRecordStore::SESSION_RECORD_KEY end def test_destroy_without_renew s = Session.create(store, req, { :renew => false }) s['set_something_so_it_loads'] = true session_model = req.env[record_key] session_model.update_attributes(:data => {'rails' => 'ftw'}) s.destroy renewed_session_model = req.env[record_key] assert_equal nil, renewed_session_model.data['rails'] end def test_destroy_with_renew s = Session.create(store, req, { :renew => true }) s['set_something_so_it_loads'] = true session_model = req.env[record_key] session_model.update_attributes(:data => {'rails' => 'ftw'}) s.destroy renewed_session_model = req.env[record_key] assert_equal 'ftw', renewed_session_model.data['rails'] end private def store ActionDispatch::Session::ActiveRecordStore.new(req.env, {}) end end end end endactiverecord-session_store-1.0.0/test/generators/000077500000000000000000000000001271635107500222165ustar00rootroot00000000000000activerecord-session_store-1.0.0/test/generators/session_migration_generator_test.rb000066400000000000000000000022231271635107500314030ustar00rootroot00000000000000require '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-1.0.0/test/helper.rb000066400000000000000000000041341271635107500216530ustar00rootroot00000000000000require '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::Cookies middleware.use ActionDispatch::Flash middleware.use Rack::Head yield(middleware) if block_given? end end private def with_test_route_set(options = {}) controller_namespace = self.class.to_s.underscore with_routing do |set| set.draw do get ':action', :controller => "#{controller_namespace}/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 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-1.0.0/test/logger_silencer_test.rb000066400000000000000000000037651271635107500246070ustar00rootroot00000000000000require 'helper' require "stringio" class LoggerSilencerTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base def set_session_value raise "missing session!" unless session session[:foo] = params[:foo] || "bar" head :ok end def get_session_value if ActiveRecord::VERSION::MAJOR == 4 render :text => "foo: #{session[:foo].inspect}" else render :plain => "foo: #{session[:foo].inspect}" end end end def setup session_class = ActiveRecord::SessionStore::Session session_class.drop_table! rescue nil session_class.create_table! ActionDispatch::Session::ActiveRecordStore.session_class = session_class end %w{ session sql_bypass }.each do |class_name| 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_log_silencer_with_logger_not_raise_exception with_logger Logger.new(Tempfile.new("tempfile")) do with_test_route_set do get "/set_session_value" end end end begin require "syslogger/logger" def test_log_silencer_with_syslog_logger_not_raise_exception with_logger Syslog::Logger.new("ar_session_store_test") do with_test_route_set do get "/set_session_value" end end end rescue LoadError; end private def with_logger(logger) original_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = logger yield ensure ActiveRecord::Base.logger = original_logger end def with_fake_logger(&block) with_logger(Logger.new(fake_logger), &block) end def fake_logger @fake_logger ||= StringIO.new end end activerecord-session_store-1.0.0/test/session_test.rb000066400000000000000000000106361271635107500231220ustar00rootroot00000000000000require 'helper' require 'active_record/session_store' require 'active_support/core_ext/hash/keys' 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) ActiveRecord::SessionStore::Session.serializer = :json 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_json_serialization Session.create_table! ActiveRecord::SessionStore::Session.serializer = :json s = session_klass.create!(:data => 'world', :session_id => '7') sessions = ActiveRecord::Base.connection.execute("SELECT * FROM #{Session.table_name}") data = Session.deserialize(sessions[0][Session.data_column_name]) assert_equal s.data, data end def test_hybrid_serialization Session.create_table! # Star with marshal, which will serialize with Marshal ActiveRecord::SessionStore::Session.serializer = :marshal s1 = session_klass.create!(:data => 'world', :session_id => '1') # Switch to hybrid, which will serialize as JSON ActiveRecord::SessionStore::Session.serializer = :hybrid s2 = session_klass.create!(:data => 'world', :session_id => '2') # Check that first was serialized with Marshal and second as JSON sessions = ActiveRecord::Base.connection.execute("SELECT * FROM #{Session.table_name}") assert_equal ::Base64.encode64(Marshal.dump(s1.data)), sessions[0][Session.data_column_name] assert_equal s2.data, Session.deserialize(sessions[1][Session.data_column_name]) end def test_hybrid_deserialization Session.create_table! # Star with marshal, which will serialize with Marshal ActiveRecord::SessionStore::Session.serializer = :marshal s = session_klass.create!(:data => 'world', :session_id => '1') # Switch to hybrid, which will deserialize with Marshal if needed ActiveRecord::SessionStore::Session.serializer = :hybrid # Check that it was serialized with Marshal, sessions = ActiveRecord::Base.connection.execute("SELECT * FROM #{Session.table_name}") assert_equal sessions[0][Session.data_column_name], ::Base64.encode64(Marshal.dump(s.data)) # deserializes properly, session = Session.find_by_session_id(s.id) assert_equal s.data, session.data # and reserializes as JSON session.save sessions = ActiveRecord::Base.connection.execute("SELECT * FROM #{Session.table_name}") assert_equal s.data,Session.deserialize(sessions[0][Session.data_column_name]) 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-1.0.0/test/sql_bypass_test.rb000066400000000000000000000041421271635107500236120ustar00rootroot00000000000000require '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 activerecord-session_store-1.0.0/test/tasks/000077500000000000000000000000001271635107500211725ustar00rootroot00000000000000activerecord-session_store-1.0.0/test/tasks/database_rake_test.rb000066400000000000000000000031661271635107500253320ustar00rootroot00000000000000require 'helper' require 'rake' module ActiveRecord module SessionStore class DatabaseRakeTest < ActiveSupport::TestCase class AddTimestampsToSession < ActiveRecord::Migration self.verbose = false def change add_column Session.table_name, :created_at, :datetime add_column Session.table_name, :updated_at, :datetime end end def setup Session.drop_table! if Session.table_exists? Session.create_table! AddTimestampsToSession.new.exec_migration(ActiveRecord::Base.connection, :up) Session.connection.schema_cache.clear! Session.reset_column_information Rake.application.rake_require "tasks/database" Rake::Task.define_task(:environment) Rake::Task.define_task("db:load_config") end def teardown Session.drop_table! if Session.table_exists? Session.connection.schema_cache.clear! Session.reset_column_information end def test_trim_task cutoff_period = 30.days.ago Session.create!(data: "obsolete") do |session| session.updated_at = 5.minutes.until(cutoff_period) end recent_session = Session.create!(data: "recent") do |session| session.updated_at = 5.minutes.since(cutoff_period) end Rake.application.invoke_task 'db:sessions:trim' old_session_count = Session.where("updated_at < ?", cutoff_period).count retained_session = Session.find(recent_session.id) assert_equal 0, old_session_count assert_equal retained_session, recent_session end end end end