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.
@@ -1,4 +1,14 @@
1
- require 'lib/ruby_odata'
2
- require 'machinist/object'
3
- require 'sham'
4
- require 'faker'
1
+ lib = File.expand_path(File.join(File.dirname(__FILE__), "../..", "lib"))
2
+
3
+ require lib + '/ruby_odata'
4
+ require 'machinist/object'
5
+ require 'sham'
6
+ require 'faker'
7
+
8
+ require 'fileutils'
9
+ root_dir = File.expand_path(File.join(File.dirname(__FILE__), "../..", "test/SampleService/App_Data"))
10
+
11
+ if !File.exists?("#{root_dir}/TestDB.mdf")
12
+ FileUtils.copy("#{root_dir}/_TestDB.mdf", "#{root_dir}/TestDB.mdf")
13
+ FileUtils.copy("#{root_dir}/_TestDB_Log.ldf", "#{root_dir}/TestDB_Log.ldf")
14
+ end
@@ -1,4 +1,4 @@
1
- Before do
2
- Sham.reset
3
- RestClient.post "http://localhost:8888/SampleService/Entities.svc/CleanDatabaseForTesting", {}
1
+ Before do
2
+ Sham.reset
3
+ RestClient.post "http://localhost:8888/SampleService/Entities.svc/CleanDatabaseForTesting", {}
4
4
  end
@@ -1,45 +1,53 @@
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
-
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
+
46
+ Scenario: DateTimes should be able to be null
47
+ Given I call "AddToProducts" on the service with a new "Product" object
48
+ And I save changes
49
+ When I call "Products" on the service
50
+ And I run the query
51
+ Then the "DiscontinuedDate" method should return a NilClass
52
+
53
+
@@ -1,18 +1,18 @@
1
- lib = File.dirname(__FILE__)
2
-
3
- $: << lib + '/ruby_odata/'
4
- require 'rubygems'
5
- require 'active_support' # Used for serializtion to JSON
6
- require 'active_support/inflector'
7
- require 'active_support/core_ext'
8
- require 'cgi'
9
- require 'open-uri'
10
- require 'rest_client'
11
- require 'nokogiri'
12
- require 'bigdecimal'
13
- require 'bigdecimal/util'
14
-
15
- require lib + '/ruby_odata/query_builder'
16
- require lib + '/ruby_odata/class_builder'
17
- require lib + '/ruby_odata/operation'
1
+ lib = File.dirname(__FILE__)
2
+
3
+ $: << lib + '/ruby_odata/'
4
+ require 'rubygems'
5
+ require 'active_support' # Used for serializtion to JSON
6
+ require 'active_support/inflector'
7
+ require 'active_support/core_ext'
8
+ require 'cgi'
9
+ require 'open-uri'
10
+ require 'rest_client'
11
+ require 'nokogiri'
12
+ require 'bigdecimal'
13
+ require 'bigdecimal/util'
14
+
15
+ require lib + '/ruby_odata/query_builder'
16
+ require lib + '/ruby_odata/class_builder'
17
+ require lib + '/ruby_odata/operation'
18
18
  require lib + '/ruby_odata/service'
@@ -1,109 +1,117 @@
1
- module OData
2
- # Internally used helper class for building a dynamic class. This class shouldn't be called directly.
3
- class ClassBuilder
4
- # Creates a new instance of the ClassBuilder class
5
- #
6
- # ==== Required Attributes
7
- # - klass_name: The name/type of the class to create
8
- # - methods: The accessor methods to add to the class
9
- # - nav_props: The accessor methods to add for navigation properties
10
- def initialize(klass_name, methods, nav_props)
11
- @klass_name = klass_name
12
- @methods = methods
13
- @nav_props = nav_props
14
- end
15
-
16
- # Returns a dynamically generated class definition based on the constructor parameters
17
- def build
18
- # return if already built
19
- return @klass unless @klass.nil?
20
-
21
- # need the class name to build class
22
- return nil if @klass_name.nil?
23
-
24
- # return if we can find constant corresponding to class name
25
- if Object.const_defined? @klass_name
26
- @klass = @klass_name.constantize
27
- return @klass
28
- end
29
-
30
- Object.const_set(@klass_name, Class.new.extend(ActiveSupport::JSON))
31
- @klass = @klass_name.constantize
32
-
33
- add_methods(@klass)
34
- add_nav_props(@klass)
35
-
36
- return @klass
37
- end
38
-
39
- private
40
- def add_methods(klass)
41
- # Add metadata methods
42
- klass.send :define_method, :__metadata do
43
- instance_variable_get("@__metadata")
44
- end
45
- klass.send :define_method, :__metadata= do |value|
46
- instance_variable_set("@__metadata", value)
47
- end
48
- klass.send :define_method, :as_json do |options|
49
- meta = '__metadata'
50
-
51
- options ||= {}
52
- options[:type] ||= :unknown
53
-
54
- vars = self.instance_values
55
-
56
- # For adds, we need to get rid of all attributes except __metadata when passing
57
- # the object to the server
58
- # TODO: There should be a universal way to figure out if we are on an addition
59
- # activesupport 2.3.8 doesn't pass through the :type to all levels, but passes :seen
60
- # activesupport 3.0.0.beta4 doesn't pass :seen in options
61
- if (options[:type] == :add || !options[:seen].nil?) && vars.has_key?(meta)
62
- vars.delete_if { |k,v| k != meta}
63
- else
64
- vars.delete(meta)
65
- end
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
-
83
- vars
84
- end
85
-
86
-
87
- # Add the methods that were passed in
88
- @methods.each do |method_name|
89
- klass.send :define_method, method_name do
90
- instance_variable_get("@#{method_name}")
91
- end
92
- klass.send :define_method, "#{method_name}=" do |value|
93
- instance_variable_set("@#{method_name}", value)
94
- end
95
- end
96
- end
97
-
98
- def add_nav_props(klass)
99
- @nav_props.each do |method_name|
100
- klass.send :define_method, method_name do
101
- instance_variable_get("@#{method_name}")
102
- end
103
- klass.send :define_method, "#{method_name}=" do |value|
104
- instance_variable_set("@#{method_name}", value)
105
- end
106
- end
107
- end
108
- end
109
- end # module OData
1
+ module OData
2
+ # Internally used helper class for building a dynamic class. This class shouldn't be called directly.
3
+ class ClassBuilder
4
+ # Creates a new instance of the ClassBuilder class
5
+ #
6
+ # ==== Required Attributes
7
+ # - klass_name: The name/type of the class to create
8
+ # - methods: The accessor methods to add to the class
9
+ # - nav_props: The accessor methods to add for navigation properties
10
+ def initialize(klass_name, methods, nav_props)
11
+ @klass_name = klass_name
12
+ @methods = methods
13
+ @nav_props = nav_props
14
+ end
15
+
16
+ # Returns a dynamically generated class definition based on the constructor parameters
17
+ def build
18
+ # return if already built
19
+ return @klass unless @klass.nil?
20
+
21
+ # need the class name to build class
22
+ return nil if @klass_name.nil?
23
+
24
+ # return if we can find constant corresponding to class name
25
+ if Object.const_defined? @klass_name
26
+ @klass = @klass_name.constantize
27
+ return @klass
28
+ end
29
+
30
+ Object.const_set(@klass_name, Class.new.extend(ActiveSupport::JSON))
31
+ @klass = @klass_name.constantize
32
+ @klass.class_eval do
33
+ include OData
34
+ end
35
+
36
+ add_methods(@klass)
37
+ add_nav_props(@klass)
38
+
39
+ return @klass
40
+ end
41
+
42
+ private
43
+ def add_methods(klass)
44
+ # Add metadata methods
45
+ klass.send :define_method, :__metadata do
46
+ instance_variable_get("@__metadata")
47
+ end
48
+ klass.send :define_method, :__metadata= do |value|
49
+ instance_variable_set("@__metadata", value)
50
+ end
51
+ klass.send :define_method, :as_json do |*args|
52
+ meta = RUBY_VERSION < "1.9" ? '@__metadata' :'@__metadata'.to_sym
53
+
54
+ options = args[0] || {}
55
+ options[:type] ||= :unknown
56
+
57
+ vars = self.instance_values
58
+
59
+ # For adds, we need to get rid of all attributes except __metadata when passing
60
+ # the object to the server
61
+ if(options[:type] == :add)
62
+ vars.each_value do |value|
63
+ if value.is_a? OData
64
+ child_vars = value.instance_variables
65
+ if(child_vars.include?(meta))
66
+ child_vars.each do |var|
67
+ value.send :remove_instance_variable, var if var != meta
68
+ end
69
+ else
70
+ value.send :remove_instance_variable, meta if value.instance_variable_defined? meta
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ # Convert a BigDecimal to a string for serialization (to match Edm.Decimal)
77
+ decimals = vars.find_all { |o| o[1].class == BigDecimal } || []
78
+ decimals.each do |d|
79
+ vars[d[0]] = d[1].to_s
80
+ end
81
+
82
+ # Convert Time to an RFC3339 string for serialization
83
+ times = vars.find_all { |o| o[1].class == Time } || []
84
+ times.each do |t|
85
+ sdate = t[1].xmlschema(3)
86
+ # Remove the ending Z (indicating UTC).
87
+ # If the Z is there when saving, the time is converted to local time on the server
88
+ sdate.chop! if sdate.match(/Z$/)
89
+ vars[t[0]] = sdate
90
+ end
91
+
92
+ vars
93
+ end
94
+
95
+ # Add the methods that were passed in
96
+ @methods.each do |method_name|
97
+ klass.send :define_method, method_name do
98
+ instance_variable_get("@#{method_name}")
99
+ end
100
+ klass.send :define_method, "#{method_name}=" do |value|
101
+ instance_variable_set("@#{method_name}", value)
102
+ end
103
+ end
104
+ end
105
+
106
+ def add_nav_props(klass)
107
+ @nav_props.each do |method_name|
108
+ klass.send :define_method, method_name do
109
+ instance_variable_get("@#{method_name}")
110
+ end
111
+ klass.send :define_method, "#{method_name}=" do |value|
112
+ instance_variable_set("@#{method_name}", value)
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end # module OData
@@ -1,18 +1,18 @@
1
- module OData
2
- # Internally used helper class for storing operations called against the service. This class shouldn't be used directly.
3
- class Operation
4
- attr_accessor :kind, :klass_name, :klass
5
-
6
- # Creates a new instance of the Operation class
7
- #
8
- # ==== Required Attributes
9
- # - kind: The operation type (Add, Update, or Delete)
10
- # - klass_name: The name/type of the class to operate against
11
- # - klass: The actual class
12
- def initialize(kind, klass_name, klass)
13
- @kind = kind
14
- @klass_name = klass_name
15
- @klass = klass
16
- end
17
- end
1
+ module OData
2
+ # Internally used helper class for storing operations called against the service. This class shouldn't be used directly.
3
+ class Operation
4
+ attr_accessor :kind, :klass_name, :klass
5
+
6
+ # Creates a new instance of the Operation class
7
+ #
8
+ # ==== Required Attributes
9
+ # - kind: The operation type (Add, Update, or Delete)
10
+ # - klass_name: The name/type of the class to operate against
11
+ # - klass: The actual class
12
+ def initialize(kind, klass_name, klass)
13
+ @kind = kind
14
+ @klass_name = klass_name
15
+ @klass = klass
16
+ end
17
+ end
18
18
  end
@@ -1,107 +1,107 @@
1
- module OData
2
- # The query builder is used to call query operations against the service. This shouldn't be called directly, but rather it is returned from the dynamic methods created for the specific service that you are calling.
3
- #
4
- # For example, given the following code snippet:
5
- # svc = OData::Service.new "http://127.0.0.1:8888/SampleService/Entities.svc"
6
- # svc.Categories
7
- # The *Categories* method would return a QueryBuilder
8
- class QueryBuilder
9
- # Creates a new instance of the QueryBuilder class
10
- #
11
- # ==== Required Attributes
12
- # - root: The root entity collection to query against
13
- def initialize(root)
14
- @root = root.to_s
15
- @expands = []
16
- @filters = []
17
- @order_bys = []
18
- @skip = nil
19
- @top = nil
20
- end
21
-
22
- # Used to eagerly-load data for nested objects, for example, obtaining a Category for a Product within one call to the server
23
- # ==== Required Attributes
24
- # - path: The path of the entity to expand relative to the root
25
- #
26
- # ==== Example
27
- # # Without expanding the query (no Category will be filled in for the Product)
28
- # svc.Products(1)
29
- # prod1 = svc.execute
30
- #
31
- # # With expanding the query (the Category will be filled in)
32
- # svc.Products(1).expand('Category')
33
- # prod1 = svc.execute
34
- def expand(path)
35
- @expands << path
36
- self
37
- end
38
-
39
- # Used to filter data being returned
40
- # ==== Required Attributes
41
- # - filter: The conditions to apply to the query
42
- #
43
- # ==== Example
44
- # svc.Products.filter("Name eq 'Product 2'")
45
- # products = svc.execute
46
- def filter(filter)
47
- @filters << CGI.escape(filter)
48
- self
49
- end
50
-
51
- # Used to order the data being returned
52
- # ==== Required Attributes
53
- # - order_by: The order by statement. Note to specify direction, use "desc" or "asc"; must be lowercase
54
- #
55
- # ==== Example
56
- # svc.Products.order_by("Name")
57
- # products = svc.execute
58
- def order_by(order_by)
59
- @order_bys << CGI.escape(order_by)
60
- self
61
- end
62
-
63
- # Used to skip a number of records
64
- # This is typically used for paging, where it would be used along with the +top+ method.
65
- # ==== Required Attributes
66
- # - num: The number of items to skip
67
- #
68
- # ==== Example
69
- # svc.Products.skip(5)
70
- # products = svc.execute # => skips the first 5 items
71
- def skip(num)
72
- @skip = num
73
- self
74
- end
75
-
76
- # Used to take only the top X records
77
- # This is typically used for paging, where it would be used along with the +skip+ method.
78
- # ==== Required Attributes
79
- # - num: The number of items to return
80
- #
81
- # ==== Example
82
- # svc.Products.top(5)
83
- # products = svc.execute # => returns only the first 5 items
84
- def top(num)
85
- @top = num
86
- self
87
- end
88
-
89
- # Builds the query URI (path, not including root) incorporating expands, filters, etc.
90
- # This is used internally when the execute method is called on the service
91
- def query
92
- q = @root.clone
93
- query_options = []
94
- query_options << "$expand=#{@expands.join(',')}" unless @expands.empty?
95
- query_options << "$filter=#{@filters.join('+and+')}" unless @filters.empty?
96
- query_options << "$orderby=#{@order_bys.join(',')}" unless @order_bys.empty?
97
- query_options << "$skip=#{@skip}" unless @skip.nil?
98
- query_options << "$top=#{@top}" unless @top.nil?
99
- if !query_options.empty?
100
- q << "?"
101
- q << query_options.join('&')
102
- end
103
- return q
104
- end
105
- end
106
-
1
+ module OData
2
+ # The query builder is used to call query operations against the service. This shouldn't be called directly, but rather it is returned from the dynamic methods created for the specific service that you are calling.
3
+ #
4
+ # For example, given the following code snippet:
5
+ # svc = OData::Service.new "http://127.0.0.1:8888/SampleService/Entities.svc"
6
+ # svc.Categories
7
+ # The *Categories* method would return a QueryBuilder
8
+ class QueryBuilder
9
+ # Creates a new instance of the QueryBuilder class
10
+ #
11
+ # ==== Required Attributes
12
+ # - root: The root entity collection to query against
13
+ def initialize(root)
14
+ @root = root.to_s
15
+ @expands = []
16
+ @filters = []
17
+ @order_bys = []
18
+ @skip = nil
19
+ @top = nil
20
+ end
21
+
22
+ # Used to eagerly-load data for nested objects, for example, obtaining a Category for a Product within one call to the server
23
+ # ==== Required Attributes
24
+ # - path: The path of the entity to expand relative to the root
25
+ #
26
+ # ==== Example
27
+ # # Without expanding the query (no Category will be filled in for the Product)
28
+ # svc.Products(1)
29
+ # prod1 = svc.execute
30
+ #
31
+ # # With expanding the query (the Category will be filled in)
32
+ # svc.Products(1).expand('Category')
33
+ # prod1 = svc.execute
34
+ def expand(path)
35
+ @expands << path
36
+ self
37
+ end
38
+
39
+ # Used to filter data being returned
40
+ # ==== Required Attributes
41
+ # - filter: The conditions to apply to the query
42
+ #
43
+ # ==== Example
44
+ # svc.Products.filter("Name eq 'Product 2'")
45
+ # products = svc.execute
46
+ def filter(filter)
47
+ @filters << CGI.escape(filter)
48
+ self
49
+ end
50
+
51
+ # Used to order the data being returned
52
+ # ==== Required Attributes
53
+ # - order_by: The order by statement. Note to specify direction, use "desc" or "asc"; must be lowercase
54
+ #
55
+ # ==== Example
56
+ # svc.Products.order_by("Name")
57
+ # products = svc.execute
58
+ def order_by(order_by)
59
+ @order_bys << CGI.escape(order_by)
60
+ self
61
+ end
62
+
63
+ # Used to skip a number of records
64
+ # This is typically used for paging, where it would be used along with the +top+ method.
65
+ # ==== Required Attributes
66
+ # - num: The number of items to skip
67
+ #
68
+ # ==== Example
69
+ # svc.Products.skip(5)
70
+ # products = svc.execute # => skips the first 5 items
71
+ def skip(num)
72
+ @skip = num
73
+ self
74
+ end
75
+
76
+ # Used to take only the top X records
77
+ # This is typically used for paging, where it would be used along with the +skip+ method.
78
+ # ==== Required Attributes
79
+ # - num: The number of items to return
80
+ #
81
+ # ==== Example
82
+ # svc.Products.top(5)
83
+ # products = svc.execute # => returns only the first 5 items
84
+ def top(num)
85
+ @top = num
86
+ self
87
+ end
88
+
89
+ # Builds the query URI (path, not including root) incorporating expands, filters, etc.
90
+ # This is used internally when the execute method is called on the service
91
+ def query
92
+ q = @root.clone
93
+ query_options = []
94
+ query_options << "$expand=#{@expands.join(',')}" unless @expands.empty?
95
+ query_options << "$filter=#{@filters.join('+and+')}" unless @filters.empty?
96
+ query_options << "$orderby=#{@order_bys.join(',')}" unless @order_bys.empty?
97
+ query_options << "$skip=#{@skip}" unless @skip.nil?
98
+ query_options << "$top=#{@top}" unless @top.nil?
99
+ if !query_options.empty?
100
+ q << "?"
101
+ q << query_options.join('&')
102
+ end
103
+ return q
104
+ end
105
+ end
106
+
107
107
  end # Module