ruby_odata 0.0.6 → 0.0.7

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.
@@ -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