Module: Candy::Wrapper

Defined in:
lib/candy/wrapper.rb

Overview

Utility methods to serialize and unserialize many types of objects into BSON.

Constant Summary collapse

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

Class Method Summary collapse

Class Method Details

.unwrap(thing, parent = nil, attribute = nil) ⇒ Object

Undoes any magic from the Wrapper.wrap method. Almost everything falls through untouched except for arrays and hashes. The ‘parent’ and ‘attribute’ parameters are for recursively setting the parent properties of embedded Candy objects.



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

def self.unwrap(thing, parent=nil, attribute=nil)
  case thing
  when Hash
    if thing.has_key?("__object_")
      unwrap_object(thing)
    else
      unwrap_hash(thing, parent, attribute)
    end
  when Array
    if parent   # We only want to create CandyArrays inside Candy pieces
      CandyArray.embed(parent, attribute, *thing)
    else
      thing.collect {|element| unwrap(element)}
    end
  else
    thing
  end
end

.unwrap_hash(hash, parent = nil, attribute = nil) ⇒ Object

Returns the hash as a Candy::Piece if a class name is embedded, or a CandyHash object otherwise. The ‘parent’ and ‘attribute’ parameters should be set by the caller if this is an embedded Candy object.



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/candy/wrapper.rb', line 117

def self.unwrap_hash(hash, parent=nil, attribute=nil)
  if class_name = hash.delete(CLASS_KEY.to_s)
    klass = qualified_const_get(class_name)
  else
    klass = CandyHash
  end
  
  if parent
    klass.embed(parent, attribute, hash)
  else
    klass.piece(hash)
  end
end

.unwrap_key(key) ⇒ Object

The inverse of Wrapper.wrap_key – removes single-quoting from strings and converts other strings to symbols.



133
134
135
136
137
138
139
# File 'lib/candy/wrapper.rb', line 133

def self.unwrap_key(key)
  if key =~ /^'(.*)'$/
    $1
  else
    key.to_sym
  end
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.



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/candy/wrapper.rb', line 144

def self.unwrap_object(hash)
  if innards = hash["__object_"]
    klass = Kernel.qualified_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.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/candy/wrapper.rb', line 27

def self.wrap(thing)
  # Pass the simple cases through
  return thing if BSON_SAFE.include?(thing.class)
  thing = thing.to_candy if thing.respond_to?(:to_candy)  # Make it sweeter if it can be sweetened
  case 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
  # Problem children
  when Proc
    raise TypeError, "Candy can't wrap Proc objects!"
  when Range
    raise TypeError, "Candy can't wrap ranges!"
  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.



51
52
53
# File 'lib/candy/wrapper.rb', line 51

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

.wrap_hash(hash) ⇒ Object

Takes a hash and returns it with values wrapped. Symbol keys are reversibly converted to strings.



56
57
58
59
60
61
62
# File 'lib/candy/wrapper.rb', line 56

def self.wrap_hash(hash)
  wrapped = {}
  hash.each do |key, value|  
    wrapped[wrap_key(key)] = wrap(value)
  end
  wrapped
end

.wrap_key(key) ⇒ Object

Lightly wraps hash keys, converting symbols to strings and wrapping strings in single quotes. Thus, we can recover symbols when we unwrap them later. Other key types will raise an exception.



66
67
68
69
70
71
72
73
74
75
# File 'lib/candy/wrapper.rb', line 66

def self.wrap_key(key)
  case key
  when String
    "'#{key}'"
  when Symbol
    key.to_s
  else
    raise TypeError, "Candy field names must be strings or symbols. You gave us #{key.class}: #{key}"
  end
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.



79
80
81
82
83
84
85
86
87
88
89
# File 'lib/candy/wrapper.rb', line 79

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