ruby_odata 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE +24 -0
- data/README.rdoc +107 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/config/cucumber.yml +7 -0
- data/doc/classes/OData/ClassBuilder.html +219 -0
- data/doc/classes/OData/Operation.html +194 -0
- data/doc/classes/OData/QueryBuilder.html +305 -0
- data/doc/classes/OData/Service.html +420 -0
- data/doc/classes/OData.html +126 -0
- data/doc/created.rid +1 -0
- data/doc/files/README_rdoc.html +252 -0
- data/doc/files/lib/odata_ruby/class_builder_rb.html +101 -0
- data/doc/files/lib/odata_ruby/operation_rb.html +101 -0
- data/doc/files/lib/odata_ruby/query_builder_rb.html +101 -0
- data/doc/files/lib/odata_ruby/service_rb.html +101 -0
- data/doc/files/lib/odata_ruby_rb.html +114 -0
- data/doc/fr_class_index.html +31 -0
- data/doc/fr_file_index.html +32 -0
- data/doc/fr_method_index.html +40 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/features/service.feature +82 -0
- data/features/service_manage.feature +44 -0
- data/features/step_definitions/service_steps.rb +132 -0
- data/features/support/env.rb +4 -0
- data/features/support/hooks.rb +4 -0
- data/lib/odata_ruby/class_builder.rb +84 -0
- data/lib/odata_ruby/operation.rb +18 -0
- data/lib/odata_ruby/query_builder.rb +63 -0
- data/lib/odata_ruby/service.rb +207 -0
- data/lib/odata_ruby.rb +15 -0
- data/ruby_odata.gemspec +94 -0
- data/test/Cassini x64.bat +1 -0
- data/test/Cassini x86.bat +1 -0
- data/test/SampleService/App_Code/Entities.cs +39 -0
- data/test/SampleService/App_Code/Model.Designer.cs +414 -0
- data/test/SampleService/App_Code/Model.edmx +140 -0
- data/test/SampleService/App_Data/_TestDB.mdf +0 -0
- data/test/SampleService/App_Data/_TestDB_Log.ldf +0 -0
- data/test/SampleService/Entities.svc +1 -0
- data/test/SampleService/web.config +27 -0
- data/test/blueprints.rb +16 -0
- metadata +158 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
Given /^an ODataService exists with uri: "([^\"]*)"$/ do |uri|
|
2
|
+
@service = OData::Service.new(uri)
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^I call "([^\"]*)" on the service$/ do |method|
|
6
|
+
@service_query = @service.send(method)
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^the result should be "([^\"]*)"$/ do |result|
|
10
|
+
@service_result.should == result
|
11
|
+
end
|
12
|
+
|
13
|
+
Then /^I should be able to call "([^\"]*)" on the service$/ do |method|
|
14
|
+
lambda { @service.send(method) }.should_not raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
Then /^I should not be able to call "([^\"]*)" on the service$/ do |method|
|
18
|
+
lambda { @service.send(method) }.should raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
Then /^I should be able to call "([^\"]*)" on the service with args: "([^\"]*)"$/ do |method, args|
|
22
|
+
lambda { @service.send(method, args) }.should_not raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
When /^I call "([^\"]*)" on the service with args: "([^\"]*)"$/ do |method, args|
|
26
|
+
@service_query = @service.send(method, args)
|
27
|
+
end
|
28
|
+
|
29
|
+
When /^I run the query$/ do
|
30
|
+
@service_result = @service.execute
|
31
|
+
end
|
32
|
+
|
33
|
+
Then /^the result should be of type "([^\"]*)"$/ do |type|
|
34
|
+
@service_result.class.to_s.should == type
|
35
|
+
end
|
36
|
+
|
37
|
+
Then /^the result should have a method: "([^\"]*)"$/ do |method|
|
38
|
+
@service_result.respond_to?(method.to_sym).should == true
|
39
|
+
end
|
40
|
+
|
41
|
+
Then /^the method "([^\"]*)" on the result should equal: "([^\"]*)"$/ do |method, value|
|
42
|
+
@service_result.send(method.to_sym).should == value
|
43
|
+
end
|
44
|
+
|
45
|
+
Then /^the method "([^\"]*)" on the result should be nil$/ do |method|
|
46
|
+
@service_result.send(method.to_sym).should == nil
|
47
|
+
end
|
48
|
+
|
49
|
+
When /^I set "([^\"]*)" on the result to "([^\"]*)"$/ do |property_name, value|
|
50
|
+
@service_result.send("#{property_name}=", value)
|
51
|
+
end
|
52
|
+
|
53
|
+
Given /^I expand the query to include "([^\"]*)"$/ do |expands|
|
54
|
+
@service_query.expand(expands)
|
55
|
+
end
|
56
|
+
|
57
|
+
When /^I filter the query with: "([^\"]*)"$/ do |filter|
|
58
|
+
@service_query.filter(filter)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
Then /^the method "([^\"]*)" on the result should be of type "([^\"]*)"$/ do |method, type|
|
63
|
+
result = @service_result.send(method.to_sym)
|
64
|
+
result.class.to_s.should == type
|
65
|
+
end
|
66
|
+
|
67
|
+
Given /^I call "([^\"]*)" on the service with a new "([^\"]*)" object(?: with (.*))?$/ do |method, object, fields|
|
68
|
+
fields_hash = {}
|
69
|
+
|
70
|
+
if !fields.nil?
|
71
|
+
fields.split(', ').each do |field|
|
72
|
+
if field =~ /^(?:(\w+): "(.*)")$/
|
73
|
+
key = $1
|
74
|
+
val = $2
|
75
|
+
if val =~ /^@@LastSave$/
|
76
|
+
val = @saved_result
|
77
|
+
end
|
78
|
+
|
79
|
+
fields_hash.merge!({ key => val })
|
80
|
+
else
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
obj = object.constantize.send(:make, fields_hash)
|
86
|
+
@service.send(method.to_sym, obj)
|
87
|
+
end
|
88
|
+
|
89
|
+
When /^I save changes$/ do
|
90
|
+
@saved_result = @service.save_changes
|
91
|
+
end
|
92
|
+
|
93
|
+
Then /^the save result should be of type "([^\"]*)"$/ do |type|
|
94
|
+
@saved_result.class.to_s.should == type
|
95
|
+
end
|
96
|
+
|
97
|
+
When /^I call "([^\"]*)" on the service with the last save result$/ do |method|
|
98
|
+
@service.send(method.to_sym, @saved_result)
|
99
|
+
end
|
100
|
+
|
101
|
+
When /^I call "([^\"]*)" on the service with the last query result$/ do |method|
|
102
|
+
@service.send(method.to_sym, @service_result)
|
103
|
+
end
|
104
|
+
|
105
|
+
Then /^the save result should equal: "([^\"]*)"$/ do |result|
|
106
|
+
@saved_result.to_s.should == result
|
107
|
+
end
|
108
|
+
|
109
|
+
Then /^the method "([^\"]*)" on the save result should equal: "([^\"]*)"$/ do |method, value|
|
110
|
+
result = @saved_result.send(method.to_sym)
|
111
|
+
result.should == value
|
112
|
+
end
|
113
|
+
|
114
|
+
When /^blueprints exist for the service$/ do
|
115
|
+
require File.expand_path(File.dirname(__FILE__) + "../../../test/blueprints")
|
116
|
+
end
|
117
|
+
|
118
|
+
Given /^I call "([^\"]*)" on the service with a new "([^\"]*)" object it should throw an exception with message "([^\"]*)"$/ do |method, object, msg|
|
119
|
+
obj = object.constantize.send :make
|
120
|
+
lambda { @service.send(method.to_sym, obj) }.should raise_error(msg)
|
121
|
+
end
|
122
|
+
|
123
|
+
Then /^no "([^\"]*)" should exist$/ do |collection|
|
124
|
+
@service.send(collection)
|
125
|
+
results = @service.execute
|
126
|
+
results.should == []
|
127
|
+
end
|
128
|
+
|
129
|
+
Then /^the method "([^\"]*)" on the result's method "([^\"]*)" should equal: "([^\"]*)"$/ do |method, result_method, value|
|
130
|
+
obj = @service_result.send(result_method.to_sym)
|
131
|
+
obj.send(method.to_sym).should == value
|
132
|
+
end
|
@@ -0,0 +1,84 @@
|
|
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.constants.include? @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
|
+
vars = self.instance_values
|
51
|
+
|
52
|
+
if !options.nil? && !options[:seen].nil? && vars.has_key?(meta)
|
53
|
+
vars.delete_if { |k,v| k != meta}
|
54
|
+
else
|
55
|
+
vars.delete(meta)
|
56
|
+
end
|
57
|
+
|
58
|
+
vars
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Add the methods that were passed in
|
63
|
+
@methods.each do |method_name|
|
64
|
+
klass.send :define_method, method_name do
|
65
|
+
instance_variable_get("@#{method_name}")
|
66
|
+
end
|
67
|
+
klass.send :define_method, "#{method_name}=" do |value|
|
68
|
+
instance_variable_set("@#{method_name}", value)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_nav_props(klass)
|
74
|
+
@nav_props.each do |method_name|
|
75
|
+
klass.send :define_method, method_name do
|
76
|
+
instance_variable_get("@#{method_name}")
|
77
|
+
end
|
78
|
+
klass.send :define_method, "#{method_name}=" do |value|
|
79
|
+
instance_variable_set("@#{method_name}", value)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end # module OData
|
@@ -0,0 +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
|
18
|
+
end
|
@@ -0,0 +1,63 @@
|
|
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
|
+
end
|
18
|
+
|
19
|
+
# Used to eagerly-load data for nested objects, for example, obtaining a Category for a Product within one call to the server
|
20
|
+
# ==== Required Attributes
|
21
|
+
# - path: The path of the entity to expand relative to the root
|
22
|
+
#
|
23
|
+
# ==== Example
|
24
|
+
# # Without expanding the query (no Category will be filled in for the Product)
|
25
|
+
# svc.Products(1)
|
26
|
+
# prod1 = svc.execute
|
27
|
+
#
|
28
|
+
# # With expanding the query (the Category will be filled in)
|
29
|
+
# svc.Products(1).expand('Category')
|
30
|
+
# prod1 = svc.execute
|
31
|
+
def expand(path)
|
32
|
+
@expands << path
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Used to filter data being returned
|
37
|
+
# ==== Required Attributes
|
38
|
+
# - filter: The path of the entity to expand relative to the root
|
39
|
+
#
|
40
|
+
# ==== Example
|
41
|
+
# svc.Products.filter("Name eq 'Product 2'")
|
42
|
+
# prod = svc.execute
|
43
|
+
def filter(filter)
|
44
|
+
@filters << CGI.escape(filter)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Builds the query URI (path, not including root) incorporating expands, filters, etc.
|
49
|
+
# This is used internally when the execute method is called on the service
|
50
|
+
def query
|
51
|
+
q = @root.clone
|
52
|
+
query_options = []
|
53
|
+
query_options << "$expand=#{@expands.join(',')}" unless @expands.empty?
|
54
|
+
query_options << "$filter=#{@filters.join('+and+')}" unless @filters.empty?
|
55
|
+
if !query_options.empty?
|
56
|
+
q << "?"
|
57
|
+
q << query_options.join('&')
|
58
|
+
end
|
59
|
+
return q
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end # Module
|
@@ -0,0 +1,207 @@
|
|
1
|
+
module OData
|
2
|
+
|
3
|
+
class Service
|
4
|
+
attr_reader :classes
|
5
|
+
# Creates a new instance of the Service class
|
6
|
+
#
|
7
|
+
# ==== Required Attributes
|
8
|
+
# - service_uri: The root URI of the OData service
|
9
|
+
def initialize(service_uri)
|
10
|
+
@uri = service_uri
|
11
|
+
@collections = get_collections
|
12
|
+
build_classes
|
13
|
+
end
|
14
|
+
|
15
|
+
# Handles the dynamic AddTo<EntityName> methods as well as the collections on the service
|
16
|
+
def method_missing(name, *args)
|
17
|
+
# Queries
|
18
|
+
if @collections.include?(name.to_s)
|
19
|
+
root = "/#{name.to_s.camelize}"
|
20
|
+
root << "(#{args.join(',')})" unless args.empty?
|
21
|
+
@query = QueryBuilder.new(root)
|
22
|
+
return @query
|
23
|
+
# Adds
|
24
|
+
elsif name.to_s =~ /^AddTo(.*)/
|
25
|
+
type = $1
|
26
|
+
if @collections.include?(type)
|
27
|
+
@save_operation = Operation.new("Add", $1, args[0])
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
# Queues an object for deletion. To actually remove it from the server, you must call save_changes as well.
|
38
|
+
#
|
39
|
+
# ==== Required Attributes
|
40
|
+
# - obj: The object to mark for deletion
|
41
|
+
#
|
42
|
+
# Note: This method will throw an exception if the +obj+ isn't a tracked entity
|
43
|
+
def delete_object(obj)
|
44
|
+
type = obj.class.to_s
|
45
|
+
if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil?
|
46
|
+
@save_operation = Operation.new("Delete", type, obj)
|
47
|
+
else
|
48
|
+
raise "You cannot delete a non-tracked entity"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Queues an object for update. To actually update it on the server, you must call save_changes as well.
|
53
|
+
#
|
54
|
+
# ==== Required Attributes
|
55
|
+
# - obj: The object to queue for update
|
56
|
+
#
|
57
|
+
# Note: This method will throw an exception if the +obj+ isn't a tracked entity
|
58
|
+
def update_object(obj)
|
59
|
+
type = obj.class.to_s
|
60
|
+
if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil?
|
61
|
+
@save_operation = Operation.new("Update", type, obj)
|
62
|
+
else
|
63
|
+
raise "You cannot update a non-tracked entity"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Performs save operations (Create/Update/Delete) against the server
|
68
|
+
def save_changes
|
69
|
+
return nil if @save_operation.nil?
|
70
|
+
|
71
|
+
result = nil
|
72
|
+
|
73
|
+
if @save_operation.kind == "Add"
|
74
|
+
save_uri = "#{@uri}/#{@save_operation.klass_name}"
|
75
|
+
json_klass = @save_operation.klass.to_json
|
76
|
+
post_result = RestClient.post save_uri, json_klass, :content_type => :json
|
77
|
+
result = build_classes_from_result(post_result)
|
78
|
+
elsif @save_operation.kind == "Update"
|
79
|
+
update_uri = @save_operation.klass.send(:__metadata)[:uri]
|
80
|
+
json_klass = @save_operation.klass.to_json
|
81
|
+
update_result = RestClient.put update_uri, json_klass, :content_type => :json
|
82
|
+
return (update_result.code == 204)
|
83
|
+
elsif @save_operation.kind == "Delete"
|
84
|
+
delete_uri = @save_operation.klass.send(:__metadata)[:uri]
|
85
|
+
delete_result = RestClient.delete delete_uri
|
86
|
+
return (delete_result.code == 204)
|
87
|
+
end
|
88
|
+
|
89
|
+
@save_operation = nil # Clear out the last operation
|
90
|
+
return result
|
91
|
+
end
|
92
|
+
|
93
|
+
# Performs query operations (Read) against the server
|
94
|
+
def execute
|
95
|
+
result = RestClient.get build_query_uri
|
96
|
+
build_classes_from_result(result)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Overridden to identify methods handled by method_missing
|
100
|
+
def respond_to?(method)
|
101
|
+
if @collections.include?(method.to_s)
|
102
|
+
return true
|
103
|
+
# Adds
|
104
|
+
elsif method.to_s =~ /^AddTo(.*)/
|
105
|
+
type = $1
|
106
|
+
if @collections.include?(type)
|
107
|
+
return true
|
108
|
+
else
|
109
|
+
super
|
110
|
+
end
|
111
|
+
else
|
112
|
+
super
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
def get_collections
|
118
|
+
doc = Nokogiri::XML(open(@uri))
|
119
|
+
collections = doc.xpath("//app:collection", "app" => "http://www.w3.org/2007/app")
|
120
|
+
collections.collect { |c| c["href"] }
|
121
|
+
end
|
122
|
+
def build_classes
|
123
|
+
@classes = Hash.new
|
124
|
+
doc = Nokogiri::XML(open("#{@uri}/$metadata"))
|
125
|
+
|
126
|
+
# Get the edm namespace
|
127
|
+
edm_ns = doc.xpath("edmx:Edmx/edmx:DataServices/*", "edmx" => "http://schemas.microsoft.com/ado/2007/06/edmx").first.namespaces['xmlns'].to_s
|
128
|
+
|
129
|
+
entity_types = doc.xpath("//edm:EntityType", "edm" => edm_ns)
|
130
|
+
entity_types.each do |e|
|
131
|
+
name = e['Name']
|
132
|
+
props = e.xpath(".//edm:Property", "edm" => edm_ns)
|
133
|
+
methods = props.collect { |p| p['Name'] } # Standard Properties
|
134
|
+
nprops = e.xpath(".//edm:NavigationProperty", "edm" => edm_ns)
|
135
|
+
nav_props = nprops.collect { |p| p['Name'] } # Standard Properties
|
136
|
+
@classes[name] = ClassBuilder.new(name, methods, nav_props).build unless @classes.keys.include?(name)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
def build_classes_from_result(result)
|
140
|
+
doc = Nokogiri::XML(result)
|
141
|
+
entries = doc.xpath("//atom:entry[not(ancestor::atom:entry)]", "atom" => "http://www.w3.org/2005/Atom")
|
142
|
+
return entry_to_class(entries[0]) if entries.length == 1
|
143
|
+
|
144
|
+
results = []
|
145
|
+
entries.each do |entry|
|
146
|
+
results << entry_to_class(entry)
|
147
|
+
end
|
148
|
+
return results
|
149
|
+
end
|
150
|
+
def entry_to_class(entry)
|
151
|
+
# Retrieve the class name from the fully qualified name (the last string after the last dot)
|
152
|
+
klass_name = entry.xpath("./atom:category/@term", "atom" => "http://www.w3.org/2005/Atom").to_s.split('.')[-1]
|
153
|
+
return nil if klass_name.empty?
|
154
|
+
|
155
|
+
properties = entry.xpath(".//m:properties/*", { "m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" })
|
156
|
+
|
157
|
+
klass = @classes[klass_name].new
|
158
|
+
|
159
|
+
# Fill metadata
|
160
|
+
meta_id = entry.xpath("./atom:id", "atom" => "http://www.w3.org/2005/Atom")[0].content
|
161
|
+
klass.send :__metadata=, { :uri => meta_id }
|
162
|
+
|
163
|
+
# Fill properties
|
164
|
+
for prop in properties
|
165
|
+
prop_name = prop.name
|
166
|
+
# puts "#{prop_name} - #{prop.content}"
|
167
|
+
klass.send "#{prop_name}=", prop.content
|
168
|
+
end
|
169
|
+
|
170
|
+
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" })
|
171
|
+
|
172
|
+
for link in inline_links
|
173
|
+
inline_entries = link.xpath(".//atom:entry", "atom" => "http://www.w3.org/2005/Atom")
|
174
|
+
|
175
|
+
if inline_entries.length == 1
|
176
|
+
property_name = link.attributes['title'].to_s
|
177
|
+
|
178
|
+
build_inline_class(klass, inline_entries[0], property_name)
|
179
|
+
else
|
180
|
+
# TODO: Test handling multiple children
|
181
|
+
for inline_entry in inline_entries
|
182
|
+
property_name = link.xpath("atom:link[@rel='edit']/@title", "atom" => "http://www.w3.org/2005/Atom")
|
183
|
+
|
184
|
+
# Build the class
|
185
|
+
inline_klass = entry_to_class(inline_entry)
|
186
|
+
|
187
|
+
# Add the property
|
188
|
+
klass.send "#{property_name}=", inline_klass
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
return klass
|
194
|
+
end
|
195
|
+
def build_query_uri
|
196
|
+
"#{@uri}#{@query.query}"
|
197
|
+
end
|
198
|
+
def build_inline_class(klass, entry, property_name)
|
199
|
+
# Build the class
|
200
|
+
inline_klass = entry_to_class(entry)
|
201
|
+
|
202
|
+
# Add the property
|
203
|
+
klass.send "#{property_name}=", inline_klass
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end # module OData
|
data/lib/odata_ruby.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
lib = File.dirname(__FILE__)
|
2
|
+
$: << lib + '/odata_ruby/'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'active_support' # Used for serializtion to JSON
|
6
|
+
require 'active_support/inflector'
|
7
|
+
require 'cgi'
|
8
|
+
require 'open-uri'
|
9
|
+
require 'rest_client'
|
10
|
+
require 'nokogiri'
|
11
|
+
|
12
|
+
require lib + '/odata_ruby/query_builder'
|
13
|
+
require lib + '/odata_ruby/class_builder'
|
14
|
+
require lib + '/odata_ruby/operation'
|
15
|
+
require lib + '/odata_ruby/service'
|
data/ruby_odata.gemspec
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ruby_odata}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Damien White"]
|
12
|
+
s.date = %q{2010-06-11}
|
13
|
+
s.description = %q{An OData Client Library for Ruby. Use this to interact with OData services}
|
14
|
+
s.email = %q{damien.white@visoftinc.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"config/cucumber.yml",
|
26
|
+
"doc/classes/OData.html",
|
27
|
+
"doc/classes/OData/ClassBuilder.html",
|
28
|
+
"doc/classes/OData/Operation.html",
|
29
|
+
"doc/classes/OData/QueryBuilder.html",
|
30
|
+
"doc/classes/OData/Service.html",
|
31
|
+
"doc/created.rid",
|
32
|
+
"doc/files/README_rdoc.html",
|
33
|
+
"doc/files/lib/odata_ruby/class_builder_rb.html",
|
34
|
+
"doc/files/lib/odata_ruby/operation_rb.html",
|
35
|
+
"doc/files/lib/odata_ruby/query_builder_rb.html",
|
36
|
+
"doc/files/lib/odata_ruby/service_rb.html",
|
37
|
+
"doc/files/lib/odata_ruby_rb.html",
|
38
|
+
"doc/fr_class_index.html",
|
39
|
+
"doc/fr_file_index.html",
|
40
|
+
"doc/fr_method_index.html",
|
41
|
+
"doc/index.html",
|
42
|
+
"doc/rdoc-style.css",
|
43
|
+
"features/service.feature",
|
44
|
+
"features/service_manage.feature",
|
45
|
+
"features/step_definitions/service_steps.rb",
|
46
|
+
"features/support/env.rb",
|
47
|
+
"features/support/hooks.rb",
|
48
|
+
"lib/odata_ruby.rb",
|
49
|
+
"lib/odata_ruby/class_builder.rb",
|
50
|
+
"lib/odata_ruby/operation.rb",
|
51
|
+
"lib/odata_ruby/query_builder.rb",
|
52
|
+
"lib/odata_ruby/service.rb",
|
53
|
+
"ruby_odata.gemspec",
|
54
|
+
"test/Cassini x64.bat",
|
55
|
+
"test/Cassini x86.bat",
|
56
|
+
"test/SampleService/App_Code/Entities.cs",
|
57
|
+
"test/SampleService/App_Code/Model.Designer.cs",
|
58
|
+
"test/SampleService/App_Code/Model.edmx",
|
59
|
+
"test/SampleService/App_Data/_TestDB.mdf",
|
60
|
+
"test/SampleService/App_Data/_TestDB_Log.ldf",
|
61
|
+
"test/SampleService/Entities.svc",
|
62
|
+
"test/SampleService/web.config",
|
63
|
+
"test/blueprints.rb"
|
64
|
+
]
|
65
|
+
s.homepage = %q{http://github.com/visoft/ruby_odata}
|
66
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
67
|
+
s.require_paths = ["lib"]
|
68
|
+
s.rubyforge_project = %q{ruby-odata}
|
69
|
+
s.rubygems_version = %q{1.3.7}
|
70
|
+
s.summary = %q{Ruby consumer of OData services.}
|
71
|
+
s.test_files = [
|
72
|
+
"test/blueprints.rb"
|
73
|
+
]
|
74
|
+
|
75
|
+
if s.respond_to? :specification_version then
|
76
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
77
|
+
s.specification_version = 3
|
78
|
+
|
79
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
80
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 2.3.5"])
|
81
|
+
s.add_runtime_dependency(%q<rest-client>, [">= 1.5.1"])
|
82
|
+
s.add_runtime_dependency(%q<nokogiri>, [">= 1.4.2"])
|
83
|
+
else
|
84
|
+
s.add_dependency(%q<activesupport>, [">= 2.3.5"])
|
85
|
+
s.add_dependency(%q<rest-client>, [">= 1.5.1"])
|
86
|
+
s.add_dependency(%q<nokogiri>, [">= 1.4.2"])
|
87
|
+
end
|
88
|
+
else
|
89
|
+
s.add_dependency(%q<activesupport>, [">= 2.3.5"])
|
90
|
+
s.add_dependency(%q<rest-client>, [">= 1.5.1"])
|
91
|
+
s.add_dependency(%q<nokogiri>, [">= 1.4.2"])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
"%ProgramFiles(x86)%\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE" /port:8888 /path:"%~dp0SampleService" /vpath:"/SampleService"
|
@@ -0,0 +1 @@
|
|
1
|
+
"%ProgramFiles%\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE" /port:8888 /path:"%~dp0SampleService" /vpath:"/SampleService"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
using System.Data.Services;
|
2
|
+
using System.Data.Services.Common;
|
3
|
+
using System.ServiceModel.Web;
|
4
|
+
using System.Web;
|
5
|
+
using Model;
|
6
|
+
|
7
|
+
public class Entities : DataService< ModelContainer >
|
8
|
+
{
|
9
|
+
// This method is called only once to initialize service-wide policies.
|
10
|
+
public static void InitializeService(DataServiceConfiguration config)
|
11
|
+
{
|
12
|
+
config.SetEntitySetAccessRule("*", EntitySetRights.All);
|
13
|
+
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
|
14
|
+
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
|
15
|
+
}
|
16
|
+
|
17
|
+
/// <summary>
|
18
|
+
/// Cleans the database for testing.
|
19
|
+
/// </summary>
|
20
|
+
[WebInvoke]
|
21
|
+
public void CleanDatabaseForTesting()
|
22
|
+
{
|
23
|
+
var context = new ModelContainer();
|
24
|
+
context.ExecuteStoreCommand("ALTER TABLE [dbo].[Products] DROP CONSTRAINT [FK_CategoryProduct]");
|
25
|
+
context.ExecuteStoreCommand("TRUNCATE TABLE [dbo].[Categories]; TRUNCATE TABLE [dbo].[Products]");
|
26
|
+
context.ExecuteStoreCommand("ALTER TABLE [dbo].[Products] ADD CONSTRAINT [FK_CategoryProduct] FOREIGN KEY ([Category_Id]) REFERENCES [dbo].[Categories]([Id])");
|
27
|
+
|
28
|
+
}
|
29
|
+
|
30
|
+
protected override void OnStartProcessingRequest(ProcessRequestArgs args)
|
31
|
+
{
|
32
|
+
base.OnStartProcessingRequest(args);
|
33
|
+
if (args.RequestUri.AbsoluteUri.ToLower().EndsWith("cleandatabasefortesting"))
|
34
|
+
{
|
35
|
+
if (HttpContext.Current.Request.UserHostAddress != "127.0.0.1")
|
36
|
+
throw new DataServiceException(401, "Access Denied");
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|