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

  ActiveSupport::Base64.encode64(Marshal.dump(data))

and unmarshaling data is

  Marshal.load(ActiveSupport::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.

Methods
Attributes
[W] data
[R] session_id
Public Class methods
connection()
     # File activerecord/lib/active_record/session_store.rb, line 182
182:         def connection
183:           @@connection ||= ActiveRecord::Base.connection
184:         end
create_table!()
     # File activerecord/lib/active_record/session_store.rb, line 201
201:         def create_table!
202:           @@connection.execute "CREATE TABLE \#{table_name} (\nid INTEGER PRIMARY KEY,\n\#{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,\n\#{@@connection.quote_column_name(data_column)} TEXT\n)\n"
203:         end
drop_table!()
     # File activerecord/lib/active_record/session_store.rb, line 212
212:         def drop_table!
213:           @@connection.execute "DROP TABLE #{table_name}"
214:         end
find_by_session_id(session_id)

Look up a session by id and unmarshal its data if found.

     # File activerecord/lib/active_record/session_store.rb, line 187
187:         def find_by_session_id(session_id)
188:           if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
189:             new(:session_id => session_id, :marshaled_data => record['data'])
190:           end
191:         end
marshal(data)
     # File activerecord/lib/active_record/session_store.rb, line 193
193:         def marshal(data)
194:           ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
195:         end
new(attributes)

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.

     # File activerecord/lib/active_record/session_store.rb, line 223
223:       def initialize(attributes)
224:         @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
225:         @new_record = @marshaled_data.nil?
226:       end
unmarshal(data)
     # File activerecord/lib/active_record/session_store.rb, line 197
197:         def unmarshal(data)
198:           Marshal.load(ActiveSupport::Base64.decode64(data)) if data
199:         end
Public Instance methods
data()

Lazy-unmarshal session state.

     # File activerecord/lib/active_record/session_store.rb, line 233
233:       def data
234:         unless @data
235:           if @marshaled_data
236:             @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
237:           else
238:             @data = {}
239:           end
240:         end
241:         @data
242:       end
destroy()
     # File activerecord/lib/active_record/session_store.rb, line 273
273:       def destroy
274:         unless @new_record
275:           @@connection.delete "DELETE FROM \#{@@table_name}\nWHERE \#{@@connection.quote_column_name(@@session_id_column)}=\#{@@connection.quote(session_id)}\n", 'Destroy session'
276:         end
277:       end
loaded?()
     # File activerecord/lib/active_record/session_store.rb, line 244
244:       def loaded?
245:         !!@data
246:       end
new_record?()
     # File activerecord/lib/active_record/session_store.rb, line 228
228:       def new_record?
229:         @new_record
230:       end
save()
     # File activerecord/lib/active_record/session_store.rb, line 248
248:       def save
249:         return false if !loaded?
250:         marshaled_data = self.class.marshal(data)
251: 
252:         if @new_record
253:           @new_record = false
254:           @@connection.update "INSERT INTO \#{@@table_name} (\n\#{@@connection.quote_column_name(@@session_id_column)},\n\#{@@connection.quote_column_name(@@data_column)} )\nVALUES (\n\#{@@connection.quote(session_id)},\n\#{@@connection.quote(marshaled_data)} )\n", 'Create session'
255:         else
256:           @@connection.update "UPDATE \#{@@table_name}\nSET \#{@@connection.quote_column_name(@@data_column)}=\#{@@connection.quote(marshaled_data)}\nWHERE \#{@@connection.quote_column_name(@@session_id_column)}=\#{@@connection.quote(session_id)}\n", 'Update session'
257:         end
258:       end