pax_global_header00006660000000000000000000000064124605310610014510gustar00rootroot0000000000000052 comment=1cc0daf77d542045da4f6a43cba9ce04c27453dd activerecord-session_store-0.1.1/000077500000000000000000000000001246053106100170565ustar00rootroot00000000000000activerecord-session_store-0.1.1/.gitignore000066400000000000000000000002241246053106100210440ustar00rootroot00000000000000*.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.yml000066400000000000000000000010321246053106100211630ustar00rootroot00000000000000language: 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/Appraisals000066400000000000000000000005511246053106100211010ustar00rootroot00000000000000%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/Gemfile000066400000000000000000000000471246053106100203520ustar00rootroot00000000000000source 'https://rubygems.org' gemspec activerecord-session_store-0.1.1/LICENSE000066400000000000000000000020711246053106100200630ustar00rootroot00000000000000Copyright (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.md000066400000000000000000000047261246053106100203460ustar00rootroot00000000000000Active 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/Rakefile000066400000000000000000000003141246053106100205210ustar00rootroot00000000000000#!/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.gemspec000066400000000000000000000015461246053106100260000ustar00rootroot00000000000000Gem::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/000077500000000000000000000000001246053106100206515ustar00rootroot00000000000000activerecord-session_store-0.1.1/gemfiles/4.0.gemfile000066400000000000000000000002661246053106100225100ustar00rootroot00000000000000# 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.gemfile000066400000000000000000000002661246053106100225110ustar00rootroot00000000000000# 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.gemfile000066400000000000000000000002661246053106100225120ustar00rootroot00000000000000# 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.gemfile000066400000000000000000000003551246053106100231120ustar00rootroot00000000000000# 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/000077500000000000000000000000001246053106100176245ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/action_dispatch/000077500000000000000000000000001246053106100227605ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/action_dispatch/session/000077500000000000000000000000001246053106100244435ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/action_dispatch/session/active_record_store.rb000066400000000000000000000113251246053106100310170ustar00rootroot00000000000000require "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/000077500000000000000000000000001246053106100224355ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/active_record/session_store.rb000066400000000000000000000022411246053106100256600ustar00rootroot00000000000000require '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/000077500000000000000000000000001246053106100253345ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/active_record/session_store/extension/000077500000000000000000000000001246053106100273505ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/active_record/session_store/extension/logger_silencer.rb000066400000000000000000000037411246053106100330450ustar00rootroot00000000000000require "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.rb000066400000000000000000000002501246053106100273070ustar00rootroot00000000000000require '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.rb000066400000000000000000000055571246053106100273600ustar00rootroot00000000000000require "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.rb000066400000000000000000000110421246053106100300370ustar00rootroot00000000000000require "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/000077500000000000000000000000001246053106100222765ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/activerecord/session_store.rb000066400000000000000000000000461246053106100255220ustar00rootroot00000000000000require 'active_record/session_store' activerecord-session_store-0.1.1/lib/generators/000077500000000000000000000000001246053106100217755ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/generators/active_record/000077500000000000000000000000001246053106100246065ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/generators/active_record/session_migration_generator.rb000066400000000000000000000013621246053106100327370ustar00rootroot00000000000000require '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/000077500000000000000000000000001246053106100266045ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/generators/active_record/templates/migration.rb000066400000000000000000000005351246053106100311250ustar00rootroot00000000000000class <%= 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/000077500000000000000000000000001246053106100207515ustar00rootroot00000000000000activerecord-session_store-0.1.1/lib/tasks/database.rake000066400000000000000000000013151246053106100233610ustar00rootroot00000000000000namespace '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/000077500000000000000000000000001246053106100200355ustar00rootroot00000000000000activerecord-session_store-0.1.1/test/action_controller_test.rb000066400000000000000000000212621246053106100251440ustar00rootroot00000000000000require '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/000077500000000000000000000000001246053106100222065ustar00rootroot00000000000000activerecord-session_store-0.1.1/test/generators/session_migration_generator_test.rb000066400000000000000000000022231246053106100313730ustar00rootroot00000000000000require '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.rb000066400000000000000000000024631246053106100216460ustar00rootroot00000000000000require '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.rb000066400000000000000000000041021246053106100231010ustar00rootroot00000000000000require '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.rb000066400000000000000000000041421246053106100236020ustar00rootroot00000000000000require '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