PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/training-web/vendor/bundle/gems/activerecord-3.2.13/lib/active_record/session_store.rb

https://bitbucket.org/ohimmelreich/asalia-training
Ruby | 360 lines | 207 code | 50 blank | 103 comment | 15 complexity | 75a089b98dd407ba990e1ff2a578e825 MD5 | raw file
  1. require 'action_dispatch/middleware/session/abstract_store'
  2. module ActiveRecord
  3. # = Active Record Session Store
  4. #
  5. # A session store backed by an Active Record class. A default class is
  6. # provided, but any object duck-typing to an Active Record Session class
  7. # with text +session_id+ and +data+ attributes is sufficient.
  8. #
  9. # The default assumes a +sessions+ tables with columns:
  10. # +id+ (numeric primary key),
  11. # +session_id+ (text, or longtext if your session data exceeds 65K), and
  12. # +data+ (text or longtext; careful if your session data exceeds 65KB).
  13. #
  14. # The +session_id+ column should always be indexed for speedy lookups.
  15. # Session data is marshaled to the +data+ column in Base64 format.
  16. # If the data you write is larger than the column's size limit,
  17. # ActionController::SessionOverflowError will be raised.
  18. #
  19. # You may configure the table name, primary key, and data column.
  20. # For example, at the end of <tt>config/application.rb</tt>:
  21. #
  22. # ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
  23. # ActiveRecord::SessionStore::Session.primary_key = 'session_id'
  24. # ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
  25. #
  26. # Note that setting the primary key to the +session_id+ frees you from
  27. # having a separate +id+ column if you don't want it. However, you must
  28. # set <tt>session.model.id = session.session_id</tt> by hand! A before filter
  29. # on ApplicationController is a good place.
  30. #
  31. # Since the default class is a simple Active Record, you get timestamps
  32. # for free if you add +created_at+ and +updated_at+ datetime columns to
  33. # the +sessions+ table, making periodic session expiration a snap.
  34. #
  35. # You may provide your own session class implementation, whether a
  36. # feature-packed Active Record or a bare-metal high-performance SQL
  37. # store, by setting
  38. #
  39. # ActiveRecord::SessionStore.session_class = MySessionClass
  40. #
  41. # You must implement these methods:
  42. #
  43. # self.find_by_session_id(session_id)
  44. # initialize(hash_of_session_id_and_data, options_hash = {})
  45. # attr_reader :session_id
  46. # attr_accessor :data
  47. # save
  48. # destroy
  49. #
  50. # The example SqlBypass class is a generic SQL session store. You may
  51. # use it as a basis for high-performance database-specific stores.
  52. class SessionStore < ActionDispatch::Session::AbstractStore
  53. module ClassMethods # :nodoc:
  54. def marshal(data)
  55. ::Base64.encode64(Marshal.dump(data)) if data
  56. end
  57. def unmarshal(data)
  58. Marshal.load(::Base64.decode64(data)) if data
  59. end
  60. def drop_table!
  61. connection.schema_cache.clear_table_cache!(table_name)
  62. connection.drop_table table_name
  63. end
  64. def create_table!
  65. connection.schema_cache.clear_table_cache!(table_name)
  66. connection.create_table(table_name) do |t|
  67. t.string session_id_column, :limit => 255
  68. t.text data_column_name
  69. end
  70. connection.add_index table_name, session_id_column, :unique => true
  71. end
  72. end
  73. # The default Active Record class.
  74. class Session < ActiveRecord::Base
  75. extend ClassMethods
  76. ##
  77. # :singleton-method:
  78. # Customizable data column name. Defaults to 'data'.
  79. cattr_accessor :data_column_name
  80. self.data_column_name = 'data'
  81. attr_accessible :session_id, :data, :marshaled_data
  82. before_save :marshal_data!
  83. before_save :raise_on_session_data_overflow!
  84. class << self
  85. def data_column_size_limit
  86. @data_column_size_limit ||= columns_hash[data_column_name].limit
  87. end
  88. # Hook to set up sessid compatibility.
  89. def find_by_session_id(session_id)
  90. setup_sessid_compatibility!
  91. find_by_session_id(session_id)
  92. end
  93. private
  94. def session_id_column
  95. 'session_id'
  96. end
  97. # Compatibility with tables using sessid instead of session_id.
  98. def setup_sessid_compatibility!
  99. # Reset column info since it may be stale.
  100. reset_column_information
  101. if columns_hash['sessid']
  102. def self.find_by_session_id(*args)
  103. find_by_sessid(*args)
  104. end
  105. define_method(:session_id) { sessid }
  106. define_method(:session_id=) { |session_id| self.sessid = session_id }
  107. else
  108. class << self; remove_method :find_by_session_id; end
  109. def self.find_by_session_id(session_id)
  110. find :first, :conditions => {:session_id=>session_id}
  111. end
  112. end
  113. end
  114. end
  115. def initialize(attributes = nil, options = {})
  116. @data = nil
  117. super
  118. end
  119. # Lazy-unmarshal session state.
  120. def data
  121. @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
  122. end
  123. attr_writer :data
  124. # Has the session been loaded yet?
  125. def loaded?
  126. @data
  127. end
  128. private
  129. def marshal_data!
  130. return false unless loaded?
  131. write_attribute(@@data_column_name, self.class.marshal(data))
  132. end
  133. # Ensures that the data about to be stored in the database is not
  134. # larger than the data storage column. Raises
  135. # ActionController::SessionOverflowError.
  136. def raise_on_session_data_overflow!
  137. return false unless loaded?
  138. limit = self.class.data_column_size_limit
  139. if limit and read_attribute(@@data_column_name).size > limit
  140. raise ActionController::SessionOverflowError
  141. end
  142. end
  143. end
  144. # A barebones session store which duck-types with the default session
  145. # store but bypasses Active Record and issues SQL directly. This is
  146. # an example session model class meant as a basis for your own classes.
  147. #
  148. # The database connection, table name, and session id and data columns
  149. # are configurable class attributes. Marshaling and unmarshaling
  150. # are implemented as class methods that you may override. By default,
  151. # marshaling data is
  152. #
  153. # ::Base64.encode64(Marshal.dump(data))
  154. #
  155. # and unmarshaling data is
  156. #
  157. # Marshal.load(::Base64.decode64(data))
  158. #
  159. # This marshaling behavior is intended to store the widest range of
  160. # binary session data in a +text+ column. For higher performance,
  161. # store in a +blob+ column instead and forgo the Base64 encoding.
  162. class SqlBypass
  163. extend ClassMethods
  164. ##
  165. # :singleton-method:
  166. # The table name defaults to 'sessions'.
  167. cattr_accessor :table_name
  168. @@table_name = 'sessions'
  169. ##
  170. # :singleton-method:
  171. # The session id field defaults to 'session_id'.
  172. cattr_accessor :session_id_column
  173. @@session_id_column = 'session_id'
  174. ##
  175. # :singleton-method:
  176. # The data field defaults to 'data'.
  177. cattr_accessor :data_column
  178. @@data_column = 'data'
  179. class << self
  180. alias :data_column_name :data_column
  181. # Use the ActiveRecord::Base.connection by default.
  182. attr_writer :connection
  183. # Use the ActiveRecord::Base.connection_pool by default.
  184. attr_writer :connection_pool
  185. def connection
  186. @connection ||= ActiveRecord::Base.connection
  187. end
  188. def connection_pool
  189. @connection_pool ||= ActiveRecord::Base.connection_pool
  190. end
  191. # Look up a session by id and unmarshal its data if found.
  192. def find_by_session_id(session_id)
  193. if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
  194. new(:session_id => session_id, :marshaled_data => record['data'])
  195. end
  196. end
  197. end
  198. delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self
  199. attr_reader :session_id, :new_record
  200. alias :new_record? :new_record
  201. attr_writer :data
  202. # Look for normal and marshaled data, self.find_by_session_id's way of
  203. # telling us to postpone unmarshaling until the data is requested.
  204. # We need to handle a normal data attribute in case of a new record.
  205. def initialize(attributes)
  206. @session_id = attributes[:session_id]
  207. @data = attributes[:data]
  208. @marshaled_data = attributes[:marshaled_data]
  209. @new_record = @marshaled_data.nil?
  210. end
  211. # Lazy-unmarshal session state.
  212. def data
  213. unless @data
  214. if @marshaled_data
  215. @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
  216. else
  217. @data = {}
  218. end
  219. end
  220. @data
  221. end
  222. def loaded?
  223. @data
  224. end
  225. def save
  226. return false unless loaded?
  227. marshaled_data = self.class.marshal(data)
  228. connect = connection
  229. if @new_record
  230. @new_record = false
  231. connect.update <<-end_sql, 'Create session'
  232. INSERT INTO #{table_name} (
  233. #{connect.quote_column_name(session_id_column)},
  234. #{connect.quote_column_name(data_column)} )
  235. VALUES (
  236. #{connect.quote(session_id)},
  237. #{connect.quote(marshaled_data)} )
  238. end_sql
  239. else
  240. connect.update <<-end_sql, 'Update session'
  241. UPDATE #{table_name}
  242. SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)}
  243. WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
  244. end_sql
  245. end
  246. end
  247. def destroy
  248. return if @new_record
  249. connect = connection
  250. connect.delete <<-end_sql, 'Destroy session'
  251. DELETE FROM #{table_name}
  252. WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
  253. end_sql
  254. end
  255. end
  256. # The class used for session storage. Defaults to
  257. # ActiveRecord::SessionStore::Session
  258. cattr_accessor :session_class
  259. self.session_class = Session
  260. SESSION_RECORD_KEY = 'rack.session.record'
  261. ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY
  262. private
  263. def get_session(env, sid)
  264. Base.silence do
  265. unless sid and session = @@session_class.find_by_session_id(sid)
  266. # If the sid was nil or if there is no pre-existing session under the sid,
  267. # force the generation of a new sid and associate a new session associated with the new sid
  268. sid = generate_sid
  269. session = @@session_class.new(:session_id => sid, :data => {})
  270. end
  271. env[SESSION_RECORD_KEY] = session
  272. [sid, session.data]
  273. end
  274. end
  275. def set_session(env, sid, session_data, options)
  276. Base.silence do
  277. record = get_session_model(env, sid)
  278. record.data = session_data
  279. return false unless record.save
  280. session_data = record.data
  281. if session_data && session_data.respond_to?(:each_value)
  282. session_data.each_value do |obj|
  283. obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
  284. end
  285. end
  286. end
  287. sid
  288. end
  289. def destroy_session(env, session_id, options)
  290. if sid = current_session_id(env)
  291. Base.silence do
  292. get_session_model(env, sid).destroy
  293. env[SESSION_RECORD_KEY] = nil
  294. end
  295. end
  296. generate_sid unless options[:drop]
  297. end
  298. def get_session_model(env, sid)
  299. if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
  300. env[SESSION_RECORD_KEY] = find_session(sid)
  301. else
  302. env[SESSION_RECORD_KEY] ||= find_session(sid)
  303. end
  304. end
  305. def find_session(id)
  306. @@session_class.find_by_session_id(id) ||
  307. @@session_class.new(:session_id => id, :data => {})
  308. end
  309. end
  310. end