ruby_odata 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,10 +23,16 @@
23
23
 
24
24
  === 0.0.5
25
25
  * Bug Fixes
26
- * Works with Ruby 1.9.1
27
- * Works with ActiveSupport 3.0.0.beta4
28
-
26
+ * Works with Ruby 1.9.1
27
+ * Works with ActiveSupport 3.0.0.beta4
28
+
29
29
  === 0.0.6
30
30
  * New Features
31
- * Ability to batch saves (Adds, Updates, Deletes); this will help save on network chatter
32
-
31
+ * Ability to batch saves (Adds, Updates, Deletes); this will help save on network chatter
32
+
33
+ === 0.0.7
34
+ * New Features
35
+ * Complex Types are now supported
36
+ * Support for Edm.Int16, Edm.Int32, Edm.Int64
37
+ * Support for Edm.Decimal
38
+ * Support for Edm.DateTime
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.7
@@ -0,0 +1,52 @@
1
+ Feature: Complex types
2
+ In order to fully support OData services
3
+ As a user of ruby_odata
4
+ I want to be able to manage objects with complex types
5
+
6
+ Background:
7
+ Given an ODataService exists with uri: "http://localhost:8888/SampleService/Entities.svc"
8
+ And blueprints exist for the service
9
+
10
+ Scenario: The proxy must generate classes for complex types if they exist
11
+ Then a class named "AuditFields" should exist
12
+
13
+ Scenario: Complex properties on an entity must be the correct type
14
+ Given I call "AddToProducts" on the service with a new "Product" object
15
+ And I save changes
16
+ And I call "Products" on the service with args: "1"
17
+ When I run the query
18
+ Then the result should have a method: "AuditFields"
19
+ And the method "AuditFields" on the result should be of type "AuditFields"
20
+
21
+ Scenario: Complex properties on an entity must be filled
22
+ Given I call "AddToProducts" on the service with a new "Product" object
23
+ And I save changes
24
+ And I call "Products" on the service with args: "1"
25
+ When I run the query
26
+ Then the result should have a method: "AuditFields"
27
+ When I call "CreateDate" for "AuditFields" on the result
28
+ Then the operation should not be null
29
+
30
+ # TODO: This scenario should have the AuditFields.CreatedBy field set in the Given
31
+ # instead it is set by the blueprint
32
+ Scenario: Complex properties should be able to be added
33
+ Given I call "AddToProducts" on the service with a new "Product" object
34
+ And I save changes
35
+ And I call "Products" on the service with args: "1"
36
+ When I run the query
37
+ Then the method "CreatedBy" on the result's method "AuditFields" should equal: "Cucumber"
38
+
39
+ Scenario: Complex propertis should be able to be updated
40
+ Given I call "AddToProducts" on the service with a new "Product" object
41
+ And I save changes
42
+ And I call "Products" on the service with args: "1"
43
+ When I run the query
44
+ When I set "CreatedBy" on the result's method "AuditFields" to "This Test"
45
+ And I call "update_object" on the service with the last query result
46
+ And I save changes
47
+ Then the save result should equal: "true"
48
+ When I call "Products" on the service with args: "1"
49
+ And I run the query
50
+ Then the method "CreatedBy" on the result's method "AuditFields" should equal: "This Test"
51
+
52
+
@@ -5,7 +5,7 @@ Feature: Service Should Generate a Proxy
5
5
 
6
6
  Background:
7
7
  Given an ODataService exists with uri: "http://localhost:8888/SampleService/Entities.svc"
8
- And blueprints exist for the service
8
+ And blueprints exist for the service
9
9
 
10
10
  Scenario: Service should respond to valid collections
11
11
  Then I should be able to call "Categories" on the service
@@ -18,14 +18,14 @@ Scenario: Service should respond to accessing a single entity by ID
18
18
 
19
19
  Scenario: Access an entity by ID should return the entity type
20
20
  Given I call "AddToCategories" on the service with a new "Category" object with Name: "Test Category"
21
- And I save changes
21
+ And I save changes
22
22
  And I call "Categories" on the service with args: "1"
23
23
  When I run the query
24
24
  Then the result should be of type "Category"
25
25
 
26
26
  Scenario: Entity should have the correct accessors
27
27
  Given I call "AddToCategories" on the service with a new "Category" object with Name: "Test Category"
28
- And I save changes
28
+ And I save changes
29
29
  And I call "Categories" on the service with args: "1"
30
30
  When I run the query
31
31
  Then the result should have a method: "Id"
@@ -33,7 +33,7 @@ Scenario: Entity should have the correct accessors
33
33
 
34
34
  Scenario: Entity should fill values
35
35
  Given I call "AddToCategories" on the service with a new "Category" object with Name: "Test Category"
36
- And I save changes
36
+ And I save changes
37
37
  And I call "Categories" on the service with args: "1"
38
38
  When I run the query
39
39
  Then the method "Id" on the result should equal: "1"
@@ -41,7 +41,7 @@ Scenario: Entity should fill values
41
41
 
42
42
  Scenario: Navigation Properties should be included in results
43
43
  Given I call "AddToProducts" on the service with a new "Product" object
44
- And I save changes
44
+ And I save changes
45
45
  And I call "Products" on the service with args: "1"
46
46
  When I run the query
47
47
  Then the result should have a method: "Category"
@@ -39,7 +39,7 @@ Then /^the result should have a method: "([^\"]*)"$/ do |method|
39
39
  end
40
40
 
41
41
  Then /^the method "([^\"]*)" on the result should equal: "([^\"]*)"$/ do |method, value|
42
- @service_result.send(method.to_sym).should == value
42
+ @service_result.send(method.to_sym).to_s.should == value
43
43
  end
44
44
 
45
45
  Then /^the method "([^\"]*)" on the result should be nil$/ do |method|
@@ -47,7 +47,7 @@ Then /^the method "([^\"]*)" on the result should be nil$/ do |method|
47
47
  end
48
48
 
49
49
  When /^I set "([^\"]*)" on the result to "([^\"]*)"$/ do |property_name, value|
50
- @service_result.send("#{property_name}=", value)
50
+ @service_result.send("#{property_name}=", value)
51
51
  end
52
52
 
53
53
  Given /^I expand the query to include "([^\"]*)"$/ do |expands|
@@ -67,7 +67,7 @@ When /^I skip (\d+)$/ do |skip|
67
67
  end
68
68
 
69
69
  When /^I ask for the top (\d+)$/ do |top|
70
- @service_query.top(top)
70
+ @service_query.top(top)
71
71
  end
72
72
 
73
73
  Then /^the method "([^\"]*)" on the result should be of type "([^\"]*)"$/ do |method, type|
@@ -79,18 +79,17 @@ Given /^I call "([^\"]*)" on the service with a new "([^\"]*)" object(?: with (.
79
79
  fields_hash = {}
80
80
 
81
81
  if !fields.nil?
82
- fields.split(', ').each do |field|
83
- if field =~ /^(?:(\w+): "(.*)")$/
84
- key = $1
85
- val = $2
86
- if val =~ /^@@LastSave$/
87
- val = @saved_result
88
- end
89
-
90
- fields_hash.merge!({ key => val })
91
- else
92
- end
93
- end
82
+ fields.split(', ').each do |field|
83
+ if field =~ /^(?:(\w+): "(.*)")$/
84
+ key = $1
85
+ val = $2
86
+ if val =~ /^@@LastSave$/
87
+ val = @saved_result
88
+ end
89
+
90
+ fields_hash.merge!({ key => val })
91
+ end
92
+ end
94
93
  end
95
94
 
96
95
  obj = object.constantize.send(:make, fields_hash)
@@ -102,7 +101,7 @@ When /^I save changes$/ do
102
101
  end
103
102
 
104
103
  Then /^the save result should be of type "([^\"]*)"$/ do |type|
105
- @saved_result.class.to_s.should == type
104
+ @saved_result.class.to_s.should == type
106
105
  end
107
106
 
108
107
  When /^I call "([^\"]*)" on the service with the last save result$/ do |method|
@@ -119,33 +118,42 @@ end
119
118
 
120
119
  Then /^the method "([^\"]*)" on the save result should equal: "([^\"]*)"$/ do |method, value|
121
120
  result = @saved_result.send(method.to_sym)
122
- result.should == value
121
+ result.should == value
123
122
  end
124
123
 
125
124
  When /^blueprints exist for the service$/ do
126
- require File.expand_path(File.dirname(__FILE__) + "../../../test/blueprints")
125
+ require File.expand_path(File.dirname(__FILE__) + "../../../test/blueprints")
127
126
  end
128
127
 
129
128
  Given /^I call "([^\"]*)" on the service with a new "([^\"]*)" object it should throw an exception with message "([^\"]*)"$/ do |method, object, msg|
130
- obj = object.constantize.send :make
129
+ obj = object.constantize.send :make
131
130
  lambda { @service.send(method.to_sym, obj) }.should raise_error(msg)
132
131
  end
133
132
 
134
133
  Then /^no "([^\"]*)" should exist$/ do |collection|
135
- @service.send(collection)
136
- results = @service.execute
137
- results.should == []
134
+ @service.send(collection)
135
+ results = @service.execute
136
+ results.should == []
138
137
  end
139
138
 
140
- Given /^the following (.*) exist:$/ do |plural_factory, table|
141
- # table is a Cucumber::Ast::Table
142
- factory = plural_factory.singularize
143
- table.hashes.map do |hash|
144
- obj = factory.constantize.send(:make, hash)
145
- @service.send("AddTo#{plural_factory}", obj)
146
- @service.save_changes
147
- end
148
- end
139
+ Given /^the following (.*) exist:$/ do |plural_factory, table|
140
+ # table is a Cucumber::Ast::Table
141
+ factory = plural_factory.singularize
142
+ table.hashes.map do |hash|
143
+ obj = factory.constantize.send(:make, hash)
144
+ @service.send("AddTo#{plural_factory}", obj)
145
+ @service.save_changes
146
+ end
147
+ end
148
+
149
+ Given /^(\d+) (.*) exist$/ do |num, plural_factory|
150
+ factory = plural_factory.singularize
151
+ num.to_i.times do
152
+ obj = factory.constantize.send(:make)
153
+ @service.send("AddTo#{plural_factory}", obj)
154
+ end
155
+ @service.save_changes # Batch save
156
+ end
149
157
 
150
158
  Then /^the result should be:$/ do |table|
151
159
  # table is a Cucumber::Ast::Table
@@ -156,16 +164,16 @@ Then /^the result should be:$/ do |table|
156
164
  results = []
157
165
 
158
166
  @service_result.each do |result|
159
- obj_hash = Hash.new
160
- fields.each do |field|
161
- obj_hash[field] = result.send(field)
162
- end
163
- results << obj_hash
164
- end
165
-
166
- result_table = Cucumber::Ast::Table.new(results)
167
-
168
- table.diff!(result_table)
167
+ obj_hash = Hash.new
168
+ fields.each do |field|
169
+ obj_hash[field] = result.send(field)
170
+ end
171
+ results << obj_hash
172
+ end
173
+
174
+ result_table = Cucumber::Ast::Table.new(results)
175
+
176
+ table.diff!(result_table)
169
177
  end
170
178
 
171
179
  Then /^the save result should be:$/ do |table|
@@ -177,19 +185,60 @@ Then /^the save result should be:$/ do |table|
177
185
  results = []
178
186
 
179
187
  @saved_result.each do |result|
180
- obj_hash = Hash.new
181
- fields.each do |field|
182
- obj_hash[field] = result.send(field)
183
- end
184
- results << obj_hash
185
- end
186
-
187
- result_table = Cucumber::Ast::Table.new(results)
188
-
189
- table.diff!(result_table)
188
+ obj_hash = Hash.new
189
+ fields.each do |field|
190
+ obj_hash[field] = result.send(field)
191
+ end
192
+ results << obj_hash
193
+ end
194
+
195
+ result_table = Cucumber::Ast::Table.new(results)
196
+
197
+ table.diff!(result_table)
190
198
  end
191
199
 
200
+ Then /^a class named "([^\"]*)" should exist$/ do |klass_name|
201
+ (Object.const_defined? klass_name).should == true
202
+ end
203
+
204
+ # Operations against a method on the service result
205
+ When /^I call "([^\"]*)" for "([^\"]*)" on the result$/ do |method2, method1|
206
+ r1 = @service_result.send(method1)
207
+ @operation_result = r1.send(method2)
208
+ end
209
+
210
+ Then /^the operation should not be null$/ do
211
+ @operation_result.nil?.should == false
212
+ end
213
+
214
+
192
215
  Then /^the method "([^\"]*)" on the result's method "([^\"]*)" should equal: "([^\"]*)"$/ do |method, result_method, value|
193
216
  obj = @service_result.send(result_method.to_sym)
194
- obj.send(method.to_sym).should == value
217
+ obj.send(method.to_sym).to_s.should == value
218
+ end
219
+
220
+ When /^I set "([^\"]*)" on the result's method "([^\"]*)" to "([^\"]*)"$/ do |property_name, result_method, value|
221
+ @service_result.send(result_method).send("#{property_name}=", value)
195
222
  end
223
+
224
+ # Type tests
225
+ Then /^the "([^\"]*)" method should return a (.*)/ do |method_name, type|
226
+ methods = method_name.split '.'
227
+ if methods.length == 1
228
+ @service_result.send(method_name).class.to_s.should == type
229
+ else
230
+ @service_result.send(methods[0]).send(methods[1]).class.to_s.should == type
231
+ end
232
+
233
+ end
234
+ Then /^I store the last query result for comparison$/ do
235
+ @stored_query_result = @service_result
236
+ end
237
+ Then /^the new query result's time "([^\"]*)" should equal the saved query result$/ do |method_name|
238
+ methods = method_name.split '.'
239
+ if methods.length == 1
240
+ @service_result.send(method_name).xmlschema(3).should == @stored_query_result.send(method_name).xmlschema(3)
241
+ else
242
+ @service_result.send(methods[0]).send(methods[1]).xmlschema(3).should == @stored_query_result.send(methods[0]).send(methods[1]).xmlschema(3)
243
+ end
244
+ end
@@ -0,0 +1,45 @@
1
+ Feature: Type conversion
2
+ In order to accurately perform operations
3
+ As a user of the API
4
+ I want types returned to be accurately represented
5
+
6
+ Background:
7
+ Given an ODataService exists with uri: "http://localhost:8888/SampleService/Entities.svc"
8
+ And blueprints exist for the service
9
+
10
+ Scenario: Integers should be Fixnums
11
+ Given I call "AddToProducts" on the service with a new "Product" object
12
+ And I save changes
13
+ When I call "Products" on the service
14
+ And I run the query
15
+ Then the "Id" method should return a Fixnum
16
+
17
+ Scenario: Decimals should be BigDecimals
18
+ Given I call "AddToProducts" on the service with a new "Product" object
19
+ And I save changes
20
+ When I call "Products" on the service
21
+ And I run the query
22
+ Then the "Price" method should return a BigDecimal
23
+
24
+ Scenario: DateTimes should be Times
25
+ Given I call "AddToProducts" on the service with a new "Product" object
26
+ And I save changes
27
+ When I call "Products" on the service
28
+ And I run the query
29
+ Then the "AuditFields.CreateDate" method should return a Time
30
+
31
+ Scenario: Verify that DateTimes don't change if not modified on an update
32
+ Given I call "AddToProducts" on the service with a new "Product" object with Name: "Test Product"
33
+ When I save changes
34
+ And I call "Products" on the service with args: "1"
35
+ And I run the query
36
+ Then I store the last query result for comparison
37
+ When I set "Name" on the result to "Changed Test Product"
38
+ Then the method "Name" on the result should equal: "Changed Test Product"
39
+ And I call "update_object" on the service with the last query result
40
+ And I save changes
41
+ Then the save result should equal: "true"
42
+ When I call "Products" on the service with args: "1"
43
+ And I run the query
44
+ Then the new query result's time "AuditFields.CreateDate" should equal the saved query result
45
+
@@ -9,6 +9,8 @@ require 'cgi'
9
9
  require 'open-uri'
10
10
  require 'rest_client'
11
11
  require 'nokogiri'
12
+ require 'bigdecimal'
13
+ require 'bigdecimal/util'
12
14
 
13
15
  require lib + '/ruby_odata/query_builder'
14
16
  require lib + '/ruby_odata/class_builder'
@@ -64,10 +64,26 @@ module OData
64
64
  vars.delete(meta)
65
65
  end
66
66
 
67
+ # Convert a BigDecimal to a string for serialization (to match Edm.Decimal)
68
+ decimals = vars.find_all { |o| o[1].class == BigDecimal } || []
69
+ decimals.each do |d|
70
+ vars[d[0]] = d[1].to_s
71
+ end
72
+
73
+ # Convert Time to an RFC3339 string for serialization
74
+ times = vars.find_all { |o| o[1].class == Time } || []
75
+ times.each do |t|
76
+ sdate = t[1].xmlschema(3)
77
+ # Remove the ending Z (indicating UTC).
78
+ # If the Z is there when saving, the time is converted to local time on the server
79
+ sdate.chop! if sdate.match(/Z$/)
80
+ vars[t[0]] = sdate
81
+ end
82
+
67
83
  vars
68
84
  end
69
-
70
-
85
+
86
+
71
87
  # Add the methods that were passed in
72
88
  @methods.each do |method_name|
73
89
  klass.send :define_method, method_name do
@@ -78,7 +94,7 @@ module OData
78
94
  end
79
95
  end
80
96
  end
81
-
97
+
82
98
  def add_nav_props(klass)
83
99
  @nav_props.each do |method_name|
84
100
  klass.send :define_method, method_name do
@@ -86,8 +102,8 @@ module OData
86
102
  end
87
103
  klass.send :define_method, "#{method_name}=" do |value|
88
104
  instance_variable_set("@#{method_name}", value)
89
- end
105
+ end
90
106
  end
91
107
  end
92
108
  end
93
- end # module OData
109
+ end # module OData
@@ -109,12 +109,15 @@ class Service
109
109
  end
110
110
  end
111
111
 
112
- private
112
+ private
113
+ # Retrieves collections from the main service page
113
114
  def get_collections
114
115
  doc = Nokogiri::XML(open(@uri))
115
116
  collections = doc.xpath("//app:collection", "app" => "http://www.w3.org/2007/app")
116
117
  collections.collect { |c| c["href"] }
117
- end
118
+ end
119
+
120
+ # Build the classes required by the metadata
118
121
  def build_classes
119
122
  @classes = Hash.new
120
123
  doc = Nokogiri::XML(open("#{@uri}/$metadata"))
@@ -122,6 +125,15 @@ class Service
122
125
  # Get the edm namespace
123
126
  edm_ns = doc.xpath("edmx:Edmx/edmx:DataServices/*", "edmx" => "http://schemas.microsoft.com/ado/2007/06/edmx").first.namespaces['xmlns'].to_s
124
127
 
128
+ # Build complex types first, these will be used for entities
129
+ complex_types = doc.xpath("//edm:ComplexType", "edm" => edm_ns) || []
130
+ complex_types.each do |c|
131
+ name = c['Name']
132
+ props = c.xpath(".//edm:Property", "edm" => edm_ns)
133
+ methods = props.collect { |p| p['Name'] } # Standard Properties
134
+ @classes[name] = ClassBuilder.new(name, methods, []).build unless @classes.keys.include?(name)
135
+ end
136
+
125
137
  entity_types = doc.xpath("//edm:EntityType", "edm" => edm_ns)
126
138
  entity_types.each do |e|
127
139
  name = e['Name']
@@ -132,6 +144,8 @@ class Service
132
144
  @classes[name] = ClassBuilder.new(name, methods, nav_props).build unless @classes.keys.include?(name)
133
145
  end
134
146
  end
147
+
148
+ # Helper to loop through a result and create an instance for each entity in the results
135
149
  def build_classes_from_result(result)
136
150
  doc = Nokogiri::XML(result)
137
151
  entries = doc.xpath("//atom:entry[not(ancestor::atom:entry)]", "atom" => "http://www.w3.org/2005/Atom")
@@ -143,6 +157,8 @@ class Service
143
157
  end
144
158
  return results
145
159
  end
160
+
161
+ # Converts an XML Entry into a class
146
162
  def entry_to_class(entry)
147
163
  # Retrieve the class name from the fully qualified name (the last string after the last dot)
148
164
  klass_name = entry.xpath("./atom:category/@term", "atom" => "http://www.w3.org/2005/Atom").to_s.split('.')[-1]
@@ -159,8 +175,7 @@ class Service
159
175
  # Fill properties
160
176
  for prop in properties
161
177
  prop_name = prop.name
162
- # puts "#{prop_name} - #{prop.content}"
163
- klass.send "#{prop_name}=", prop.content
178
+ klass.send "#{prop_name}=", parse_value(prop)
164
179
  end
165
180
 
166
181
  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" })
@@ -188,6 +203,7 @@ class Service
188
203
 
189
204
  return klass
190
205
  end
206
+
191
207
  def build_query_uri
192
208
  "#{@uri}#{@query.query}"
193
209
  end
@@ -215,6 +231,11 @@ class Service
215
231
  return (delete_result.code == 204)
216
232
  end
217
233
  end
234
+
235
+ # Batch Saves
236
+ def generate_guid
237
+ rand(36**12).to_s(36).insert(4, "-").insert(9, "-")
238
+ end
218
239
  def batch_save(operations)
219
240
  batch_num = generate_guid
220
241
  changeset_num = generate_guid
@@ -245,7 +266,6 @@ class Service
245
266
 
246
267
  return body
247
268
  end
248
-
249
269
  def build_batch_operation(operation, changeset_num)
250
270
  accept_headers = "Accept-Charset: utf-8\n"
251
271
  accept_headers << "Content-Type: application/json;charset=utf-8\n" unless operation.kind == "Delete"
@@ -278,10 +298,52 @@ class Service
278
298
 
279
299
  return content
280
300
  end
281
-
282
- def generate_guid
283
- rand(36**12).to_s(36).insert(4, "-").insert(9, "-")
301
+
302
+ # Complex Types
303
+ def complex_type_to_class(complex_type_xml)
304
+ klass_name = complex_type_xml.attr('type').split('.')[-1]
305
+ klass = @classes[klass_name].new
306
+
307
+ # Fill in the properties
308
+ properties = complex_type_xml.xpath(".//*")
309
+ properties.each do |prop|
310
+ klass.send "#{prop.name}=", parse_value(prop)
311
+ end
312
+
313
+ return klass
284
314
  end
315
+
316
+ # Field Converters
317
+ def parse_value(property_xml)
318
+ property_type = property_xml.attr('type')
319
+
320
+ # Handle a nil property type, this is a string
321
+ return property_xml.content if property_type.nil?
322
+
323
+ # Handle complex types
324
+ return complex_type_to_class(property_xml) if !property_type.match(/^Edm/)
325
+
326
+ # Handle integers
327
+ return property_xml.content.to_i if property_type.match(/^Edm.Int/)
328
+
329
+ # Handle decimals
330
+ return property_xml.content.to_d if property_type.match(/Edm.Decimal/)
331
+
332
+ # Handle DateTimes
333
+ # return Time.parse(property_xml.content) if property_type.match(/Edm.DateTime/)
334
+ if property_type.match(/Edm.DateTime/)
335
+ sdate = property_xml.content
336
+
337
+ # Assume this is UTC if no timezone is specified
338
+ sdate = sdate + "Z" unless sdate.match(/Z|([+|-]\d{2}:\d{2})$/)
339
+
340
+ return Time.parse(sdate)
341
+ end
342
+
343
+ # If we can't parse the value, just return the element's content
344
+ property_xml.content
345
+ end
346
+
285
347
  end
286
348
 
287
349
  end # module OData
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ruby_odata}
8
- s.version = "0.0.6"
8
+ s.version = "0.0.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Damien White"]
12
- s.date = %q{2010-06-17}
12
+ s.date = %q{2010-07-04}
13
13
  s.description = %q{An OData Client Library for Ruby. Use this to interact with OData services}
14
14
  s.email = %q{damien.white@visoftinc.com}
15
15
  s.extra_rdoc_files = [
@@ -26,12 +26,14 @@ Gem::Specification.new do |s|
26
26
  "VERSION",
27
27
  "config/cucumber.yml",
28
28
  "features/batch_request.feature",
29
+ "features/complex_types.feature",
29
30
  "features/query_builder.feature",
30
31
  "features/service.feature",
31
32
  "features/service_manage.feature",
32
33
  "features/step_definitions/service_steps.rb",
33
34
  "features/support/env.rb",
34
35
  "features/support/hooks.rb",
36
+ "features/type_conversion.feature",
35
37
  "lib/ruby_odata.rb",
36
38
  "lib/ruby_odata/class_builder.rb",
37
39
  "lib/ruby_odata/operation.rb",
@@ -40,9 +42,11 @@ Gem::Specification.new do |s|
40
42
  "ruby_odata.gemspec",
41
43
  "test/Cassini x64.bat",
42
44
  "test/Cassini x86.bat",
45
+ "test/SampleService/App_Code/AuditFields.cs",
43
46
  "test/SampleService/App_Code/Entities.cs",
44
47
  "test/SampleService/App_Code/Model.Designer.cs",
45
48
  "test/SampleService/App_Code/Model.edmx",
49
+ "test/SampleService/App_Code/ModelContainerExtended.cs",
46
50
  "test/SampleService/App_Data/_TestDB.mdf",
47
51
  "test/SampleService/App_Data/_TestDB_Log.ldf",
48
52
  "test/SampleService/Entities.svc",
@@ -0,0 +1,13 @@
1
+ using System;
2
+
3
+ namespace Model
4
+ {
5
+ public partial class AuditFields
6
+ {
7
+ public AuditFields()
8
+ {
9
+ CreateDate = DateTime.UtcNow;
10
+ ModifiedDate = DateTime.UtcNow;
11
+ }
12
+ }
13
+ }
@@ -252,13 +252,15 @@ namespace Model
252
252
  /// <param name="name">Initial value of the Name property.</param>
253
253
  /// <param name="description">Initial value of the Description property.</param>
254
254
  /// <param name="price">Initial value of the Price property.</param>
255
- public static Product CreateProduct(global::System.Int32 id, global::System.String name, global::System.String description, global::System.Decimal price)
255
+ /// <param name="auditFields">Initial value of the AuditFields property.</param>
256
+ public static Product CreateProduct(global::System.Int32 id, global::System.String name, global::System.String description, global::System.Decimal price, AuditFields auditFields)
256
257
  {
257
258
  Product product = new Product();
258
259
  product.Id = id;
259
260
  product.Name = name;
260
261
  product.Description = description;
261
262
  product.Price = price;
263
+ product.AuditFields = StructuralObject.VerifyComplexObjectIsNotNull(auditFields, "AuditFields");
262
264
  return product;
263
265
  }
264
266
 
@@ -364,6 +366,40 @@ namespace Model
364
366
  partial void OnPriceChanging(global::System.Decimal value);
365
367
  partial void OnPriceChanged();
366
368
 
369
+ #endregion
370
+ #region Complex Properties
371
+
372
+ /// <summary>
373
+ /// No Metadata Documentation available.
374
+ /// </summary>
375
+ [EdmComplexPropertyAttribute()]
376
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
377
+ [XmlElement(IsNullable=true)]
378
+ [SoapElement(IsNullable=true)]
379
+ [DataMemberAttribute()]
380
+ public AuditFields AuditFields
381
+ {
382
+ get
383
+ {
384
+ _AuditFields = GetValidValue(_AuditFields, "AuditFields", false, _AuditFieldsInitialized);
385
+ _AuditFieldsInitialized = true;
386
+ return _AuditFields;
387
+ }
388
+ set
389
+ {
390
+ OnAuditFieldsChanging(value);
391
+ ReportPropertyChanging("AuditFields");
392
+ _AuditFields = SetValidValue(_AuditFields, value, "AuditFields");
393
+ _AuditFieldsInitialized = true;
394
+ ReportPropertyChanged("AuditFields");
395
+ OnAuditFieldsChanged();
396
+ }
397
+ }
398
+ private AuditFields _AuditFields;
399
+ private bool _AuditFieldsInitialized;
400
+ partial void OnAuditFieldsChanging(AuditFields value);
401
+ partial void OnAuditFieldsChanged();
402
+
367
403
  #endregion
368
404
 
369
405
  #region Navigation Properties
@@ -410,5 +446,109 @@ namespace Model
410
446
  }
411
447
 
412
448
  #endregion
449
+ #region ComplexTypes
450
+
451
+ /// <summary>
452
+ /// No Metadata Documentation available.
453
+ /// </summary>
454
+ [EdmComplexTypeAttribute(NamespaceName="Model", Name="AuditFields")]
455
+ [DataContractAttribute(IsReference=true)]
456
+ [Serializable()]
457
+ public partial class AuditFields : ComplexObject
458
+ {
459
+ #region Factory Method
460
+
461
+ /// <summary>
462
+ /// Create a new AuditFields object.
463
+ /// </summary>
464
+ /// <param name="createDate">Initial value of the CreateDate property.</param>
465
+ /// <param name="modifiedDate">Initial value of the ModifiedDate property.</param>
466
+ public static AuditFields CreateAuditFields(global::System.DateTime createDate, global::System.DateTime modifiedDate)
467
+ {
468
+ AuditFields auditFields = new AuditFields();
469
+ auditFields.CreateDate = createDate;
470
+ auditFields.ModifiedDate = modifiedDate;
471
+ return auditFields;
472
+ }
473
+
474
+ #endregion
475
+ #region Primitive Properties
476
+
477
+ /// <summary>
478
+ /// No Metadata Documentation available.
479
+ /// </summary>
480
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
481
+ [DataMemberAttribute()]
482
+ public global::System.DateTime CreateDate
483
+ {
484
+ get
485
+ {
486
+ return _CreateDate;
487
+ }
488
+ set
489
+ {
490
+ OnCreateDateChanging(value);
491
+ ReportPropertyChanging("CreateDate");
492
+ _CreateDate = StructuralObject.SetValidValue(value);
493
+ ReportPropertyChanged("CreateDate");
494
+ OnCreateDateChanged();
495
+ }
496
+ }
497
+ private global::System.DateTime _CreateDate;
498
+ partial void OnCreateDateChanging(global::System.DateTime value);
499
+ partial void OnCreateDateChanged();
500
+
501
+ /// <summary>
502
+ /// No Metadata Documentation available.
503
+ /// </summary>
504
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
505
+ [DataMemberAttribute()]
506
+ public global::System.DateTime ModifiedDate
507
+ {
508
+ get
509
+ {
510
+ return _ModifiedDate;
511
+ }
512
+ set
513
+ {
514
+ OnModifiedDateChanging(value);
515
+ ReportPropertyChanging("ModifiedDate");
516
+ _ModifiedDate = StructuralObject.SetValidValue(value);
517
+ ReportPropertyChanged("ModifiedDate");
518
+ OnModifiedDateChanged();
519
+ }
520
+ }
521
+ private global::System.DateTime _ModifiedDate;
522
+ partial void OnModifiedDateChanging(global::System.DateTime value);
523
+ partial void OnModifiedDateChanged();
524
+
525
+ /// <summary>
526
+ /// No Metadata Documentation available.
527
+ /// </summary>
528
+ [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
529
+ [DataMemberAttribute()]
530
+ public global::System.String CreatedBy
531
+ {
532
+ get
533
+ {
534
+ return _CreatedBy;
535
+ }
536
+ set
537
+ {
538
+ OnCreatedByChanging(value);
539
+ ReportPropertyChanging("CreatedBy");
540
+ _CreatedBy = StructuralObject.SetValidValue(value, true);
541
+ ReportPropertyChanged("CreatedBy");
542
+ OnCreatedByChanged();
543
+ }
544
+ }
545
+ private global::System.String _CreatedBy;
546
+ partial void OnCreatedByChanging(global::System.String value);
547
+ partial void OnCreatedByChanged();
548
+
549
+ #endregion
550
+ }
551
+
552
+ #endregion
413
553
 
414
554
  }
@@ -29,6 +29,9 @@
29
29
  <Property Name="Description" Type="nvarchar(max)" Nullable="false" />
30
30
  <Property Name="Price" Type="money" Nullable="false" />
31
31
  <Property Name="Category_Id" Type="int" Nullable="false" />
32
+ <Property Name="CreateDate" Type="datetime" Nullable="false" />
33
+ <Property Name="ModifiedDate" Type="datetime" Nullable="false" />
34
+ <Property Name="CreatedBy" Type="nvarchar" MaxLength="50" />
32
35
  </EntityType>
33
36
  <Association Name="FK_CategoryProduct">
34
37
  <End Role="Categories" Type="Model.Store.Categories" Multiplicity="1" />
@@ -63,6 +66,7 @@
63
66
  <Property Type="String" Name="Description" Nullable="false" />
64
67
  <Property Type="Decimal" Name="Price" Nullable="false" />
65
68
  <NavigationProperty Name="Category" Relationship="Model.CategoryProduct" FromRole="Product" ToRole="Category" />
69
+ <Property Name="AuditFields" Type="Model.AuditFields" Nullable="false" />
66
70
  </EntityType>
67
71
  <EntityType Name="Category">
68
72
  <Key>
@@ -76,6 +80,11 @@
76
80
  <End Type="Model.Category" Role="Category" Multiplicity="1" />
77
81
  <End Type="Model.Product" Role="Product" Multiplicity="*" />
78
82
  </Association>
83
+ <ComplexType Name="AuditFields">
84
+ <Property Type="DateTime" Name="CreateDate" Nullable="false" />
85
+ <Property Type="DateTime" Name="ModifiedDate" Nullable="false" />
86
+ <Property Type="String" Name="CreatedBy" Nullable="true" />
87
+ </ComplexType>
79
88
  </Schema>
80
89
  </edmx:ConceptualModels>
81
90
  <!-- C-S mapping content -->
@@ -85,10 +94,15 @@
85
94
  <EntitySetMapping Name="Products">
86
95
  <EntityTypeMapping TypeName="IsTypeOf(Model.Product)">
87
96
  <MappingFragment StoreEntitySet="Products">
88
- <ScalarProperty Name="Id" ColumnName="Id" />
97
+ <ScalarProperty Name="Id" ColumnName="Id" />
89
98
  <ScalarProperty Name="Name" ColumnName="Name" />
90
99
  <ScalarProperty Name="Description" ColumnName="Description" />
91
100
  <ScalarProperty Name="Price" ColumnName="Price" />
101
+ <ComplexProperty Name="AuditFields">
102
+ <ScalarProperty Name="CreatedBy" ColumnName="CreatedBy" />
103
+ <ScalarProperty Name="ModifiedDate" ColumnName="ModifiedDate" />
104
+ <ScalarProperty Name="CreateDate" ColumnName="CreateDate" />
105
+ </ComplexProperty>
92
106
  </MappingFragment>
93
107
  </EntityTypeMapping>
94
108
  </EntitySetMapping>
@@ -128,7 +142,7 @@
128
142
  <!-- Diagram content (shape and connector positions) -->
129
143
  <edmx:Diagrams>
130
144
  <Diagram Name="Model" >
131
- <EntityTypeShape EntityType="Model.Product" Width="1.5" PointX="8" PointY="1.75" Height="1.787985026041667" />
145
+ <EntityTypeShape EntityType="Model.Product" Width="1.5" PointX="8" PointY="1.75" Height="2.172587890625" />
132
146
  <EntityTypeShape EntityType="Model.Category" Width="1.5" PointX="5.25" PointY="1.875" Height="1.59568359375" />
133
147
  <AssociationConnector Association="Model.CategoryProduct">
134
148
  <ConnectorPoint PointX="6.75" PointY="2.672841796875" />
@@ -0,0 +1,32 @@
1
+ using System;
2
+ using System.Data;
3
+ using System.Data.Objects;
4
+ using System.Linq;
5
+
6
+ namespace Model
7
+ {
8
+ /// <summary>
9
+ /// Extensions to the default container
10
+ /// </summary>
11
+ public partial class ModelContainer
12
+ {
13
+ partial void OnContextCreated()
14
+ {
15
+ SavingChanges += ContainerSavingChanges;
16
+ }
17
+
18
+ private static void ContainerSavingChanges(object sender, EventArgs e)
19
+ {
20
+ var updatedEntites = ((ObjectContext) sender).ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
21
+ foreach (var ose in updatedEntites)
22
+ {
23
+ var type = ose.Entity.GetType();
24
+ if (type.GetProperties().Any(p => typeof(AuditFields).IsAssignableFrom(p.PropertyType)))
25
+ {
26
+ dynamic entity = ose.Entity;
27
+ entity.AuditFields.ModifiedDate = DateTime.UtcNow;
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
@@ -5,7 +5,7 @@
5
5
  -->
6
6
  <configuration>
7
7
  <system.web>
8
- <compilation debug="false" targetFramework="4.0">
8
+ <compilation debug="true" targetFramework="4.0">
9
9
  <assemblies>
10
10
  <add assembly="System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
11
11
  <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
@@ -9,8 +9,13 @@ Product.blueprint do
9
9
  Description "Test Widget"
10
10
  Price { Sham.price }
11
11
  Category { Category.make }
12
+ AuditFields { AuditFields.make }
12
13
  end
13
14
 
14
15
  Category.blueprint do
15
16
  Name { Sham.category_name }
17
+ end
18
+
19
+ AuditFields.blueprint do
20
+ CreatedBy "Cucumber"
16
21
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_odata
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 6
10
- version: 0.0.6
9
+ - 7
10
+ version: 0.0.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - Damien White
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-06-17 00:00:00 -04:00
18
+ date: 2010-07-04 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -85,12 +85,14 @@ files:
85
85
  - VERSION
86
86
  - config/cucumber.yml
87
87
  - features/batch_request.feature
88
+ - features/complex_types.feature
88
89
  - features/query_builder.feature
89
90
  - features/service.feature
90
91
  - features/service_manage.feature
91
92
  - features/step_definitions/service_steps.rb
92
93
  - features/support/env.rb
93
94
  - features/support/hooks.rb
95
+ - features/type_conversion.feature
94
96
  - lib/ruby_odata.rb
95
97
  - lib/ruby_odata/class_builder.rb
96
98
  - lib/ruby_odata/operation.rb
@@ -99,9 +101,11 @@ files:
99
101
  - ruby_odata.gemspec
100
102
  - test/Cassini x64.bat
101
103
  - test/Cassini x86.bat
104
+ - test/SampleService/App_Code/AuditFields.cs
102
105
  - test/SampleService/App_Code/Entities.cs
103
106
  - test/SampleService/App_Code/Model.Designer.cs
104
107
  - test/SampleService/App_Code/Model.edmx
108
+ - test/SampleService/App_Code/ModelContainerExtended.cs
105
109
  - test/SampleService/App_Data/_TestDB.mdf
106
110
  - test/SampleService/App_Data/_TestDB_Log.ldf
107
111
  - test/SampleService/Entities.svc