trick_serial 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +128 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/cabar.yml +17 -0
- data/lib/trick_serial.rb +3 -0
- data/lib/trick_serial/serializer.rb +427 -0
- data/lib/trick_serial/serializer/cgi_session.rb +239 -0
- data/lib/trick_serial/serializer/rails.rb +88 -0
- data/lib/trick_serial/serializer/simple.rb +129 -0
- data/spec/spec_helper.rb +76 -0
- data/spec/trick_serial/cgi_session_spec.rb +169 -0
- data/spec/trick_serial/serializer_simple_spec.rb +268 -0
- data/spec/trick_serial/serializer_spec.rb +275 -0
- metadata +207 -0
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'trick_serial/serializer'
|
2
|
+
|
3
|
+
module TrickSerial
|
4
|
+
class Serializer
|
5
|
+
# Support for ::CGI::Session stores.
|
6
|
+
#
|
7
|
+
# Stores for use with CGI::Session and TrickSerial::Serializer::CgiSession::Store
|
8
|
+
# must implement #_data and #_data= to get access to the underlying Hash structure.
|
9
|
+
#
|
10
|
+
module CgiSession
|
11
|
+
def self.activate!
|
12
|
+
require 'cgi/session'
|
13
|
+
require 'cgi/session/pstore'
|
14
|
+
|
15
|
+
::CGI::Session.send(:include, SessionSerializer)
|
16
|
+
|
17
|
+
if defined? ::CGI::Session::FileStore
|
18
|
+
::CGI::Session::FileStore.send(:include, FileStoreSerializer)
|
19
|
+
end
|
20
|
+
if defined? ::CGI::Session::PStore
|
21
|
+
::CGI::Session::PStore.send(:include, PStoreSerializer)
|
22
|
+
end
|
23
|
+
if defined? ::CGI::Session::MemCacheStore
|
24
|
+
::CGI::Session::MemCacheStore.send(:include, MemCacheStoreSerializer)
|
25
|
+
end
|
26
|
+
if defined? ::CGI::Session::CassandraStore
|
27
|
+
::CGI::Session::CassandraStore.send(:include, CassandraStoreSerializer)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Defines a Session store Decorator that interjects TrickSerial::Serializer
|
32
|
+
# inside #restore, #update, etc.
|
33
|
+
#
|
34
|
+
# Example:
|
35
|
+
#
|
36
|
+
# cgi = CGI.new("html4")
|
37
|
+
# session = CGI::Session.new(
|
38
|
+
# 'database_manager' => TrickSerial::Serializer::CgiSession::Store, # The Decorator.
|
39
|
+
# 'TrickSerial.database_manager' => CGI::Session::PStore, # Actual store Class.
|
40
|
+
# # Options for PStore instance:
|
41
|
+
# 'tmpdir' => '/tmp/mysessions',
|
42
|
+
# 'session_key' => 'mykey',
|
43
|
+
# ...
|
44
|
+
# )
|
45
|
+
#
|
46
|
+
class Store
|
47
|
+
attr_accessor :logger, :logger_level
|
48
|
+
|
49
|
+
# Options:
|
50
|
+
#
|
51
|
+
# 'TrickSerial.database_manager': the actual session Store class (e.g.: CGI::Session::PStore).
|
52
|
+
# 'TrickSerial.dbman': an actual session store instance.
|
53
|
+
# 'TrickSerial.serializer': a clonable instance of TrickSerial::Serializer.
|
54
|
+
# 'TrickSerial.logger': a Log4r object.
|
55
|
+
# 'TrickSerial.logger_level': a Symbol for the logger level (e.g: :debug)
|
56
|
+
#
|
57
|
+
# The remaining options are passed to the actual Store specified by
|
58
|
+
# :'TrickSerial.database_manager'.
|
59
|
+
def initialize(session, option={})
|
60
|
+
@session = session
|
61
|
+
@option = option
|
62
|
+
@dbman_cls = option.delete('TrickSerial.database_manager') ||
|
63
|
+
(raise "#{self} options did not specify TrickSerial.database_manager: #{option.inspect}")
|
64
|
+
@dbman = option.delete('TrickSerial.dbman')
|
65
|
+
# option['new_session'] = true
|
66
|
+
@option['database_manager'] = @dbman_cls
|
67
|
+
@serializer = option.delete('TrickSerial.serializer')
|
68
|
+
@logger = option.delete('TrickSerial.logger')
|
69
|
+
@logger_level = option.delete('TrickSerial.logger_level') || :debug
|
70
|
+
_log { "creating #{self} for #{option.inspect}" }
|
71
|
+
end
|
72
|
+
|
73
|
+
def _dbman
|
74
|
+
@dbman ||=
|
75
|
+
begin
|
76
|
+
# Fool decorated Store.
|
77
|
+
save = @session.new_session
|
78
|
+
@session.new_session = true
|
79
|
+
# debugger
|
80
|
+
@dbman_cls.new(@session, @option)
|
81
|
+
ensure
|
82
|
+
@session.new_session = save
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def _make_serializer
|
87
|
+
(@serializer || TrickSerial::Serializer.default).dup
|
88
|
+
end
|
89
|
+
|
90
|
+
def restore
|
91
|
+
_log { "#{self} restore" }
|
92
|
+
_dbman.restore
|
93
|
+
_dbman.decode_with_trick_serial_serializer! if _dbman.respond_to?(:decode_with_trick_serial_serializer!)
|
94
|
+
_dbman._data
|
95
|
+
end
|
96
|
+
|
97
|
+
def update
|
98
|
+
_log { "#{self} update" }
|
99
|
+
serializer = _make_serializer
|
100
|
+
data_save = _dbman._data
|
101
|
+
_dbman._data = serializer.encode(_dbman._data)
|
102
|
+
_dbman.encode_with_trick_serial_serializer! if _dbman.respond_to?(:encode_with_trick_serial_serializer!)
|
103
|
+
# debugger
|
104
|
+
_dbman.update
|
105
|
+
ensure
|
106
|
+
_dbman._data = data_save
|
107
|
+
end
|
108
|
+
|
109
|
+
def close
|
110
|
+
_log { "#{self} close" }
|
111
|
+
serializer = _make_serializer
|
112
|
+
data_save = _dbman._data
|
113
|
+
_dbman._data = serializer.encode(_dbman._data)
|
114
|
+
_dbman.encode_with_trick_serial_serializer! if _dbman.respond_to?(:encode_with_trick_serial_serializer!)
|
115
|
+
_dbman.close
|
116
|
+
ensure
|
117
|
+
_dbman._data = data_save
|
118
|
+
end
|
119
|
+
|
120
|
+
def delete
|
121
|
+
_log { "#{self} close" }
|
122
|
+
_dbman.delete
|
123
|
+
end
|
124
|
+
|
125
|
+
def _log msg = nil
|
126
|
+
msg ||= yield if block_given?
|
127
|
+
if msg && @logger
|
128
|
+
@logger.send(@logger_level, msg)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Hacks to get access to Session.new_session.
|
134
|
+
module SessionSerializer
|
135
|
+
attr_writer :session_id, :new_session
|
136
|
+
end
|
137
|
+
|
138
|
+
# Defines common mixin for interjecting TrickSerial::Serializer before
|
139
|
+
# SessionStore#update saves its data.
|
140
|
+
module SessionStoreDataHook
|
141
|
+
def self.included target
|
142
|
+
super
|
143
|
+
target.extend(ModuleMethods)
|
144
|
+
end
|
145
|
+
|
146
|
+
module ModuleMethods
|
147
|
+
def included target
|
148
|
+
super
|
149
|
+
=begin
|
150
|
+
target.class_eval do
|
151
|
+
alias :restore_without_trick_serial_serializer :restore
|
152
|
+
alias :restore :restore_with_trick_serial_serializer
|
153
|
+
alias :update_without_trick_serial_serializer :update
|
154
|
+
alias :update :update_with_trick_serial_serializer
|
155
|
+
end
|
156
|
+
=end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
=begin
|
161
|
+
def restore_with_trick_serial_serializer
|
162
|
+
restore_without_trick_serial_serializer
|
163
|
+
decode_with_trick_serial_serializer!
|
164
|
+
_data
|
165
|
+
end
|
166
|
+
=end
|
167
|
+
|
168
|
+
def encode_with_trick_serial_serializer!
|
169
|
+
end
|
170
|
+
|
171
|
+
def decode_with_trick_serial_serializer!
|
172
|
+
end
|
173
|
+
|
174
|
+
=begin
|
175
|
+
# Clones TrickSerial::Serializer.default.
|
176
|
+
# Encodes the session store's "data".
|
177
|
+
# Replaces the session store's data with the encoded data.
|
178
|
+
# Call original #update.
|
179
|
+
# Restores old session store's "data".
|
180
|
+
def update_with_trick_serial_serializer
|
181
|
+
serializer = TrickSerial::Serializer.default.dup
|
182
|
+
data_save = self._data
|
183
|
+
self._data = serializer.encode(self._data)
|
184
|
+
encode_with_trick_serial_serializer!
|
185
|
+
update_without_trick_serial_serializer
|
186
|
+
ensure
|
187
|
+
self._data = data_save
|
188
|
+
end
|
189
|
+
=end
|
190
|
+
end
|
191
|
+
|
192
|
+
# FileStore can only handle String => String data.
|
193
|
+
# Use Marshal and Base64 to further encode it.
|
194
|
+
module FileStoreSerializer
|
195
|
+
include SessionStoreDataHook
|
196
|
+
def self.included target
|
197
|
+
super
|
198
|
+
require 'base64'
|
199
|
+
end
|
200
|
+
|
201
|
+
def _data; @hash; end
|
202
|
+
def _data= x; @hash = x; end
|
203
|
+
|
204
|
+
PHONY_KEY = '_'.freeze
|
205
|
+
|
206
|
+
def encode_with_trick_serial_serializer!
|
207
|
+
# $stderr.puts "#{self} encode <= @hash=#{@hash.inspect}"
|
208
|
+
@hash &&= { PHONY_KEY => ::Base64.encode64(Marshal.dump(@hash)).chomp! }
|
209
|
+
# $stderr.puts "#{self} encode => @hash=#{@hash.inspect}"
|
210
|
+
end
|
211
|
+
def decode_with_trick_serial_serializer!
|
212
|
+
# $stderr.puts "#{self} decode <= @hash=#{@hash.inspect}"
|
213
|
+
@hash &&= (v = @hash[PHONY_KEY]) ? Marshal.load(::Base64.decode64(v)) : { }
|
214
|
+
# $stderr.puts "#{self} decode => @hash=#{@hash.inspect}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
module PStoreSerializer
|
219
|
+
include SessionStoreDataHook
|
220
|
+
def _data; @hash; end
|
221
|
+
def _data= x; @hash = x; end
|
222
|
+
end
|
223
|
+
|
224
|
+
module MemCacheStoreSerializer
|
225
|
+
include SessionStoreDataHook
|
226
|
+
def _data; @session_data; end
|
227
|
+
def _data=x; @session_data = x; end
|
228
|
+
end
|
229
|
+
|
230
|
+
module CassandraStoreSerializer
|
231
|
+
include SessionStoreDataHook
|
232
|
+
def _data; @session_data; end
|
233
|
+
def _data=x; @session_data = x; end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'trick_serial/serializer'
|
2
|
+
|
3
|
+
module TrickSerial
|
4
|
+
class Serializer
|
5
|
+
module Rails
|
6
|
+
def self.activate!
|
7
|
+
rails_version =
|
8
|
+
(Rails.version rescue nil) ||
|
9
|
+
(RAILS_VERSION rescue nil) || :unknown
|
10
|
+
case rails_version
|
11
|
+
when /^3\./
|
12
|
+
V3
|
13
|
+
when /^1\.2\./
|
14
|
+
V12
|
15
|
+
else
|
16
|
+
raise ArgumentError, "#{self}: Unknown Rails version: #{rails_version.inspect}"
|
17
|
+
end.activate!
|
18
|
+
end
|
19
|
+
|
20
|
+
# Rails 3 support.
|
21
|
+
module V3
|
22
|
+
def self.activate!
|
23
|
+
=begin
|
24
|
+
if defined? ::ActiveRecord::Session
|
25
|
+
::ActiveRecord::Sesson.send(:include, ActiveRecordSessionSerializer)
|
26
|
+
end
|
27
|
+
=end
|
28
|
+
if defined? ::ActionDispatch::Session::MemCacheStore
|
29
|
+
::ActionDispatch::Session::MemCacheStore.send(:include, SessionStoreSerializer)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module SessionStoreSerializer
|
34
|
+
def self.included target
|
35
|
+
super
|
36
|
+
target.class_eval do
|
37
|
+
alias :get_session_without_trick_serial_serializer :get_session
|
38
|
+
alias :get_session :get_session_with_trick_serial_serializer
|
39
|
+
alias :set_session_without_trick_serial_serializer :set_session
|
40
|
+
alias :set_session :get_session_with_trick_serial_serializer
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_session_with_trick_serial_serializer env, sid
|
45
|
+
result = get_session_without_trick_serial_serializer env, sid
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_session_with_trick_serial_serializer env, sid, session_data
|
50
|
+
serializer = (env[:'TrickSerial::Serializer.instance'] || TrickSerial::Serializer.default).dup
|
51
|
+
session_data = serializer.encode(session_data)
|
52
|
+
set_session_without_trick_serial_serializer env, sid, session_data
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module ActiveRecordSessionSerializer
|
58
|
+
def self.included target
|
59
|
+
super
|
60
|
+
target.class_eval do
|
61
|
+
alias :marshal_data_without_trick_serial_serializer! :marshal_data!
|
62
|
+
alias :marshal_data! :marshal_data_with_trick_serial_serializer!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def marshal_data_with_trick_serial_serializer!
|
67
|
+
save_data = @data
|
68
|
+
if loaded?
|
69
|
+
serializer = TrickSerial::Serializer.default.dup
|
70
|
+
@data = serializer.encode(@data)
|
71
|
+
end
|
72
|
+
marshal_data_without_trick_serial_serializer!
|
73
|
+
ensure
|
74
|
+
@data = save_data
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Rails 1.2 support.
|
80
|
+
module V12
|
81
|
+
def self.activate!
|
82
|
+
require 'trick_serial/serializer/cgi_session'
|
83
|
+
TrickSerial::Serializer::CgiSession.activate!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
|
2
|
+
require 'trick_serial/serializer'
|
3
|
+
|
4
|
+
module TrickSerial
|
5
|
+
class Serializer
|
6
|
+
# Simple, non-swizzling coder.
|
7
|
+
# This requires encode and decode operations.
|
8
|
+
# Array and Hash are not extended to support swizzling.
|
9
|
+
# Ivar swizzling is not used.
|
10
|
+
class Simple < self
|
11
|
+
def _encode! x
|
12
|
+
# pp [ :_encode!, x.class, x ]
|
13
|
+
|
14
|
+
case x
|
15
|
+
when ObjectProxy
|
16
|
+
x
|
17
|
+
|
18
|
+
when Array
|
19
|
+
if o = @visited[x.object_id]
|
20
|
+
return o.first
|
21
|
+
end
|
22
|
+
extended = false
|
23
|
+
o = x
|
24
|
+
x = x.dup if @copy
|
25
|
+
@visited[o.object_id] = [ x, o ]
|
26
|
+
x.map! do | v |
|
27
|
+
_encode! v
|
28
|
+
end
|
29
|
+
|
30
|
+
when Hash
|
31
|
+
if o = @visited[x.object_id]
|
32
|
+
return o.first
|
33
|
+
end
|
34
|
+
extended = false
|
35
|
+
o = x
|
36
|
+
x = x.dup if @copy
|
37
|
+
@visited[o.object_id] = [ x, o ]
|
38
|
+
x.keys.to_a.each do | k |
|
39
|
+
x[k] = _encode!(x[k])
|
40
|
+
end
|
41
|
+
|
42
|
+
when *@proxyable
|
43
|
+
if proxy = @object_to_proxy_map[x.object_id]
|
44
|
+
return proxy.first
|
45
|
+
end
|
46
|
+
# debugger
|
47
|
+
o = x
|
48
|
+
proxy_cls = nil
|
49
|
+
if class_option = self._class_option(x)
|
50
|
+
proxy_cls = class_option[:proxy_class]
|
51
|
+
# Deeply encode instance vars?
|
52
|
+
if ivs = class_option[:instance_vars]
|
53
|
+
ivs = x.instance_variables if ivs == true
|
54
|
+
x = x.dup if @copy
|
55
|
+
@object_to_proxy_map[o.object_id] = [ x, o ]
|
56
|
+
ivs.each do | ivar |
|
57
|
+
v = x.instance_variable_get(ivar)
|
58
|
+
v = _encode!(v)
|
59
|
+
x.instance_variable_set(ivar, v)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
x = _make_proxy o, x, proxy_cls
|
65
|
+
end
|
66
|
+
|
67
|
+
x
|
68
|
+
end # def
|
69
|
+
|
70
|
+
def _decode! x
|
71
|
+
case x
|
72
|
+
when ObjectProxy
|
73
|
+
x = x.object
|
74
|
+
|
75
|
+
when Array
|
76
|
+
if o = @visited[x.object_id]
|
77
|
+
return o.first
|
78
|
+
end
|
79
|
+
extended = false
|
80
|
+
o = x
|
81
|
+
x = x.dup if @copy
|
82
|
+
@visited[o.object_id] = [ x, o ]
|
83
|
+
x.map! do | v |
|
84
|
+
_decode! v
|
85
|
+
end
|
86
|
+
|
87
|
+
when Hash
|
88
|
+
if o = @visited[x.object_id]
|
89
|
+
return o.first
|
90
|
+
end
|
91
|
+
extended = false
|
92
|
+
o = x
|
93
|
+
x = x.dup if @copy
|
94
|
+
@visited[o.object_id] = [ x, o ]
|
95
|
+
x.keys.to_a.each do | k |
|
96
|
+
x[k] = _decode!(x[k])
|
97
|
+
end
|
98
|
+
|
99
|
+
when *@proxyable
|
100
|
+
if proxy = @object_to_proxy_map[x.object_id]
|
101
|
+
return proxy.first
|
102
|
+
end
|
103
|
+
# debugger
|
104
|
+
o = x
|
105
|
+
if class_option = _class_option(x)
|
106
|
+
# Deeply encode instance vars?
|
107
|
+
if ivs = class_option[:instance_vars]
|
108
|
+
ivs = x.instance_variables if ivs == true
|
109
|
+
x = x.dup if @copy
|
110
|
+
@object_to_proxy_map[o.object_id] = [ x, o ]
|
111
|
+
ivs.each do | ivar |
|
112
|
+
v = x.instance_variable_get(ivar)
|
113
|
+
# $stderr.puts "\n#{x.class} #{x.object_id} ivar #{ivar} #{v.inspect}" if @debug
|
114
|
+
v = _decode!(v)
|
115
|
+
x.instance_variable_set(ivar, v)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
@object_to_proxy_map[o.object_id] ||= [ x, o ]
|
120
|
+
end # case
|
121
|
+
|
122
|
+
x
|
123
|
+
end # def
|
124
|
+
|
125
|
+
end # class
|
126
|
+
end # class
|
127
|
+
end # module
|
128
|
+
|
129
|
+
|