Module: HappyMapper

Defined in:
lib/happymapper.rb,
lib/happymapper/item.rb,
lib/happymapper/element.rb,
lib/happymapper/version.rb,
lib/happymapper/attribute.rb

Defined Under Namespace

Modules: ClassMethods Classes: Attribute, Element, Item

Constant Summary collapse

DEFAULT_NS =
"happymapper"
Version =
'0.5.0'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



12
13
14
15
16
17
18
# File 'lib/happymapper.rb', line 12

def self.included(base)
  base.instance_variable_set("@attributes", {})
  base.instance_variable_set("@elements", {})
  base.instance_variable_set("@registered_namespaces", {})
  
  base.extend ClassMethods
end

Instance Method Details

#to_xml(parent_node = nil, default_namespace = nil) ⇒ Object

Create an xml representation of the specified class based on defined HappyMapper elements and attributes. The method is defined in a way that it can be called recursively by classes that are also HappyMapper classes, allowing for the composition of classes.



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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/happymapper.rb', line 145

def to_xml(parent_node = nil, default_namespace = nil)

  #
  # Create a tag that uses the tag name of the class that has no contents
  # but has the specified namespace or uses the default namespace
  #
  current_node = XML::Node.new(self.class.tag_name)


  if parent_node
    #
    # if #to_xml has been called with a parent_node that means this method
    # is being called recursively (or a special case) and we want to return
    # the parent_node with the new node as a child
    #
    parent_node << current_node
  else
    #
    # If #to_xml has been called without a Node (and namespace) that
    # means we want to return an xml document
    #
    write_out_to_xml = true
  end
  
  #
  # Add all the registered namespaces to the current node and the current node's
  # root element. Without adding it to the root element it is not possible to
  # parse or use xpath to find elements.
  #
  if self.class.instance_variable_get('@registered_namespaces')
    
    # Given a node, continue moving up to parents until there are no more parents
    find_root_node = lambda {|node| while node.parent? ; node = node.parent ; end ; node }
    root_node = find_root_node.call(current_node)
    
    # Add the registered namespace to the found root node only if it does not already have one defined
    self.class.instance_variable_get('@registered_namespaces').each_pair do |prefix,href|
      XML::Namespace.new(current_node,prefix,href)
      XML::Namespace.new(root_node,prefix,href) unless root_node.namespaces.find_by_prefix(prefix)
    end
  end

  #
  # Determine the tag namespace if one has been specified. This value takes
  # precendence over one that is handed down to composed sub-classes.
  #
  tag_namespace = current_node.namespaces.find_by_prefix(self.class.namespace) || default_namespace
  
  # Set the namespace of the current node to the specified namespace
  current_node.namespaces.namespace = tag_namespace if tag_namespace

  #
  # Add all the attribute tags to the current node with their namespace, if one
  # is defined, or the namespace handed down to the node.
  #
  self.class.attributes.each do |attribute|
    attribute_namespace = current_node.namespaces.find_by_prefix(attribute.options[:namespace]) || default_namespace
    
    value = send(attribute.method_name)

    #
    # If the attribute has a :on_save attribute defined that is a proc or
    # a defined method, then call those with the current value.
    #
    if on_save_operation = attribute.options[:on_save]
      if on_save_operation.is_a?(Proc)
        value = on_save_operation.call(value)
      elsif respond_to?(on_save_operation)
        value = send(on_save_operation,value)
      end
    end
    
    current_node[ "#{attribute_namespace ? "#{attribute_namespace.prefix}:" : ""}#{attribute.tag}" ] = value.to_s
  end

  #
  # All all the elements defined (e.g. has_one, has_many, element) ...
  #
  self.class.elements.each do |element|

    tag = element.tag || element.name
    
    element_namespace = current_node.namespaces.find_by_prefix(element.options[:namespace]) || tag_namespace
    
    value = send(element.name)

    #
    # If the element defines an :on_save lambda/proc then we will call that
    # operation on the specified value. This allows for operations to be 
    # performed to convert the value to a specific value to be saved to the xml.
    #
    if on_save_operation = element.options[:on_save]
      if on_save_operation.is_a?(Proc)
        value = on_save_operation.call(value)
      elsif respond_to?(on_save_operation)
        value = send(on_save_operation,value)
      end
    end

    #
    # Normally a nil value would be ignored, however if specified then
    # an empty element will be written to the xml
    #
    if value.nil? && element.options[:state_when_nil]
      current_node << XML::Node.new(tag,nil,element_namespace)
    end

    #
    # To allow for us to treat both groups of items and singular items
    # equally we wrap the value and treat it as an array.
    #
    if value.nil?
      values = []
    elsif value.respond_to?(:to_ary) && !element.options[:single]
      values = value.to_ary
    else
      values = [value]
    end


    values.each do |item|

      if item.is_a?(HappyMapper)

        #
        # Other HappyMapper items that are convertable should not be called
        # with the current node and the namespace defined for the element.
        #
        item.to_xml(current_node,element_namespace)

      elsif item

        #
        # When a value exists we should append the value for the tag
        #
        current_node << XML::Node.new(tag,item.to_s,element_namespace)

      else
        
        #
        # Normally a nil value would be ignored, however if specified then
        # an empty element will be written to the xml
        #
        current_node << XML.Node.new(tag,nil,element_namespace) if element.options[:state_when_nil]

      end

    end

  end


  #
  # Generate xml from a document if no node was passed as a parameter. Otherwise
  # this method is being called recursively (or special case) and we should
  # return the node with this node attached as a child.
  #
  if write_out_to_xml
    document = XML::Document.new
    document.root = current_node
    document.to_s
  else
    parent_node
  end

end