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.



43
44
45
46
47
# File 'lib/trick_serial/serializer.rb', line 43

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.



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

def class_option_map
  @class_option_map
end

#debugObject

Returns the value of attribute debug.



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

def debug
  @debug
end

#enabledObject

Boolean or Proc.



19
20
21
# File 'lib/trick_serial/serializer.rb', line 19

def enabled
  @enabled
end

#loggerObject

Returns the value of attribute logger.



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

def logger
  @logger
end

#logger_levelObject

Returns the value of attribute logger_level.



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

def logger_level
  @logger_level
end

#rootObject (readonly)

Returns the value of attribute root.



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

def root
  @root
end

#verboseObject

Returns the value of attribute verbose.



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

def verbose
  @verbose
end

Class Method Details

.class_option_mapObject



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

def self.class_option_map 
  @@class_option_map
end

.class_option_map=(x) ⇒ Object



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

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

.defaultObject



35
36
37
38
# File 'lib/trick_serial/serializer.rb', line 35

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

.default=(x) ⇒ Object



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

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

Instance Method Details

#_class_option(x) ⇒ Object

def



235
236
237
238
239
240
241
242
# File 'lib/trick_serial/serializer.rb', line 235

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



257
258
259
260
261
262
263
264
265
266
# File 'lib/trick_serial/serializer.rb', line 257

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



124
125
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
# File 'lib/trick_serial/serializer.rb', line 124

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 = 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 ]
    x = o
    t = x.instance_variable_get("@table")
    t.each do | k, v |
      t[k] = _encode! 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.map! do | v |
      v = _encode! v
      if ! extended && ObjectProxy === v
        x.extend ProxySwizzlingArray
        extended = true
      end
      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[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.



269
270
271
272
273
# File 'lib/trick_serial/serializer.rb', line 269

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



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

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.



246
247
248
249
250
251
252
253
254
255
# File 'lib/trick_serial/serializer.rb', line 246

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



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

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.



78
79
80
81
82
# File 'lib/trick_serial/serializer.rb', line 78

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.



86
87
88
89
90
# File 'lib/trick_serial/serializer.rb', line 86

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

#enabled?Boolean

Returns:

  • (Boolean)


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

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.



61
62
63
64
65
# File 'lib/trick_serial/serializer.rb', line 61

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

#encode!(x) ⇒ Object

Encodes using #proxy_class_map in-place.



68
69
70
71
72
# File 'lib/trick_serial/serializer.rb', line 68

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

#proxyableObject

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



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

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]} << ObjectProxy
    @class_option_cache ||= { }
    @proxyable.freeze
  end
  @proxyable
end