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

class_option_map[RW]
debug[RW]
enabled[RW]

Boolean or Proc.

logger[RW]
logger_level[RW]
root[R]
verbose[RW]

Public Class Methods

class_option_map() click to toggle source
# File lib/trick_serial/serializer.rb, line 29
def self.class_option_map 
  @@class_option_map
end
class_option_map=(x) click to toggle source
# File lib/trick_serial/serializer.rb, line 32
def self.class_option_map= x
  @@class_option_map = x
end
default() click to toggle source
# File lib/trick_serial/serializer.rb, line 37
def self.default
  Thread.current[:'TrickSerial::Serializer.default'] ||
    @@default
end
default=(x) click to toggle source
# File lib/trick_serial/serializer.rb, line 41
def self.default= x
  @@default = x
end
new() click to toggle source
# 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

_class_option(x) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

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
_log(msg = nil) { || ... } click to toggle source
# 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) click to toggle source

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
_prepare(x) { || ... } click to toggle source
# 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) click to toggle source

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

# File lib/trick_serial/serializer.rb, line 80
def decode x
  return x unless enabled?
  @copy = true
  decode! x
end
decode!(x) click to toggle source

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
enabled?() click to toggle source
# File lib/trick_serial/serializer.rb, line 51
def enabled?
  case @enabled
  when Proc
    @enabled.call
  else
    @enabled
  end
end
encode(x) click to toggle source

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

# File lib/trick_serial/serializer.rb, line 63
def encode x
  return x unless enabled?
  @copy = true
  encode! x
end
encode!(x) click to toggle source

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
proxyable() click to toggle source

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