Module: Candygram::Wrapper

Defined in:
lib/candygram/wrapper.rb

Overview

Utility methods to serialize and unserialize objects into BSON

Constant Summary collapse

BSON_SAFE =
[String, 
NilClass, 
TrueClass, 
FalseClass, 
Fixnum, 
Float, 
Time,
Regexp,
ByteBuffer, 
Mongo::ObjectID, 
Mongo::Code,
Mongo::DBRef]

Class Method Summary collapse

Class Method Details

.unwrap(thing) ⇒ Object

Undoes any complicated magic from the Wrapper.wrap method. Almost everything falls through untouched except for symbol strings and hashed objects.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/candygram/wrapper.rb', line 75

def self.unwrap(thing)
  case thing
  when Hash
    if thing["__object_"]
      unwrap_object(thing)
    else
      unwrap_hash(thing)
    end
  when Array
    thing.map {|element| unwrap(element)}
  when /^__sym_(.+)/
    $1.to_sym
  else
    thing
  end
end

.unwrap_hash(hash) ⇒ Object

Traverses the hash, unwrapping both keys and values. Returns the hash that results.



93
94
95
96
97
# File 'lib/candygram/wrapper.rb', line 93

def self.unwrap_hash(hash)
  unwrapped = {}
  hash.each {|k,v| unwrapped[unwrap(k)] = unwrap(v)}
  unwrapped
end

.unwrap_object(hash) ⇒ Object

Turns a hashed object back into an object of the stated class, setting any captured instance variables. The main limitation is that the object’s class must respond to Class.new without any parameters; we will not attempt to guess at any complex initialization behavior.



102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/candygram/wrapper.rb', line 102

def self.unwrap_object(hash)
  if innards = hash["__object_"]
    klass = Kernel.const_get(innards["class"])
    object = klass.new
    if innards["ivars"]
      innards["ivars"].each do |name, value|
        object.instance_variable_set(name, unwrap(value))
      end
    end
    object
  end
end

.wrap(thing) ⇒ Object

Makes an object safe for the sharp pointy edges of MongoDB. Types properly serialized by the BSON.serialize call get passed through unmolested; others are unpacked and their pieces individually shrink-wrapped.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/candygram/wrapper.rb', line 23

def self.wrap(thing)
  # Pass the simple cases through
  return thing if BSON_SAFE.include?(thing.class)
  case thing
  when Symbol
    wrap_symbol(thing)
  when Array
    wrap_array(thing)
  when Hash
    wrap_hash(thing)
  when Numeric  # The most obvious are in BSON_SAFE, but not all
    thing
  when Date
    thing.to_time
  else
    wrap_object(thing)  # Our catchall machinery
  end
end

.wrap_array(array) ⇒ Object

Takes an array and returns the same array with unsafe objects wrapped



43
44
45
# File 'lib/candygram/wrapper.rb', line 43

def self.wrap_array(array)
  array.map {|element| wrap(element)}
end

.wrap_hash(hash) ⇒ Object

Takes a hash and returns it with both keys and values wrapped



48
49
50
51
52
# File 'lib/candygram/wrapper.rb', line 48

def self.wrap_hash(hash)
  wrapped = {}
  hash.each {|k, v| wrapped[wrap(k)] = wrap(v)}
  wrapped
end

.wrap_object(object) ⇒ Object

Returns a nested hash containing the class and instance variables of the object. It’s not the deepest we could ever go (it doesn’t handle singleton methods, etc.) but it’s a start.



61
62
63
64
65
66
67
68
69
70
71
# File 'lib/candygram/wrapper.rb', line 61

def self.wrap_object(object)
  wrapped = {"class" => object.class.name}
  ivars = {}
  object.instance_variables.each do |ivar|
    # Different Ruby versions spit different things out for instance_variables.  Annoying.
    ivar_name = '@' + ivar.to_s.sub(/^@/,'')
    ivars[ivar_name] = wrap(object.instance_variable_get(ivar_name))
  end
  wrapped["ivars"] = ivars unless ivars.empty?
  {"__object_" => wrapped}
end

.wrap_symbol(symbol) ⇒ Object

Returns a string that’s distinctive enough for us to unwrap later and produce the same symbol



55
56
57
# File 'lib/candygram/wrapper.rb', line 55

def self.wrap_symbol(symbol)
  "__sym_" + symbol.to_s
end