trick_serial 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|