Class: TrickSerial::Serializer

Inherits:
Object
  • Object
show all
Defined in:
lib/trick_serial/serializer.rb,
lib/trick_serial/serializer/rails.rb,
lib/trick_serial/serializer/simple.rb,
lib/trick_serial/serializer/cgi_session.rb

Overview

Serializes objects using proxies for classes defined in #proxy_class_map. Instances of the keys in #proxy_class_map are replaced by proxies if the proxy class returns true for #can_proxy?(instance).

Container classes are extended with ProxySwizzling to automatically replace the Proxy objects with their #object when accessed.

The result of this class does not require explicit decoding. However, this particular class only works with serializers that can handle Hash and Array objects extended with Modules.

See Serializer::Simple for support for simpler encode/decode behavior without ProxySwizzling support.

Direct Known Subclasses

Simple

Defined Under Namespace

Modules: CgiSession, ObjectProxy, ProxySwizzling, ProxySwizzlingArray, ProxySwizzlingHash, Rails Classes: ActiveRecordProxy, ProxySwizzlingIvar, Simple

Constant Summary collapse

@@class_option_map =
nil
@@default =
nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSerializer

Returns a new instance of Serializer.



45
46
47
48
49
# File 'lib/trick_serial/serializer.rb', line 45

def initialize
  @class_option_map ||= @@class_option_map || EMPTY_Hash
  @enabled = true
  @debug = 0
end

Instance Attribute Details

#class_option_mapObject

Returns the value of attribute class_option_map.



27
28
29
# File 'lib/trick_serial/serializer.rb', line 27

def class_option_map
  @class_option_map
end

#debugObject

Returns the value of attribute debug.



24
25
26
# File 'lib/trick_serial/serializer.rb', line 24

def debug
  @debug
end

#enabledObject

Boolean or Proc.



21
22
23
# File 'lib/trick_serial/serializer.rb', line 21

def enabled
  @enabled
end

#loggerObject

Returns the value of attribute logger.



23
24
25
# File 'lib/trick_serial/serializer.rb', line 23

def logger
  @logger
end

#logger_levelObject

Returns the value of attribute logger_level.



23
24
25
# File 'lib/trick_serial/serializer.rb', line 23

def logger_level
  @logger_level
end

#rootObject (readonly)

Returns the value of attribute root.



25
26
27
# File 'lib/trick_serial/serializer.rb', line 25

def root
  @root
end

#verboseObject

Returns the value of attribute verbose.



24
25
26
# File 'lib/trick_serial/serializer.rb', line 24

def verbose
  @verbose
end

Class Method Details

.class_option_mapObject



29
30
31
# File 'lib/trick_serial/serializer.rb', line 29

def self.class_option_map 
  @@class_option_map
end

.class_option_map=(x) ⇒ Object



32
33
34
# File 'lib/trick_serial/serializer.rb', line 32

def self.class_option_map= x
  @@class_option_map = x
end

.defaultObject



37
38
39
40
# File 'lib/trick_serial/serializer.rb', line 37

def self.default
  Thread.current[:'TrickSerial::Serializer.default'] ||
    @@default
end

.default=(x) ⇒ Object



41
42
43
# File 'lib/trick_serial/serializer.rb', line 41

def self.default= x
  @@default = x
end

Instance Method Details

#_class_option(x) ⇒ Object

def



243
244
245
246
247
248
249
250
# File 'lib/trick_serial/serializer.rb', line 243

def _class_option x
  (@class_option_cache[x.class] ||=
   [
    x.class.ancestors.
    map { |c| @class_option_map[c] }.
    find { |c| c }
   ]).first
end

#_copy_with_extensions(x) ⇒ Object



265
266
267
268
269
270
271
272
273
274
# File 'lib/trick_serial/serializer.rb', line 265

def _copy_with_extensions x
  if @copy 
    o = x.dup
    (_extended_by(x) - _extended_by(o)).reverse_each do | m |
      o.extend(m)
    end rescue nil # :symbol.extend(m) => TypeError: can't define singleton
    x = o
  end
  x
end

#_encode!(x) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/trick_serial/serializer.rb', line 126

def _encode! x
  # pp [ :_encode!, x.class, x.object_id, x.to_s ] if @debug >= 1

  case x
  when *@do_not_traverse
    # NOTHING

  when ObjectProxy
    # NOTHING

  when Struct
    if o = @visited[x.object_id]
      return o.first
    end
    o = x
    x = _copy_with_extensions(x)
    @visited[o.object_id] = [ x, o ]
    x.class.members.each do | m |
      v = x.send(m)
      v = _encode! v
      x.send(:"#{m}=", v)
    end

  when OpenStruct
    if o = @visited[x.object_id]
      return o.first
    end
    o = x
    x = _copy_with_extensions(x)
    @visited[o.object_id] = [ x, o ]
    extended = false
    t = x.instance_variable_get("@table")
    t.keys.to_a.each do | k |
      v = t._get_without_trick_serial(k)
      v = _encode! v
      if ! extended && ObjectProxy === v
        t.extend ProxySwizzlingHash
        extended = true
      end
      x.send(:"#{k}=", v)
    end

  when Array
    if o = @visited[x.object_id]
      return o.first
    end
    o = x
    x = _copy_with_extensions(x)
    @visited[o.object_id] = [ x, o ]
    extended = false
    x.size.times do | i |
      v = x._get_without_trick_serial(i)
      v = _encode! v
      if ! extended && ObjectProxy === v
        x.extend ProxySwizzlingArray
        extended = true
      end
      x[i] = v
    end

  when Hash
    if o = @visited[x.object_id]
      return o.first
    end
    o = x
    x = _copy_with_extensions(x)
    @visited[o.object_id] = [ x, o ]
    extended = false
    x.keys.to_a.each do | k |
      # pp [ :Hash_key, k ] if @debug >= 1
      v = x[k] = _encode!(x._get_without_trick_serial(k))
      if ! extended && ObjectProxy === v
        x.extend ProxySwizzlingHash
        extended = true
      end
    end

  when *@proxyable
    if proxy = @object_to_proxy_map[x.object_id]
      # if @debug >= 1
      #   o = proxy.first
      #   $stderr.puts "  #{x.class} #{x.object_id} ==>> (#{o.class} #{o.object_id})"
      # end
      return proxy.first
    end
    # debugger

    o = x
    proxy_x = proxy_cls = nil
    if class_option = _class_option(x)
      proxy_cls = class_option[:proxy_class]
      # Deeply encode instance vars?
      if ivs = class_option[:instance_vars]
        ivs = x.instance_variables if ivs == true
        x = _copy_with_extensions x
        proxy_x = _make_proxy o, x, proxy_cls
        ivs.each do | ivar |
          v = x.instance_variable_get(ivar)
          v = _encode!(v)
          if ObjectProxy === v
            ivar.freeze
            v = ProxySwizzlingIvar.new(x, ivar, v)
          end
          x.instance_variable_set(ivar, v)
        end
      else
        proxy_x = _make_proxy o, x, proxy_cls
      end
    end
    x = proxy_x if proxy_cls
  end

  # pp [ :"_encode!=>", x.class, x.object_id, x.to_s ] if @debug >= 1

  x
end

#_extended_by(x) ⇒ Object

This is similar to Rails Object#extended_by.



277
278
279
280
281
# File 'lib/trick_serial/serializer.rb', line 277

def _extended_by x
  # Note: if Symbol === x this happens:
  # #<TypeError: no virtual class for Symbol>
  (class << x; ancestors; end) rescue [ ]
end

#_log(msg = nil) ⇒ Object



283
284
285
286
287
288
# File 'lib/trick_serial/serializer.rb', line 283

def _log msg = nil
  if @logger
    msg ||= yield if block_given?
    @logger.send(@logger_level, msg) if msg
  end
end

#_make_proxy(o, x, proxy_cls) ⇒ Object

Create a proxy for x for original object o. x may be a dup of o.



254
255
256
257
258
259
260
261
262
263
# File 'lib/trick_serial/serializer.rb', line 254

def _make_proxy o, x, proxy_cls
  # Can the object x be proxied for the original object o?
  # i.e. does it have an id?
  if proxy_cls && proxy_cls.can_proxy?(x)
    x = proxy_cls.new(x, self)
    _log { "created proxy #{x} for #{o.class} #{o.id}" }
  end
  @object_to_proxy_map[o.object_id] = [ x, o ]
  x
end

#_prepare(x) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/trick_serial/serializer.rb', line 94

def _prepare x
  return x unless enabled?
  proxyable
  @root = x
  @visited = { }
  @object_to_proxy_map = { }
  # debugger
  yield
ensure
  @visited.clear if @visited
  @object_to_proxy_map.clear if @object_to_proxy_map
  @copy =
  @visited =
    @object_to_proxy_map = 
    @root = nil
end

#decode(x) ⇒ Object

Same as #decode!, but copies Array and Hash structures recursively. Does not copy structure if #enabled? is false. Only implemented by some subclasses.



80
81
82
83
84
# File 'lib/trick_serial/serializer.rb', line 80

def decode x
  return x unless enabled?
  @copy = true
  decode! x
end

#decode!(x) ⇒ Object

Decodes using #proxy_class_map in-place. Only implemented by some subclasses.



88
89
90
91
92
# File 'lib/trick_serial/serializer.rb', line 88

def decode! x
  _prepare x do 
    _decode! x
  end
end

#enabled?Boolean

Returns:

  • (Boolean)


51
52
53
54
55
56
57
58
# File 'lib/trick_serial/serializer.rb', line 51

def enabled?
  case @enabled
  when Proc
    @enabled.call
  else
    @enabled
  end
end

#encode(x) ⇒ Object

Same as #encode!, but copies Array and Hash structures recursively. Does not copy structure if #enabled? is false.



63
64
65
66
67
# File 'lib/trick_serial/serializer.rb', line 63

def encode x
  return x unless enabled?
  @copy = true
  encode! x
end

#encode!(x) ⇒ Object

Encodes using #proxy_class_map in-place.



70
71
72
73
74
# File 'lib/trick_serial/serializer.rb', line 70

def encode! x
  _prepare x do
    _encode! x
  end
end

#proxyableObject

Returns a list of Modules that are proxable based on the configuration.



112
113
114
115
116
117
118
119
120
# File 'lib/trick_serial/serializer.rb', line 112

def proxyable
  unless @proxyable
    @proxyable = @class_option_map.keys.select{|cls| ! @class_option_map[cls][:do_not_traverse]}
    @do_not_traverse ||= @class_option_map.keys.select{|cls| @class_option_map[cls][:do_not_traverse]}
    @class_option_cache ||= { }
    @proxyable.freeze
  end
  @proxyable
end