ruby_odata 0.0.7 → 0.0.8
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.
- 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
|
|