class TrickSerial::Serializer
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.
Attributes
Boolean or Proc.
Public Class Methods
# File lib/trick_serial/serializer.rb, line 29 def self.class_option_map @@class_option_map end
# File lib/trick_serial/serializer.rb, line 32 def self.class_option_map= x @@class_option_map = x end
# File lib/trick_serial/serializer.rb, line 37 def self.default Thread.current[:'TrickSerial::Serializer.default'] || @@default end
# File lib/trick_serial/serializer.rb, line 41 def self.default= x @@default = x end
# File lib/trick_serial/serializer.rb, line 45 def initialize @class_option_map ||= @@class_option_map || EMPTY_Hash @enabled = true @debug = 0 end
Public Instance Methods
# 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
# 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
# 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
This is similar to Rails
Object#extended_by.
# 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
# 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
Create a proxy for x for original object o. x may be a dup of o.
# 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
# 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
Decodes using proxy_class_map in-place. Only implemented by some subclasses.
# File lib/trick_serial/serializer.rb, line 88 def decode! x _prepare x do _decode! x end end
# File lib/trick_serial/serializer.rb, line 51 def enabled? case @enabled when Proc @enabled.call else @enabled end end
Encodes using proxy_class_map in-place.
# File lib/trick_serial/serializer.rb, line 70 def encode! x _prepare x do _encode! x end end
Returns a list of Modules that are proxable based on the configuration.
# 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