mappum 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/.gitignore +4 -0
  2. data/LICENSE +15 -0
  3. data/README +53 -0
  4. data/Rakefile +48 -0
  5. data/VERSION +1 -0
  6. data/bin/mapserver.rb +4 -0
  7. data/java-api/pom.xml +63 -0
  8. data/java-api/src/main/java/pl/ivmx/mappum/JavaTransform.java +12 -0
  9. data/java-api/src/main/java/pl/ivmx/mappum/MappumApi.java +83 -0
  10. data/java-api/src/main/java/pl/ivmx/mappum/TreeElement.java +23 -0
  11. data/java-api/src/main/java/pl/ivmx/mappum/WorkdirLoader.java +12 -0
  12. data/java-api/src/test/java/iv/Client.java +237 -0
  13. data/java-api/src/test/java/iv/Person.java +261 -0
  14. data/java-api/src/test/java/pl/ivmx/mappum/MappumTest.java +122 -0
  15. data/java-api/src/test/resources/map/example_map.rb +88 -0
  16. data/lib/mappum.rb +46 -0
  17. data/lib/mappum/autoconv_catalogue.rb +43 -0
  18. data/lib/mappum/dsl.rb +255 -0
  19. data/lib/mappum/java_transform.rb +107 -0
  20. data/lib/mappum/map.rb +194 -0
  21. data/lib/mappum/mapserver/mapgraph.rb +192 -0
  22. data/lib/mappum/mapserver/mapserver.rb +213 -0
  23. data/lib/mappum/mapserver/maptable.rb +80 -0
  24. data/lib/mappum/mapserver/views/doc.erb +15 -0
  25. data/lib/mappum/mapserver/views/main.erb +39 -0
  26. data/lib/mappum/mapserver/views/maptable.erb +16 -0
  27. data/lib/mappum/mapserver/views/rubysource.erb +25 -0
  28. data/lib/mappum/mapserver/views/transform-ws.wsdl.erb +50 -0
  29. data/lib/mappum/mapserver/views/ws-error.erb +10 -0
  30. data/lib/mappum/open_xml_object.rb +68 -0
  31. data/lib/mappum/ruby_transform.rb +199 -0
  32. data/lib/mappum/xml_transform.rb +382 -0
  33. data/mappum.gemspec +117 -0
  34. data/sample/address_fixture.xml +11 -0
  35. data/sample/crm.rb +9 -0
  36. data/sample/crm_client.xsd +28 -0
  37. data/sample/erp.rb +7 -0
  38. data/sample/erp_person.xsd +44 -0
  39. data/sample/example_conversions.rb +12 -0
  40. data/sample/example_map.rb +92 -0
  41. data/sample/example_notypes.rb +77 -0
  42. data/sample/example_when.rb +13 -0
  43. data/sample/person_fixture.xml +23 -0
  44. data/sample/person_fixture_any.xml +26 -0
  45. data/sample/server/map/example_any.rb +28 -0
  46. data/sample/server/map/example_soap4r.rb +59 -0
  47. data/sample/server/mapserver.sh +1 -0
  48. data/sample/server/schema/crm_client.xsd +29 -0
  49. data/sample/server/schema/erp/erp_person.xsd +38 -0
  50. data/test/test_conversions.rb +24 -0
  51. data/test/test_example.rb +175 -0
  52. data/test/test_openstruct.rb +129 -0
  53. data/test/test_soap4r.rb +108 -0
  54. data/test/test_when.rb +35 -0
  55. data/test/test_xml_any.rb +62 -0
  56. metadata +164 -0
@@ -0,0 +1,80 @@
1
+ module Mappum
2
+ module MapServer
3
+ class MapTable
4
+ attr_reader :edge_maps
5
+
6
+ def initialize(map)
7
+ unless map.normalized?
8
+ raise "Tables are for unidirectional maps only"
9
+ end
10
+ @edge_maps = {}
11
+
12
+ @max_llevel, @max_rlevel = 0, 0
13
+ findMaxLevel(map)
14
+ @table = "<TABLE BORDER=1 CELLPADDING=3 CELLSPACING=1 RULES=ALL FRAME=BOX>" + getRowsFrom(map).join("\n") + "</TABLE>"
15
+ end
16
+ def getHtml()
17
+ return @table
18
+ end
19
+ private
20
+ def getRowsFrom(map,rlevel=0,llevel=0)
21
+
22
+ lname = map.from.name.to_s if map.from.respond_to?(:name)
23
+ rname = map.to.name.to_s if map.to.respond_to?(:name)
24
+
25
+ lname ||= ""
26
+ rname ||= ""
27
+
28
+ lname += "(#{map.from.clazz.to_s})" if map.from.respond_to?(:clazz) and not map.from.clazz.nil?
29
+ rname += "(#{map.to.clazz.to_s})" if map.to.respond_to?(:clazz) and not map.to.clazz.nil?
30
+
31
+
32
+ llevel = llevel -1 if map.from.respond_to?(:placeholder?) and map.from.placeholder?
33
+ rlevel = rlevel -1 if map.to.respond_to?(:placeholder?) and map.to.placeholder?
34
+
35
+ rows=[]
36
+ oper = ""
37
+ map.maps.each do |submap|
38
+ rows += getRowsFrom(submap,rlevel+1,llevel+1)
39
+ end
40
+
41
+ oper = "&gt;&gt;" if map.maps.empty?
42
+ oper = "&gt;f&gt;" unless map.func.nil?
43
+ unless map.dict.nil?
44
+ @edge_maps[@edge_maps.size+1]=map
45
+ oper = "&gt;d<font style='vertical-align: super; font-size: smaller;'>#{@edge_maps.size}</font>&gt;"
46
+ end
47
+
48
+ oper = "set constant \"#{map.from.value.to_s}\"" if map.from.kind_of?(Constant)
49
+ oper = "Func call" if map.from.kind_of?(Function)
50
+ oper += "<BR/><FONT style='color: #009020;'> #{map.desc} </FONT>" unless map.desc.nil?
51
+
52
+ lempy_td = ""
53
+ llevel.times{lempy_td += "<TD> </TD>"}
54
+ rempy_td = ""
55
+ rlevel.times{rempy_td += "<TD> </TD>"}
56
+
57
+ lstyle = ""
58
+ #color structure names
59
+ lstyle = "bgcolor='#AAAAAA'" if not map.maps.empty? and map.from.respond_to?(:placeholder?) and not map.from.placeholder?
60
+ rstyle = ""
61
+ #color structure names
62
+ rstyle = "bgcolor='#AAAAAA'" if not map.maps.empty? and map.to.respond_to?(:placeholder?) and not map.to.placeholder?
63
+
64
+ row = "<TR>#{lempy_td}<TD #{lstyle} colspan='#{@max_llevel +1 - llevel}'>#{lname}</TD>"
65
+ row += "<TD align='center'>#{oper}</TD>"
66
+ row += "#{rempy_td}<TD #{rstyle} colspan='#{@max_rlevel+1 - rlevel}'>#{rname}</TD></TR>"
67
+
68
+ rows = [row] + rows
69
+ return rows
70
+ end
71
+ def findMaxLevel(map,rlevel=0,llevel=0)
72
+ map.maps.each do |submap|
73
+ findMaxLevel(submap,rlevel+1,llevel+1)
74
+ end
75
+ @max_llevel = [@max_llevel,llevel].max
76
+ @max_rlevel = [@max_rlevel,rlevel].max
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,15 @@
1
+ <html>
2
+ <body>
3
+ <h1><%=@map_name%></h1>
4
+ <p>
5
+ <%=@map.desc%>
6
+ </p>
7
+ <object type="image/svg+xml" data="/<%=@catalogue%>/svggraph?map=<%=@map_name%>"><img src="/<%=@catalogue%>/pnggraph?map=<%=@map_name%>"></object>
8
+ <table border="1" cellspacing="0">
9
+ <tr><td>Number</td><td>Description</td><td>Technical explanation</td></tr>
10
+ <%@edge_maps.each do |k, edm, expl|%>
11
+ <tr><td><%=k%></td><td><%=edm.desc%>&nbsp;</td><td><%=expl%>&nbsp;</td></tr>
12
+ <%end%>
13
+ </table>
14
+ </body>
15
+ <html>
@@ -0,0 +1,39 @@
1
+ <html>
2
+ <body>
3
+ <form action="/" method="get">
4
+ Catalogue:
5
+ <select name="catalogue">
6
+ <%@catalogues.each do |cat|%>
7
+ <option value="<%=cat%>" <%="selected='true'" if cat == @catalogue%>><%=cat%></option>
8
+ <%end%>
9
+ </select>
10
+ <input type="submit" value="Change"/> (<a href="/<%=@catalogue%>/transform-ws.wsdl">wsdl</a>)
11
+ </form>
12
+ <form action="/<%=@catalogue%>/transform" method="post">
13
+ <p>
14
+ <select name="map">
15
+ <option value="auto_select" selected="true">auto select</option>
16
+ <%@maps_name_source.collect do |mn, source|%>
17
+ <option value='<%=mn%>'><%=mn%></option>
18
+ <%end%>
19
+ </select>
20
+ <input type="checkbox" name="ignore" value="false">Ignore types</input>
21
+ <br/>
22
+ <TEXTAREA name="doc" rows="20" cols="80"></TEXTAREA><br/>
23
+ <INPUT type="submit" value="Send"/><INPUT type="reset"/>
24
+ </p>
25
+ </form>
26
+ <BR/>
27
+ Bidirectional maps:<p>
28
+ <%@bidi_maps_name_source.each do |mn, source|%>
29
+ <a href='/<%=@catalogue%>/doc?map=<%=mn%>'><%=mn%></a> (<a href='<%=source%>'>source</a>)<br/>
30
+ <%end%>
31
+ </p>
32
+ <BR/>
33
+ Unidirectional maps:<p>
34
+ <%@maps_name_source.collect do |mn, source|%>
35
+ <a href='/<%=@catalogue%>/doc?map=<%=mn%>'><%=mn%></a> (<a href='/<%=@catalogue%>/maptable?map=<%=mn%>'>table</a>) (<a href='<%=source%>'>source</a>)<br/>
36
+ <%end%>
37
+ </p>
38
+ </body>
39
+ <html>
@@ -0,0 +1,16 @@
1
+ <html>
2
+ <body>
3
+ <h1><%=@map_name%></h1>
4
+ <p>
5
+ <%=@map.desc%>
6
+ </p>
7
+ <%=@maptable.getHtml%>
8
+ <h2>Dictionaries:</h2>
9
+ <table border="1" cellspacing="0">
10
+ <tr><td>Number</td><td>Description</td><td>Technical explanation</td></tr>
11
+ <%@edge_maps.each do |k, edm, expl|%>
12
+ <tr><td><%=k%></td><td><%=edm.desc%>&nbsp;</td><td><%=expl%>&nbsp;</td></tr>
13
+ <%end%>
14
+ </table>
15
+ </body>
16
+ <html>
@@ -0,0 +1,25 @@
1
+ <html>
2
+ <head>
3
+ <style type='text/css'>
4
+ .normal {}
5
+ .comment { color: #005; font-style: italic; }
6
+ .keyword { color: #A00; font-weight: bold; }
7
+ .method { color: #077; }
8
+ .class { color: #074; }
9
+ .module { color: #050; }
10
+ .punct { color: #447; font-weight: bold; }
11
+ .symbol { color: #099; }
12
+ .string { color: #944; }
13
+ .char { color: #F07; }
14
+ .ident { color: #004; }
15
+ .constant { color: #07F; }
16
+ .regex { color: #B66; }
17
+ .number { color: #D55; }
18
+ .attribute { color: #377; }
19
+ .global { color: #3B7; }
20
+ .expr { color: #227; }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <%=@body%>
25
+ </body>
@@ -0,0 +1,50 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <wsdl:definitions name="transfer-ws"
3
+ targetNamespace="http://mappum.ivmx.pl/transfer-ws/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
4
+ xmlns:tns="http://mappum.ivmx.pl/transfer-ws/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
6
+ <%@xml_imports.each do |file, namespace|%>
7
+ xmlns:<%=get_xmlns(namespace)%>="<%=namespace%>"
8
+ <%end%>>
9
+
10
+ <%@xml_imports.each do |file, namespace|%>
11
+ <wsdl:import namespace="<%=namespace%>"
12
+ location="<%=file%>"/>
13
+ <%end%>
14
+ <%@xml_elements.each do |qname|%>
15
+ <wsdl:message name="<%=qname.name%>">
16
+ <wsdl:part name="parameters" element="<%=get_xmlns(qname.namespace)%>:<%=qname.name%>"/>
17
+ </wsdl:message>
18
+ <%end%>
19
+ <wsdl:message name="any">
20
+ <wsdl:part name="parameters" type="xsd:anyType"/>
21
+ </wsdl:message>
22
+ <wsdl:portType name="default">
23
+ <%@xml_maps.each do |name, from, to|%>
24
+ <wsdl:operation name="<%=name%>">
25
+ <wsdl:input message="tns:<%=from.name%>"/>
26
+ <wsdl:output message="tns:<%=to.name%>"/>
27
+ </wsdl:operation>
28
+ <%end%>
29
+ </wsdl:portType>
30
+ <wsdl:binding name="binding" type="tns:default">
31
+ <soap:binding style="document"
32
+ transport="http://schemas.xmlsoap.org/soap/http" />
33
+ <%@xml_maps.each do |name, from, to|%>
34
+ <wsdl:operation name="<%=name%>">
35
+ <soap:operation soapAction="<%=name%>" />
36
+ <wsdl:input>
37
+ <soap:body use="literal" />
38
+ </wsdl:input>
39
+ <wsdl:output>
40
+ <soap:body use="literal" />
41
+ </wsdl:output>
42
+ </wsdl:operation>
43
+ <%end%>
44
+ </wsdl:binding>
45
+ <wsdl:service name="transfer">
46
+ <wsdl:port name="http" binding="tns:binding">
47
+ <soap:address location="http://localhost:9292/transform-ws" />
48
+ </wsdl:port>
49
+ </wsdl:service>
50
+ </wsdl:definitions>
@@ -0,0 +1,10 @@
1
+ <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
2
+ <env:Body>
3
+ <env:Fault>
4
+ <faultcode><%=ERB::Util.html_escape @error.class.to_s%></faultcode>
5
+ <faultstring><%=ERB::Util.html_escape @error.message%></faultstring>
6
+ <faultactor>Server</faultactor>
7
+ <detail><%=ERB::Util.html_escape @error.backtrace.join("\n")%></detail>
8
+ </env:Fault>
9
+ </env:Body>
10
+ </env:Envelope>
@@ -0,0 +1,68 @@
1
+ class SOAP::Mapping::Object
2
+ def id
3
+ self[XSD::QName.new(nil, "id")]
4
+ end
5
+ def type
6
+ self[XSD::QName.new(nil, "type")]
7
+ end
8
+
9
+ #
10
+ # XmlAny element is equal to the other xmlAny element when it
11
+ # has same elements and attributes regardles of ordering of
12
+ # elements and attributes.
13
+ #
14
+ def == other
15
+ return false if other.class != self.class
16
+ return false if @__xmlele - other.__xmlele == []
17
+ return false if @__xmlattr != other.__xmlattr
18
+ return true
19
+ end
20
+ end
21
+
22
+ class OpenXmlObject < SOAP::Mapping::Object
23
+ def method_missing(sym, *args, &block)
24
+
25
+ safename = XSD::CodeGen::GenSupport.safemethodname(sym.to_s).to_sym
26
+
27
+ if safename != sym and self.respond_to?(safename)
28
+ return self.send(safename, *args, &block)
29
+ end
30
+
31
+ if sym.to_s[-1..-1] == "=" then
32
+ if sym.to_s[0..7] == "xmlattr_"
33
+ #attribute
34
+ name = sym.to_s[8..-2]
35
+ __add_xmlattr_from_method(name, args[0])
36
+ else
37
+ #element
38
+ __add_xmlele_from_method(sym.to_s[0..-2], args[0])
39
+ end
40
+ else
41
+ super(sym, *args, &block)
42
+ end
43
+ end
44
+ def id=(value)
45
+ __add_xmlele_from_method("id", value)
46
+ end
47
+ def type=(value)
48
+ __add_xmlele_from_method("type",value)
49
+ end
50
+ private
51
+ def __add_xmlattr_from_method(name, value)
52
+ @__xmlattr[XSD::QName.new(nil, name)] = value
53
+ self.instance_eval <<-EOS
54
+ def xmlattr_#{name}
55
+ @__xmlattr[XSD::QName.new(nil, '#{name}')]
56
+ end
57
+
58
+ def xmlattr_#{name}=(value)
59
+ @__xmlattr[XSD::QName.new(nil, '#{name}')] = value
60
+ end
61
+ EOS
62
+ end
63
+ def __add_xmlele_from_method(name, value)
64
+ Thread.current[:SOAPMapping] ||= {}
65
+ Thread.current[:SOAPMapping][:SafeMethodName] ||= {}
66
+ __add_xmlele_value(XSD::QName.new(nil, name),value)
67
+ end
68
+ end
@@ -0,0 +1,199 @@
1
+ # TODO docs
2
+ require 'set'
3
+ require 'mappum'
4
+ require 'ostruct'
5
+ require 'mappum/autoconv_catalogue'
6
+
7
+ module Mappum
8
+ #
9
+ # Main class handling transformation of ruby to ruby objects.
10
+ # This class is a base for other transformations in Mappum.
11
+ #
12
+ class RubyTransform
13
+ attr_accessor :map_catalogue
14
+
15
+ def initialize(map_catalogue=nil, default_struct_class=nil, force_open_struct=false)
16
+ @map_catalogue = map_catalogue if map_catalogue.kind_of?(Mappum::Map)
17
+ @map_catalogue ||= Mappum.catalogue(map_catalogue)
18
+ @autoconv_map_catalogue = Mappum.catalogue("MAPPUM_AUTOCONV")
19
+ @default_struct_class = default_struct_class
20
+ @force_open_struct = force_open_struct
21
+ @default_struct_class ||= Mappum::OpenStruct;
22
+ end
23
+ #
24
+ # Method for transforming from object using map to "to" object.
25
+ #
26
+ def transform(from, map=nil, to=nil)
27
+
28
+ raise RuntimeError.new("Map catalogue is empty!") if @map_catalogue.nil?
29
+
30
+ map ||= @map_catalogue[from.class]
31
+
32
+ map = @map_catalogue[map] if map.kind_of?(Symbol) or map.kind_of?(String)
33
+
34
+ raise MapMissingException.new(from) if map.nil?
35
+
36
+ # skip mapping on false :map_when function
37
+ return to unless map.map_when.nil? or map.map_when.call(from)
38
+
39
+ all_nils = true
40
+ map.maps.each do |sm|
41
+ from_value, to_value = nil, nil
42
+
43
+ if sm.from.respond_to?(:name)
44
+ from_value = get(from, sm.from.name)
45
+ else
46
+ from_value = sm.from.value
47
+ end
48
+
49
+ # skip to next mapping on false :map_when function
50
+ next unless sm.map_when.nil? or sm.map_when.call(from_value)
51
+
52
+ unless sm.func.nil? or (not sm.func_on_nil? and from_value.nil?)
53
+ from_value = sm.func.call(from_value)
54
+ end
55
+ unless sm.from.func.nil? or from_value.nil?
56
+ mappum_block = sm.from.block
57
+ if from_value.kind_of?(Array)
58
+ # TODO Fix it for JavaArrays
59
+ #or (Module.constants.include? "ArrayJavaProxy" and from_value.kind_of?(Module.const_get(:ArrayJavaProxy)))
60
+ from_value = from_value.compact.instance_eval(sm.from.func)
61
+ else
62
+ from_value = from_value.instance_eval(sm.from.func)
63
+ end
64
+ end
65
+
66
+ submaps = sm.maps
67
+ if sm.maps.empty?
68
+ unless sm.submap_alias.nil? or sm.submap_alias.empty?
69
+ submaps = @map_catalogue[sm.submap_alias].maps
70
+ end
71
+ if sm.to.respond_to?(:clazz) and sm.from.respond_to?(:clazz) and sm.from.clazz != sm.to.clazz
72
+ sub = @map_catalogue[sm.from.clazz,sm.to.clazz]
73
+ sub ||= @autoconv_map_catalogue[sm.from.clazz,sm.to.clazz]
74
+ unless sub.nil?
75
+ submaps = sub.maps
76
+ else
77
+ to_value = from_value
78
+ end
79
+ else
80
+ to_value = from_value
81
+ end
82
+ end
83
+ unless submaps.empty? or from_value.nil?
84
+ if from_value.kind_of?(Array) or
85
+ (Module.constants.include? "ArrayJavaProxy" and from_value.kind_of?(Module.const_get(:ArrayJavaProxy)))
86
+ sm_v = sm.clone
87
+ if sm_v.from.is_array
88
+ sm_v.from = sm.from.clone
89
+ sm_v.from.is_array = false
90
+ end
91
+ if sm_v.to.is_array
92
+ sm_v.to = sm.to.clone
93
+ sm_v.to.is_array = false
94
+ end
95
+ sm_v.maps = submaps if sm_v.maps.empty?
96
+
97
+ to_value = from_value.collect{|v| transform(v, sm_v)}
98
+ else
99
+ to ||= map.to.clazz.new unless @force_open_struct or map.to.clazz.nil? or map.to.clazz.kind_of?(Symbol)
100
+ to ||= @default_struct_class.new
101
+ v_to = nil
102
+ #array values are assigned after return
103
+ v_to = get(to, sm.to.name) unless sm.to.is_array and not sm.from.is_array
104
+ sm_v = sm
105
+ if sm_v.maps.empty?
106
+ sm_v = sm.clone
107
+ sm_v.maps = submaps
108
+ end
109
+ to_value = transform(from_value, sm_v, v_to)
110
+ end
111
+
112
+ end
113
+ unless sm.dict.nil?
114
+ to_value = sm.dict[to_value]
115
+ end
116
+ if sm.to.is_array and not sm.from.is_array
117
+ to_array = convert_from(get(to,sm.to.name),sm.from)
118
+ to_array ||= []
119
+ to_array << to_value
120
+
121
+ if to_array.empty? and sm.strip_empty?
122
+ to_array = nil
123
+ end
124
+
125
+ all_nils = false unless to_array.nil?
126
+
127
+ if sm.to.name.nil?
128
+ to = convert_to(to_array, sm.to)
129
+ else
130
+ to ||= map.to.clazz.new unless @force_open_struct or map.to.clazz.nil? or map.to.clazz.kind_of?(Symbol)
131
+ to ||= @default_struct_class.new
132
+ to.send("#{sm.to.name}=", convert_to(to_array, sm.to)) unless to_array.nil?
133
+ end
134
+ else
135
+
136
+ if to_value.respond_to?(:empty?) and to_value.empty? and sm.strip_empty?
137
+ to_value = nil
138
+ end
139
+
140
+ all_nils = false unless to_value.nil?
141
+
142
+ if sm.to.name.nil?
143
+ to ||= to_value
144
+ else
145
+ to ||= map.to.clazz.new unless @force_open_struct or map.to.clazz.nil? or map.to.clazz.kind_of?(Symbol)
146
+ to ||= @default_struct_class.new
147
+ to.send("#{sm.to.name}=", convert_to(to_value, sm.to)) unless to_value.nil?
148
+ end
149
+ end
150
+
151
+ end
152
+ if all_nils and map.strip_empty?
153
+ return nil
154
+ end
155
+ return to
156
+ end
157
+
158
+ protected
159
+
160
+ def get(object, field)
161
+ if field.nil? or object.nil?
162
+ return object
163
+ elsif
164
+ begin
165
+ return object.send(field)
166
+ rescue NoMethodError => e
167
+ #for open structures field will be defined later
168
+ if object.kind_of?(@default_struct_class)
169
+ return nil
170
+ else
171
+ raise e
172
+ end
173
+ end
174
+ end
175
+ end
176
+ def convert_to(to, field_def)
177
+ return to
178
+ end
179
+ def convert_from(from, field_def)
180
+ return from
181
+ end
182
+ end
183
+ class OpenStruct < OpenStruct
184
+ def type(*attr)
185
+ method_missing(:type, *attr)
186
+ end
187
+ def id(*attr)
188
+ method_missing(:id, *attr)
189
+ end
190
+ end
191
+ class MapMissingException < RuntimeError
192
+ attr_accessor :from
193
+ def initialize(from, msg=nil)
194
+ msg ||= "Map for class \"#{from.class}\" not found!"
195
+ super(msg)
196
+ @from = from
197
+ end
198
+ end
199
+ end