Class: Adaptation::Message
- Includes:
- ROXML, Validateable
- Defined in:
- lib/adaptation/message.rb
Overview
Adaptation::Message – XML to classes mapping.
Adaptation::Message connects ruby objects and xml data to create a model where logic and data are presented in one wrapping. It’s is inspired on activerecord, and pretends to solve xml data management problems in a similar way activerecord handles database data management.
Adaptation::Message classes describe xml structures, allowing separation of common structures as standalone classes. It offers a set of macros to specify wich structures are part of a bigger one and to allow validation of the different parts.
A summary of the major features and their usage:
-
Automated mapping between classes and xml attributes and elements.
class Contact < Adaptation::Message has_one :text, :name end
...is automatically mapped to a xml structure with root element named "contact", such as:
<contact>
<name>Name</name>
</contact>
...which gives Contact#name
-
Associations between objects controlled by simple meta-programming macros.
class Agenda < Adaptation::Message has_one :attribute, :type has_one :text, :owner has_many :contacts end class Contact < Adaptation::Message has_one :text, :name end
Agenda class would be mapping the following xml structure:
<agenda type="...">
<owner>...</owner>
<contact>...</contact>
<contact>...</contact>
...
</agenda>
while the _Contact_ class would map:
<contact>
<name>...</name>
</contact>
The Contact class is a partial, a structure included in a bigger structure, so its
file name starts with an underscore: _contact.rb
We could have wrote Agenda like this, to change the contacts container name:
class Agenda < Adaptation::Message
has_one :attribute, :type
has_one :text, :owner
has_many :contacts, :in => :contact_list
end
and Contact like:
class Contact < Adaptation::Message
has_one :text, :name
end
Then the mapping in Agenda would be:
<agenda type="...">
<owner>...</owner>
<contact_list>
<contact>...</contact>
</contact_list>
</agenda>
-
Validation rules.
Adaptation::Message uses {ActiveRecord::Validations}[http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html].
This means that {ActiveRecord validations}[http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html]
can be used in an Adaptation::Message object and that custom validations can be easily implemented.
Adaptation::Message also defines some {custom validation methods}[link:../rdoc/classes/ActiveRecord/Validations/ClassMethods.html].
Validations usage example:
class Agenda < Adaptation::Message
has_one :attribute, :type
has_one :text, :owner
validates_presence_of :owner, :type
validates_value_of :type, "indexed"
end
Constant Summary collapse
- @@classes_with_brothers =
[]
Instance Attribute Summary collapse
-
#id ⇒ Object
readonly
avoid id method deprecation warnings.
Class Method Summary collapse
-
.get_class_object(mapped_xml) ⇒ Object
:nodoc:.
-
.has_many(*options) ⇒ Object
Maps many xml subelements.
-
.has_one(*symbols) ⇒ Object
Maps an xml attribute or element.
-
.has_text ⇒ Object
Maps an xml element text.
-
.mapped_xml ⇒ Object
Returns the xml element this class is mapping.
-
.maps_xml(element) ⇒ Object
Defines the xml element that this class is mapping.
-
.to_object(xml_message) ⇒ Object
This is the constructor.
Instance Method Summary collapse
-
#check ⇒ Object
Alias for Adaptation::Message#valid? Deprecated, use valid? instead.
Methods included from Validateable
append_features, included, #method_missing
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class Validateable
Instance Attribute Details
#id ⇒ Object (readonly)
avoid id method deprecation warnings
105 106 107 |
# File 'lib/adaptation/message.rb', line 105 def id @id end |
Class Method Details
.get_class_object(mapped_xml) ⇒ Object
:nodoc:
313 314 315 316 317 318 319 320 321 322 |
# File 'lib/adaptation/message.rb', line 313 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
Maps many xml subelements.
Example 1:
<xml>
<subelement>...</subelement>
<subelement>...</subelement>
<subelement>...</subelement>
</xml>
class Xml < Adaptation::Message
has_many :subelements
end
Example 2:
<xml>
<subelement_list>
<subelement>...</subelement>
<subelement>...</subelement>
<subelement>...</subelement>
</subelement_list>
</xml>
class Xml < Adaptation::Message
has_many :subelements, :in => :subelement_list
end
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 |
# File 'lib/adaptation/message.rb', line 244 def self.has_many * configuration = {} configuration.update(.pop) if .last.is_a?(Hash) class_with_container = nil if configuration[:in] class_with_container = [0].to_s.capitalize[0..-2] + ":#{configuration[:in].to_s}" else class_with_container = [0].to_s.capitalize[0..-2] + ":#{[0].to_s}" end unless @@classes_with_brothers.include?(class_with_container) @@classes_with_brothers << class_with_container end @has_many = [] if @has_many.nil? unless @has_many.include?([0]) @has_many << [0] load "#{ADAPTOR_ROOT}/app/messages/_#{[0].to_s[0..-2]}.rb" klass = get_class_object([0].to_s.capitalize[0..-2]) if configuration[:in] xml_object configuration[:in], klass, ROXML::TAG_ARRAY, configuration[:in].to_s validates_associated configuration[:in] else xml_object [0], klass, ROXML::TAG_ARRAY #, options[0].to_s validates_associated [0] end end end |
.has_one(*symbols) ⇒ Object
Maps an xml attribute or element.
Example 1: Mapping an attribute:
<xml attr="something"/>
class Xml < Adaptation::Message
has_one :attribute, :attr
end
Example 2: Mapping an xml element that contains text.
<xml>
<element>something</element>
</xml>
class Xml < Adaptation::Message
has_one :text, :element
end
Example 3: Mapping an xml element that contains xml data.
<xml>
<element>
<subelement>something</subelement>
</element>
</xml>
class Xml < Adaptation::Message
has_one :object, :element
end
The element xml structure would be described in a separate partial file _element.rb:
class Element < Adaptation::Message
has_one :text, :subelement
end
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 |
# File 'lib/adaptation/message.rb', line 149 def self.has_one *symbols xml_tag = symbols[0] case xml_tag when :object @objects = [] if @objects.nil? unless @objects.include?(symbols[1]) @objects << symbols[1] klass = get_class_object(symbols[1]) if klass.nil? require "#{ADAPTOR_ROOT}/app/messages/_#{symbols[1].to_s}.rb" klass = get_class_object(symbols[1]) end xml_object symbols[1], klass validates_associated symbols[1] end when :attribute @attributes = [] if @attributes.nil? unless @attributes.include?(symbols[1]) @attributes << symbols[1] s = "" @attributes.each do |a| s << a.to_s end xml_attribute symbols[1] end when :text @texts = [] if @texts.nil? unless @texts.include?(symbols[1]) @texts << symbols[1] xml_text symbols[1] end end end |
.has_text ⇒ Object
Maps an xml element text.
If an element contains plain text data and has not been declared in its parent element with has_one :text, it must declare itself with has_text to be able to contain text.
Example:
<xml>
<element attr="something">something more</element>
</xml>
class Xml < Adaptation::Message
has_one :object, :element
end
In this case element cannot be declared just like has_one :text, because it also has an attribute. It must be declared like has_one :object. Then the element declares itself in a file called _element.rb like this:
class Element < Adaptation::Message
has_one :attribute, :attr
has_text
end
209 210 211 212 213 214 |
# File 'lib/adaptation/message.rb', line 209 def self.has_text if @has_text.nil? @has_text = true xml_text :text, nil, ROXML::TEXT_CONTENT end end |
.mapped_xml ⇒ Object
Returns the xml element this class is mapping
309 310 311 |
# File 'lib/adaptation/message.rb', line 309 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
303 304 305 306 |
# File 'lib/adaptation/message.rb', line 303 def self.maps_xml element @mapped_xml = element xml_name element.to_s end |
.to_object(xml_message) ⇒ Object
This is the constructor. Instead of Adaptation::Message#new, an Adaptation::Message instance is created like this:
m = Adaptation::Message.to_object("<xml>valid xml</xml>")
329 330 331 |
# File 'lib/adaptation/message.rb', line 329 def self.to_object parse end |
Instance Method Details
#check ⇒ Object
Alias for Adaptation::Message#valid? Deprecated, use valid? instead.
335 336 337 |
# File 'lib/adaptation/message.rb', line 335 def check valid? end |