ruby_odata 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -1
- data/CHANGELOG.rdoc +46 -38
- data/Gemfile +4 -0
- data/LICENSE +24 -24
- data/README.rdoc +179 -153
- data/Rakefile +10 -28
- data/features/basic_auth.feature +24 -0
- data/features/batch_request.feature +94 -94
- data/features/complex_types.feature +52 -52
- data/features/query_builder.feature +153 -153
- data/features/service.feature +49 -49
- data/features/service_manage.feature +54 -44
- data/features/step_definitions/service_steps.rb +255 -243
- data/features/support/env.rb +14 -4
- data/features/support/hooks.rb +3 -3
- data/features/type_conversion.feature +53 -45
- data/lib/ruby_odata.rb +17 -17
- data/lib/ruby_odata/class_builder.rb +117 -109
- data/lib/ruby_odata/operation.rb +17 -17
- data/lib/ruby_odata/query_builder.rb +106 -106
- data/lib/ruby_odata/service.rb +359 -339
- data/lib/ruby_odata/version.rb +3 -0
- data/ruby_odata.gemspec +24 -78
- data/test/SampleService/App_Code/Entities.cs +105 -2
- data/test/SampleService/App_Code/Model.Designer.cs +24 -0
- data/test/SampleService/App_Code/Model.edmx +3 -0
- data/test/SampleService/App_Data/_TestDB.mdf +0 -0
- data/test/SampleService/App_Data/_TestDB_Log.ldf +0 -0
- data/test/SampleService/BasicAuth/Entities.svc +1 -0
- data/test/SampleService/web.config +11 -1
- data/test/blueprints.rb +20 -20
- data/test/iisExpress x64.bat +1 -0
- data/test/iisExpress x86.bat +1 -0
- metadata +88 -14
- data/VERSION +0 -1
data/lib/ruby_odata/service.rb
CHANGED
@@ -1,348 +1,368 @@
|
|
1
1
|
require 'logger'
|
2
|
+
require 'base64'
|
2
3
|
|
3
4
|
module OData
|
4
|
-
|
5
|
+
|
5
6
|
class Service
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
7
|
+
attr_reader :classes
|
8
|
+
# Creates a new instance of the Service class
|
9
|
+
#
|
10
|
+
# ==== Required Attributes
|
11
|
+
# - service_uri: The root URI of the OData service
|
12
|
+
def initialize(service_uri, options = {})
|
13
|
+
@uri = service_uri
|
14
|
+
if not options[:username].nil?
|
15
|
+
@auth_header = "Basic " + Base64.encode64( options[:username] + ":" + (options[:password] || "") )
|
16
|
+
@http_headers = {:Authorization => @auth_header}
|
17
|
+
else
|
18
|
+
@http_headers = {}
|
19
|
+
end
|
20
|
+
@collections = get_collections
|
21
|
+
@save_operations = []
|
22
|
+
build_classes
|
23
|
+
end
|
24
|
+
|
25
|
+
# Handles the dynamic AddTo<EntityName> methods as well as the collections on the service
|
26
|
+
def method_missing(name, *args)
|
27
|
+
# Queries
|
28
|
+
if @collections.include?(name.to_s)
|
29
|
+
root = "/#{name.to_s.camelize}"
|
30
|
+
root << "(#{args.join(',')})" unless args.empty?
|
31
|
+
@query = QueryBuilder.new(root)
|
32
|
+
return @query
|
33
|
+
# Adds
|
34
|
+
elsif name.to_s =~ /^AddTo(.*)/
|
35
|
+
type = $1
|
36
|
+
if @collections.include?(type)
|
37
|
+
@save_operations << Operation.new("Add", $1, args[0])
|
38
|
+
else
|
39
|
+
super
|
40
|
+
end
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# Queues an object for deletion. To actually remove it from the server, you must call save_changes as well.
|
48
|
+
#
|
49
|
+
# ==== Required Attributes
|
50
|
+
# - obj: The object to mark for deletion
|
51
|
+
#
|
52
|
+
# Note: This method will throw an exception if the +obj+ isn't a tracked entity
|
53
|
+
def delete_object(obj)
|
54
|
+
type = obj.class.to_s
|
55
|
+
if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil?
|
56
|
+
@save_operations << Operation.new("Delete", type, obj)
|
57
|
+
else
|
58
|
+
raise "You cannot delete a non-tracked entity"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Queues an object for update. To actually update it on the server, you must call save_changes as well.
|
63
|
+
#
|
64
|
+
# ==== Required Attributes
|
65
|
+
# - obj: The object to queue for update
|
66
|
+
#
|
67
|
+
# Note: This method will throw an exception if the +obj+ isn't a tracked entity
|
68
|
+
def update_object(obj)
|
69
|
+
type = obj.class.to_s
|
70
|
+
if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil?
|
71
|
+
@save_operations << Operation.new("Update", type, obj)
|
72
|
+
else
|
73
|
+
raise "You cannot update a non-tracked entity"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Performs save operations (Create/Update/Delete) against the server
|
78
|
+
def save_changes
|
79
|
+
return nil if @save_operations.empty?
|
80
|
+
|
81
|
+
result = nil
|
82
|
+
|
83
|
+
if @save_operations.length == 1
|
84
|
+
result = single_save(@save_operations[0])
|
85
|
+
else
|
86
|
+
result = batch_save(@save_operations)
|
87
|
+
end
|
88
|
+
|
89
|
+
# TODO: We should probably perform a check here
|
90
|
+
# to make sure everything worked before clearing it out
|
91
|
+
@save_operations.clear
|
92
|
+
|
93
|
+
return result
|
94
|
+
end
|
95
|
+
|
96
|
+
# Performs query operations (Read) against the server
|
97
|
+
def execute
|
98
|
+
result = RestClient.get build_query_uri, @http_headers
|
99
|
+
build_classes_from_result(result)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Overridden to identify methods handled by method_missing
|
103
|
+
def respond_to?(method)
|
104
|
+
if @collections.include?(method.to_s)
|
105
|
+
return true
|
106
|
+
# Adds
|
107
|
+
elsif method.to_s =~ /^AddTo(.*)/
|
108
|
+
type = $1
|
109
|
+
if @collections.include?(type)
|
110
|
+
return true
|
111
|
+
else
|
112
|
+
super
|
113
|
+
end
|
114
|
+
else
|
115
|
+
super
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
# Wrapper around open call to make http request
|
121
|
+
def http_open( address )
|
122
|
+
if not @auth_header.nil?
|
123
|
+
return open(address, "Authorization" => @auth_header)
|
124
|
+
else
|
125
|
+
return open(address)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Retrieves collections from the main service page
|
130
|
+
def get_collections
|
131
|
+
doc = Nokogiri::XML(http_open(@uri))
|
132
|
+
collections = doc.xpath("//app:collection", "app" => "http://www.w3.org/2007/app")
|
133
|
+
collections.collect { |c| c["href"] }
|
134
|
+
end
|
135
|
+
|
136
|
+
# Build the classes required by the metadata
|
137
|
+
def build_classes
|
138
|
+
@classes = Hash.new
|
139
|
+
doc = Nokogiri::XML(http_open("#{@uri}/$metadata"))
|
140
|
+
|
141
|
+
# Get the edm namespace
|
142
|
+
edm_ns = doc.xpath("edmx:Edmx/edmx:DataServices/*", "edmx" => "http://schemas.microsoft.com/ado/2007/06/edmx").first.namespaces['xmlns'].to_s
|
143
|
+
|
144
|
+
# Build complex types first, these will be used for entities
|
145
|
+
complex_types = doc.xpath("//edm:ComplexType", "edm" => edm_ns) || []
|
146
|
+
complex_types.each do |c|
|
147
|
+
name = c['Name']
|
148
|
+
props = c.xpath(".//edm:Property", "edm" => edm_ns)
|
149
|
+
methods = props.collect { |p| p['Name'] } # Standard Properties
|
150
|
+
@classes[name] = ClassBuilder.new(name, methods, []).build unless @classes.keys.include?(name)
|
151
|
+
end
|
152
|
+
|
153
|
+
entity_types = doc.xpath("//edm:EntityType", "edm" => edm_ns)
|
154
|
+
entity_types.each do |e|
|
155
|
+
name = e['Name']
|
156
|
+
props = e.xpath(".//edm:Property", "edm" => edm_ns)
|
157
|
+
methods = props.collect { |p| p['Name'] } # Standard Properties
|
158
|
+
nprops = e.xpath(".//edm:NavigationProperty", "edm" => edm_ns)
|
159
|
+
nav_props = nprops.collect { |p| p['Name'] } # Standard Properties
|
160
|
+
@classes[name] = ClassBuilder.new(name, methods, nav_props).build unless @classes.keys.include?(name)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Helper to loop through a result and create an instance for each entity in the results
|
165
|
+
def build_classes_from_result(result)
|
166
|
+
doc = Nokogiri::XML(result)
|
167
|
+
entries = doc.xpath("//atom:entry[not(ancestor::atom:entry)]", "atom" => "http://www.w3.org/2005/Atom")
|
168
|
+
return entry_to_class(entries[0]) if entries.length == 1
|
169
|
+
|
170
|
+
results = []
|
171
|
+
entries.each do |entry|
|
172
|
+
results << entry_to_class(entry)
|
173
|
+
end
|
174
|
+
return results
|
175
|
+
end
|
176
|
+
|
177
|
+
# Converts an XML Entry into a class
|
178
|
+
def entry_to_class(entry)
|
179
|
+
# Retrieve the class name from the fully qualified name (the last string after the last dot)
|
180
|
+
klass_name = entry.xpath("./atom:category/@term", "atom" => "http://www.w3.org/2005/Atom").to_s.split('.')[-1]
|
181
|
+
return nil if klass_name.empty?
|
182
|
+
|
183
|
+
properties = entry.xpath(".//m:properties/*", { "m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" })
|
184
|
+
|
185
|
+
klass = @classes[klass_name].new
|
186
|
+
|
187
|
+
# Fill metadata
|
188
|
+
meta_id = entry.xpath("./atom:id", "atom" => "http://www.w3.org/2005/Atom")[0].content
|
189
|
+
klass.send :__metadata=, { :uri => meta_id }
|
190
|
+
|
191
|
+
# Fill properties
|
192
|
+
for prop in properties
|
193
|
+
prop_name = prop.name
|
194
|
+
klass.send "#{prop_name}=", parse_value(prop)
|
195
|
+
end
|
196
|
+
|
197
|
+
inline_links = entry.xpath("./atom:link[m:inline]", { "m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata", "atom" => "http://www.w3.org/2005/Atom" })
|
198
|
+
|
199
|
+
for link in inline_links
|
200
|
+
inline_entries = link.xpath(".//atom:entry", "atom" => "http://www.w3.org/2005/Atom")
|
201
|
+
|
202
|
+
if inline_entries.length == 1
|
203
|
+
property_name = link.attributes['title'].to_s
|
204
|
+
|
205
|
+
build_inline_class(klass, inline_entries[0], property_name)
|
190
206
|
else
|
191
207
|
# TODO: Test handling multiple children
|
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
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
208
|
+
for inline_entry in inline_entries
|
209
|
+
property_name = link.xpath("atom:link[@rel='edit']/@title", "atom" => "http://www.w3.org/2005/Atom")
|
210
|
+
|
211
|
+
# Build the class
|
212
|
+
inline_klass = entry_to_class(inline_entry)
|
213
|
+
|
214
|
+
# Add the property
|
215
|
+
klass.send "#{property_name}=", inline_klass
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
return klass
|
221
|
+
end
|
222
|
+
|
223
|
+
def build_query_uri
|
224
|
+
"#{@uri}#{@query.query}"
|
225
|
+
end
|
226
|
+
def build_inline_class(klass, entry, property_name)
|
227
|
+
# Build the class
|
228
|
+
inline_klass = entry_to_class(entry)
|
229
|
+
|
230
|
+
# Add the property
|
231
|
+
klass.send "#{property_name}=", inline_klass
|
232
|
+
end
|
233
|
+
def single_save(operation)
|
234
|
+
if operation.kind == "Add"
|
235
|
+
save_uri = "#{@uri}/#{operation.klass_name}"
|
236
|
+
json_klass = operation.klass.to_json(:type => :add)
|
237
|
+
post_result = RestClient.post save_uri, json_klass, {:content_type => :json}.merge(@http_headers)
|
238
|
+
return build_classes_from_result(post_result)
|
239
|
+
elsif operation.kind == "Update"
|
240
|
+
update_uri = operation.klass.send(:__metadata)[:uri]
|
241
|
+
json_klass = operation.klass.to_json
|
242
|
+
update_result = RestClient.put update_uri, json_klass, {:content_type => :json}.merge(@http_headers)
|
243
|
+
return (update_result.code == 204)
|
244
|
+
elsif operation.kind == "Delete"
|
245
|
+
delete_uri = operation.klass.send(:__metadata)[:uri]
|
246
|
+
delete_result = RestClient.delete delete_uri, @http_headers
|
247
|
+
return (delete_result.code == 204)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Batch Saves
|
252
|
+
def generate_guid
|
253
|
+
rand(36**12).to_s(36).insert(4, "-").insert(9, "-")
|
254
|
+
end
|
255
|
+
def batch_save(operations)
|
256
|
+
batch_num = generate_guid
|
257
|
+
changeset_num = generate_guid
|
258
|
+
batch_uri = "#{@uri}/$batch"
|
259
|
+
|
260
|
+
body = build_batch_body(operations, batch_num, changeset_num)
|
261
|
+
|
262
|
+
result = RestClient.post batch_uri, body, {:content_type => "multipart/mixed; boundary=batch_#{batch_num}"}.merge(@http_headers)
|
263
|
+
|
264
|
+
# TODO: More result validation needs to be done.
|
265
|
+
# The result returns HTTP 202 even if there is an error in the batch
|
266
|
+
return (result.code == 202)
|
267
|
+
end
|
268
|
+
def build_batch_body(operations, batch_num, changeset_num)
|
269
|
+
# Header
|
270
|
+
body = "--batch_#{batch_num}\n"
|
271
|
+
body << "Content-Type: multipart/mixed;boundary=changeset_#{changeset_num}\n\n"
|
272
|
+
|
273
|
+
# Operations
|
274
|
+
operations.each do |operation|
|
275
|
+
body << build_batch_operation(operation, changeset_num)
|
276
|
+
body << "\n"
|
277
|
+
end
|
278
|
+
|
279
|
+
# Footer
|
280
|
+
body << "\n\n--changeset_#{changeset_num}--\n"
|
281
|
+
body << "--batch_#{batch_num}--"
|
282
|
+
|
283
|
+
return body
|
284
|
+
end
|
285
|
+
def build_batch_operation(operation, changeset_num)
|
286
|
+
accept_headers = "Accept-Charset: utf-8\n"
|
287
|
+
accept_headers << "Content-Type: application/json;charset=utf-8\n" unless operation.kind == "Delete"
|
288
|
+
accept_headers << "\n"
|
289
|
+
|
290
|
+
content = "--changeset_#{changeset_num}\n"
|
291
|
+
content << "Content-Type: application/http\n"
|
292
|
+
content << "Content-Transfer-Encoding: binary\n\n"
|
293
|
+
|
294
|
+
if operation.kind == "Add"
|
295
|
+
save_uri = "#{@uri}/#{operation.klass_name}"
|
296
|
+
json_klass = operation.klass.to_json(:type => :add)
|
297
|
+
|
298
|
+
content << "POST #{save_uri} HTTP/1.1\n"
|
299
|
+
content << accept_headers
|
300
|
+
content << json_klass
|
301
|
+
elsif operation.kind == "Update"
|
302
|
+
update_uri = operation.klass.send(:__metadata)[:uri]
|
303
|
+
json_klass = operation.klass.to_json
|
304
|
+
|
305
|
+
content << "PUT #{update_uri} HTTP/1.1\n"
|
306
|
+
content << accept_headers
|
307
|
+
content << json_klass
|
308
|
+
elsif operation.kind == "Delete"
|
309
|
+
delete_uri = operation.klass.send(:__metadata)[:uri]
|
310
|
+
|
311
|
+
content << "DELETE #{delete_uri} HTTP/1.1\n"
|
312
|
+
content << accept_headers
|
313
|
+
end
|
314
|
+
|
315
|
+
return content
|
316
|
+
end
|
317
|
+
|
318
|
+
# Complex Types
|
319
|
+
def complex_type_to_class(complex_type_xml)
|
320
|
+
klass_name = complex_type_xml.attr('type').split('.')[-1]
|
321
|
+
klass = @classes[klass_name].new
|
322
|
+
|
323
|
+
# Fill in the properties
|
324
|
+
properties = complex_type_xml.xpath(".//*")
|
325
|
+
properties.each do |prop|
|
326
|
+
klass.send "#{prop.name}=", parse_value(prop)
|
327
|
+
end
|
328
|
+
|
329
|
+
return klass
|
330
|
+
end
|
331
|
+
|
332
|
+
# Field Converters
|
333
|
+
def parse_value(property_xml)
|
334
|
+
property_type = property_xml.attr('type')
|
335
|
+
property_null = property_xml.attr('null')
|
336
|
+
|
337
|
+
# Handle a nil property type, this is a string
|
338
|
+
return property_xml.content if property_type.nil?
|
339
|
+
|
340
|
+
# Handle anything marked as null
|
341
|
+
return nil if !property_null.nil? && property_null == "true"
|
342
|
+
|
343
|
+
# Handle complex types
|
344
|
+
return complex_type_to_class(property_xml) if !property_type.match(/^Edm/)
|
345
|
+
|
346
|
+
# Handle integers
|
347
|
+
return property_xml.content.to_i if property_type.match(/^Edm.Int/)
|
348
|
+
|
349
|
+
# Handle decimals
|
350
|
+
return property_xml.content.to_d if property_type.match(/Edm.Decimal/)
|
351
|
+
|
352
|
+
# Handle DateTimes
|
353
|
+
# return Time.parse(property_xml.content) if property_type.match(/Edm.DateTime/)
|
354
|
+
if property_type.match(/Edm.DateTime/)
|
355
|
+
sdate = property_xml.content
|
356
|
+
|
357
|
+
# Assume this is UTC if no timezone is specified
|
358
|
+
sdate = sdate + "Z" unless sdate.match(/Z|([+|-]\d{2}:\d{2})$/)
|
359
|
+
|
360
|
+
return Time.parse(sdate)
|
361
|
+
end
|
362
|
+
|
363
|
+
# If we can't parse the value, just return the element's content
|
364
|
+
property_xml.content
|
365
|
+
end
|
346
366
|
|
347
367
|
end
|
348
368
|
|