Class: Adaptation::Message

Inherits:
Object show all
Includes:
Validateable
Defined in:
lib/adaptation/message.rb

Overview

Adaptation::Message – XML to classes mapping.

Adaptation::Message maps xml data into a ruby object. It also provides validators (inspired by ActiveRecord ORM) to check xml format.

Examples:

contact = Adaptation::Message.new('<contact><name kind="surname">Name</name></contact>')

contact.name.content        # -> "Name"
contact.names.first.content # -> "Name"
contact.names.length        # -> 1
contact.name.kind           # -> "surname"

Let’s add some validations:

class Contact < Adaptation::Message
  validates_presence_of :name
end

contact = Contact.new('<contact><name kind="surname">Name</name></contact>')
contact.valid?              # -> true

contact = Contact.new('<contact><phone>555</phone></contact>')
contact.valid?              # -> false

class SeriousContact < Adaptation::Message
  maps_xml :contact # tell Adaptation that xml data like <contact>...</contact> is mapped by this class
  validates_value_of :kind, "surname", :in => :names 
end

contact = SeriousContact.new('<contact><name kind="surname">Name</name></contact>')
contact.valid?              # -> true

contact = SeriousContact.new('<contact><name kind="alias">Alias</name></contact>')
contact.valid?              # -> false

More on validations here.

Constant Summary collapse

@@classes_with_brothers =
[]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Validateable

append_features, included, #method_missing

Constructor Details

#initialize(xml_string) ⇒ Message

Constructor. Transforms xml passsed as a String to an object wich methods map the input xml elements and attributes.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/adaptation/message.rb', line 56

def initialize xml_string 
  @hash_with_root = XmlSimple.xml_in("<adaptation_wrapper>" + xml_string + "</adaptation_wrapper>", 'ForceArray' => false, 'AttrPrefix' => true) 
 
  first_value = @hash_with_root.values.first
  hash = first_value.is_a?(String) ? {"content" => first_value} : first_value
  array = hash.is_a?(Array) ? hash : [hash]

  array.each do |h|
    if end_of_tree?(h)
      h.each_pair do |k, v|
        if !v.is_a?(Array)
          is_attribute = k.include?("@") ? true : false
          var = k.gsub("@","")
          self.class_eval "attr_accessor :#{var}"         
          eval "@#{var} = v"
          var2 = pluralize(var)
          if !is_attribute and var != var2  
            self.class_eval "attr_accessor :#{var2}"         
            eval "@#{var2} = []; @#{var2} << '#{var}'" 
          end
        else 
          var = pluralize(k.gsub("@",""))
          self.class_eval "attr_accessor :#{var}"         
          eval "@#{var} = []"
          v.each do |val|
            if is_attribute?(val)
              xml_substring = XmlSimple.xml_out(val, 'NoIndent' => true, 'RootName' => k,   'AttrPrefix' => true)
              eval "@#{var} << Adaptation::Message.new('#{xml_substring}')"
            else
              eval "@#{var} << '#{val}'"  
            end
          end 
        end
      end
    else
      h.each_pair do |k,v|
        if k[0..0] == "@"
          var = k.gsub("@","")
          self.class_eval "attr_accessor :#{var}"
          eval "@#{var} = '#{v}'" 
        else
          self.class_eval "attr_accessor :#{k}"
          xml_substring = ""
          if !v.is_a?(Array)
            xml_substring = XmlSimple.xml_out(v, 'NoIndent' => true, 'RootName' => k,   'AttrPrefix' => true)
            eval "@#{k} = Adaptation::Message.new('#{xml_substring}')"
            k2 = pluralize(k)
            if k != k2  
              self.class_eval "attr_accessor :#{k2}"
              eval "@#{k2} = []; @#{k2} << @#{k}" 
            end
          else
            k2 = pluralize(k)
            self.class_eval "attr_accessor :#{k2}"
            eval "@#{k2} = [];" 
            v.each do |val|  
              xml_substring = XmlSimple.xml_out(val, 'NoIndent' => true, 'RootName' => k, 'AttrPrefix' => true) 
              eval "@#{k} = Adaptation::Message.new('#{xml_substring}')"
              eval "@#{k2} << @#{k}" 
            end
          end
        end
      end
    end
  end

end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Validateable

Instance Attribute Details

#idObject (readonly)

avoid id method deprecation warnings



47
48
49
# File 'lib/adaptation/message.rb', line 47

def id
  @id
end

Class Method Details

.get_class_object(mapped_xml) ⇒ Object

:nodoc:



173
174
175
176
177
178
179
180
181
182
# File 'lib/adaptation/message.rb', line 173

def self.get_class_object(mapped_xml) #:nodoc:
  # TODO: reimplement this as read in ruby-talk (using 'inherited' method)
  mapped_xml = mapped_xml.downcase.to_sym if mapped_xml.is_a?(String)
  klass = nil
  ObjectSpace.each_object(Class) do |c|
    next unless c.ancestors.include?(Adaptation::Message) and (c != self) and (c != Adaptation::Message)
    (klass = c and break) if c.mapped_xml == mapped_xml rescue next
  end
  klass
end

.has_many(*options) ⇒ Object

:nodoc:



132
133
134
# File 'lib/adaptation/message.rb', line 132

def self.has_many *options #:nodoc: 
  logger.info "has_many is deprecated and not necessary"
end

.has_one(*symbols) ⇒ Object

:nodoc:



124
125
126
# File 'lib/adaptation/message.rb', line 124

def self.has_one *symbols #:nodoc:
  logger.info "has_one is deprecated and not necessary"
end

.has_textObject

:nodoc:



128
129
130
# File 'lib/adaptation/message.rb', line 128

def self.has_text #:nodoc:
  logger.info "has_text is deprecated and not necessary"
end

.loggerObject

:nodoc:#



196
197
198
# File 'lib/adaptation/message.rb', line 196

def self.logger#:nodoc:#
  Adaptation::Base.logger rescue Logger.new(STDOUT)
end

.mapped_xmlObject

Returns the xml element this class is mapping



169
170
171
# File 'lib/adaptation/message.rb', line 169

def self.mapped_xml
  @mapped_xml || self.to_s.downcase.gsub("::","_").to_sym
end

.maps_xml(element) ⇒ Object

Defines the xml element that this class is mapping. This is useful to avoid class name collisions:

Example:

We already have a database table called 'things' and we
interoperate with it with an ActiveRecord subclass called
'Thing':

  class Thing < ActiveRecord::Base
    ...
  end

But in the same Adaptation application we want to parse the
following xml:

  <thing>...</thing>

Defining another class Thing would produce a class name 
collision, but we can do:

  class XmlThing < Adaptation::Message
    maps_xml :thing
    ...     
  end

and store it in a file called app/messages/thing.rb


164
165
166
# File 'lib/adaptation/message.rb', line 164

def self.maps_xml element
  @mapped_xml = element
end

.to_object(xml_message) ⇒ Object

Deprecated, use new instead.



185
186
187
188
# File 'lib/adaptation/message.rb', line 185

def self.to_object xml_message #:nodoc:
   logger.info "to_object is deprecated, use new instead"
   self.new(xml_message)
end

Instance Method Details

#checkObject

Deprecated, use valid? instead.



191
192
193
194
# File 'lib/adaptation/message.rb', line 191

def check #:nodoc:
  logger.info "check is deprecated, use valid? instead"
  valid?
end

#to_hashObject



204
205
206
# File 'lib/adaptation/message.rb', line 204

def to_hash
  @hash_with_root
end

#to_xmlObject



200
201
202
# File 'lib/adaptation/message.rb', line 200

def to_xml
  xml_out(@hash_with_root).gsub("\"","'").gsub(/(<|<\/)content(>| *\/>)/,"")
end