frodata 0.9.1
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.
- checksums.yaml +7 -0
- data/.autotest +2 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +75 -0
- data/CHANGELOG.md +150 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +427 -0
- data/Rakefile +7 -0
- data/TODO.md +55 -0
- data/frodata.gemspec +34 -0
- data/lib/frodata.rb +36 -0
- data/lib/frodata/entity.rb +332 -0
- data/lib/frodata/entity_container.rb +75 -0
- data/lib/frodata/entity_set.rb +161 -0
- data/lib/frodata/errors.rb +68 -0
- data/lib/frodata/navigation_property.rb +29 -0
- data/lib/frodata/navigation_property/proxy.rb +80 -0
- data/lib/frodata/properties.rb +32 -0
- data/lib/frodata/properties/binary.rb +50 -0
- data/lib/frodata/properties/boolean.rb +37 -0
- data/lib/frodata/properties/collection.rb +50 -0
- data/lib/frodata/properties/complex.rb +114 -0
- data/lib/frodata/properties/date.rb +27 -0
- data/lib/frodata/properties/date_time.rb +83 -0
- data/lib/frodata/properties/date_time_offset.rb +17 -0
- data/lib/frodata/properties/decimal.rb +50 -0
- data/lib/frodata/properties/enum.rb +62 -0
- data/lib/frodata/properties/float.rb +67 -0
- data/lib/frodata/properties/geography.rb +13 -0
- data/lib/frodata/properties/geography/base.rb +162 -0
- data/lib/frodata/properties/geography/line_string.rb +33 -0
- data/lib/frodata/properties/geography/point.rb +31 -0
- data/lib/frodata/properties/geography/polygon.rb +38 -0
- data/lib/frodata/properties/guid.rb +17 -0
- data/lib/frodata/properties/integer.rb +107 -0
- data/lib/frodata/properties/number.rb +14 -0
- data/lib/frodata/properties/string.rb +72 -0
- data/lib/frodata/properties/time.rb +40 -0
- data/lib/frodata/properties/time_of_day.rb +27 -0
- data/lib/frodata/property.rb +139 -0
- data/lib/frodata/property_registry.rb +41 -0
- data/lib/frodata/query.rb +233 -0
- data/lib/frodata/query/criteria.rb +92 -0
- data/lib/frodata/query/criteria/comparison_operators.rb +49 -0
- data/lib/frodata/query/criteria/date_functions.rb +61 -0
- data/lib/frodata/query/criteria/geography_functions.rb +21 -0
- data/lib/frodata/query/criteria/lambda_operators.rb +27 -0
- data/lib/frodata/query/criteria/string_functions.rb +40 -0
- data/lib/frodata/query/in_batches.rb +58 -0
- data/lib/frodata/railtie.rb +19 -0
- data/lib/frodata/schema.rb +155 -0
- data/lib/frodata/schema/complex_type.rb +79 -0
- data/lib/frodata/schema/enum_type.rb +95 -0
- data/lib/frodata/service.rb +254 -0
- data/lib/frodata/service/request.rb +85 -0
- data/lib/frodata/service/response.rb +162 -0
- data/lib/frodata/service/response/atom.rb +40 -0
- data/lib/frodata/service/response/json.rb +41 -0
- data/lib/frodata/service/response/plain.rb +36 -0
- data/lib/frodata/service/response/xml.rb +40 -0
- data/lib/frodata/service_registry.rb +52 -0
- data/lib/frodata/version.rb +3 -0
- data/spec/fixtures/files/entity_to_xml.xml +17 -0
- data/spec/fixtures/files/error.xml +5 -0
- data/spec/fixtures/files/metadata.xml +150 -0
- data/spec/fixtures/files/product_0.json +10 -0
- data/spec/fixtures/files/product_0.xml +28 -0
- data/spec/fixtures/files/products.json +106 -0
- data/spec/fixtures/files/products.xml +308 -0
- data/spec/fixtures/files/supplier_0.json +26 -0
- data/spec/fixtures/files/supplier_0.xml +32 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs.yml +1635 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/bad_entry.yml +183 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/existing_entry.yml +256 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/new_entry.yml +185 -0
- data/spec/fixtures/vcr_cassettes/entity_specs.yml +285 -0
- data/spec/fixtures/vcr_cassettes/navigation_property_proxy_specs.yml +346 -0
- data/spec/fixtures/vcr_cassettes/query/result_specs.yml +189 -0
- data/spec/fixtures/vcr_cassettes/query_specs.yml +1060 -0
- data/spec/fixtures/vcr_cassettes/schema/complex_type_specs.yml +127 -0
- data/spec/fixtures/vcr_cassettes/service/request_specs.yml +193 -0
- data/spec/fixtures/vcr_cassettes/service_registry_specs.yml +129 -0
- data/spec/fixtures/vcr_cassettes/service_specs.yml +127 -0
- data/spec/fixtures/vcr_cassettes/usage_example_specs.yml +1330 -0
- data/spec/frodata/entity/shared_examples.rb +82 -0
- data/spec/frodata/entity_container_spec.rb +38 -0
- data/spec/frodata/entity_set_spec.rb +168 -0
- data/spec/frodata/entity_spec.rb +151 -0
- data/spec/frodata/errors_spec.rb +48 -0
- data/spec/frodata/navigation_property/proxy_spec.rb +44 -0
- data/spec/frodata/navigation_property_spec.rb +55 -0
- data/spec/frodata/properties/binary_spec.rb +50 -0
- data/spec/frodata/properties/boolean_spec.rb +72 -0
- data/spec/frodata/properties/collection_spec.rb +44 -0
- data/spec/frodata/properties/date_spec.rb +23 -0
- data/spec/frodata/properties/date_time_offset_spec.rb +30 -0
- data/spec/frodata/properties/date_time_spec.rb +23 -0
- data/spec/frodata/properties/decimal_spec.rb +51 -0
- data/spec/frodata/properties/float_spec.rb +45 -0
- data/spec/frodata/properties/geography/line_string_spec.rb +33 -0
- data/spec/frodata/properties/geography/point_spec.rb +29 -0
- data/spec/frodata/properties/geography/polygon_spec.rb +55 -0
- data/spec/frodata/properties/geography/shared_examples.rb +72 -0
- data/spec/frodata/properties/guid_spec.rb +17 -0
- data/spec/frodata/properties/integer_spec.rb +58 -0
- data/spec/frodata/properties/string_spec.rb +46 -0
- data/spec/frodata/properties/time_of_day_spec.rb +23 -0
- data/spec/frodata/properties/time_spec.rb +15 -0
- data/spec/frodata/property_registry_spec.rb +16 -0
- data/spec/frodata/property_spec.rb +71 -0
- data/spec/frodata/query/criteria_spec.rb +229 -0
- data/spec/frodata/query_spec.rb +199 -0
- data/spec/frodata/schema/complex_type_spec.rb +96 -0
- data/spec/frodata/schema/enum_type_spec.rb +112 -0
- data/spec/frodata/schema_spec.rb +97 -0
- data/spec/frodata/service/request_spec.rb +49 -0
- data/spec/frodata/service/response_spec.rb +85 -0
- data/spec/frodata/service_registry_spec.rb +18 -0
- data/spec/frodata/service_spec.rb +191 -0
- data/spec/frodata/usage_example_spec.rb +188 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/coverage.rb +2 -0
- data/spec/support/vcr.rb +9 -0
- metadata +401 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
#
|
|
3
|
+
class EntityContainer
|
|
4
|
+
# The EntityContainer's parent service
|
|
5
|
+
attr_reader :service
|
|
6
|
+
# The EntityContainer's metadata
|
|
7
|
+
attr_reader :metadata
|
|
8
|
+
|
|
9
|
+
# Creates a new EntityContainer
|
|
10
|
+
# @param service [FrOData::Service] The entity container's parent service
|
|
11
|
+
def initialize(service)
|
|
12
|
+
@metadata = service.metadata.xpath('//EntityContainer').first
|
|
13
|
+
@service = service
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The EntityContainer's surrounding Schema
|
|
17
|
+
# @return [Nokogiri::XML]
|
|
18
|
+
def schema
|
|
19
|
+
@schema ||= metadata.ancestors('Schema').first
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the EntityContainer's namespace.
|
|
23
|
+
# @return [String]
|
|
24
|
+
def namespace
|
|
25
|
+
@namespace ||= schema.attributes['Namespace'].value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the EntityContainer's name.
|
|
29
|
+
# @return [String]
|
|
30
|
+
def name
|
|
31
|
+
@name ||= metadata.attributes['Name'].value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns a hash of EntitySet names and their respective EntityTypes.
|
|
35
|
+
# @return [Hash<String, String>]
|
|
36
|
+
def entity_sets
|
|
37
|
+
@entity_sets ||= metadata.xpath('//EntitySet').map do |entity|
|
|
38
|
+
[
|
|
39
|
+
entity.attributes['Name'].value,
|
|
40
|
+
entity.attributes['EntityType'].value
|
|
41
|
+
]
|
|
42
|
+
end.to_h
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Retrieves the EntitySet associated with a specific EntityType by name
|
|
46
|
+
#
|
|
47
|
+
# @param entity_set_name [to_s] the name of the EntitySet desired
|
|
48
|
+
# @return [FrOData::EntitySet] an FrOData::EntitySet to query
|
|
49
|
+
def [](entity_set_name)
|
|
50
|
+
xpath_query = "//EntitySet[@Name='#{entity_set_name}']"
|
|
51
|
+
entity_set_node = metadata.xpath(xpath_query).first
|
|
52
|
+
raise ArgumentError, "Unknown Entity Set: #{entity_set_name}" if entity_set_node.nil?
|
|
53
|
+
entity_type = entity_set_node.attributes['EntityType'].value
|
|
54
|
+
FrOData::EntitySet.new(
|
|
55
|
+
name: entity_set_name,
|
|
56
|
+
namespace: namespace,
|
|
57
|
+
type: entity_type,
|
|
58
|
+
service_name: service.name,
|
|
59
|
+
container: name
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def singletons
|
|
64
|
+
# TODO return singletons exposed by this EntityContainer
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def actions
|
|
68
|
+
# TODO return action imports exposed by this EntityContainer
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def functions
|
|
72
|
+
# TODO return function imports exposed by this EntityContainer
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
# This class represents a set of entities within an FrOData service. It is
|
|
3
|
+
# instantiated whenever an FrOData::Service is asked for an EntitySet via the
|
|
4
|
+
# FrOData::Service#[] method call. It also provides Enumerable behavior so that
|
|
5
|
+
# you can interact with the entities within a set in a very comfortable way.
|
|
6
|
+
#
|
|
7
|
+
# This class also implements a query interface for finding certain entities
|
|
8
|
+
# based on query criteria or limiting the result set returned by the set. This
|
|
9
|
+
# functionality is implemented through transparent proxy objects.
|
|
10
|
+
class EntitySet
|
|
11
|
+
include Enumerable
|
|
12
|
+
|
|
13
|
+
# The name of the EntitySet
|
|
14
|
+
attr_reader :name
|
|
15
|
+
# The Entity type for the EntitySet
|
|
16
|
+
attr_reader :type
|
|
17
|
+
# The FrOData::Service's namespace
|
|
18
|
+
attr_reader :namespace
|
|
19
|
+
# The FrOData::Service's identifiable name
|
|
20
|
+
attr_reader :service_name
|
|
21
|
+
# The EntitySet's container name
|
|
22
|
+
attr_reader :container
|
|
23
|
+
|
|
24
|
+
# Sets up the EntitySet to permit querying for the resources in the set.
|
|
25
|
+
#
|
|
26
|
+
# @param options [Hash] the options to setup the EntitySet
|
|
27
|
+
# @return [FrOData::EntitySet] an instance of the EntitySet
|
|
28
|
+
def initialize(options = {})
|
|
29
|
+
@name = options[:name]
|
|
30
|
+
@type = options[:type]
|
|
31
|
+
@namespace = options[:namespace]
|
|
32
|
+
@service_name = options[:service_name]
|
|
33
|
+
@container = options[:container]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Provided for Enumerable functionality
|
|
37
|
+
#
|
|
38
|
+
# @param block [block] a block to evaluate
|
|
39
|
+
# @return [FrOData::Entity] each entity in turn from this set
|
|
40
|
+
def each(&block)
|
|
41
|
+
query.execute.each(&block)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Return the first `n` Entities for the set.
|
|
45
|
+
# If count is 1 it returns the single entity, otherwise its an array of entities
|
|
46
|
+
# @return [FrOData::EntitySet]
|
|
47
|
+
def first(count = 1)
|
|
48
|
+
result = query.limit(count).execute
|
|
49
|
+
count == 1 ? result.first : result.to_a
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns the number of entities within the set.
|
|
53
|
+
# Not supported in Microsoft CRM2011
|
|
54
|
+
# @return [Integer]
|
|
55
|
+
def count
|
|
56
|
+
query.count
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Create a new Entity for this set with the given properties.
|
|
60
|
+
# @param properties [Hash] property name as key and it's initial value
|
|
61
|
+
# @return [FrOData::Entity]
|
|
62
|
+
def new_entity(properties = {})
|
|
63
|
+
FrOData::Entity.with_properties(properties, entity_options)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns a query targetted at the current EntitySet.
|
|
67
|
+
# @param options [Hash] query options
|
|
68
|
+
# @return [FrOData::Query]
|
|
69
|
+
def query(options = {})
|
|
70
|
+
FrOData::Query.new(self, options)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Find the Entity with the supplied key value.
|
|
74
|
+
# @param key [to_s] primary key to lookup
|
|
75
|
+
# @return [FrOData::Entity,nil]
|
|
76
|
+
def [](key, options={})
|
|
77
|
+
properties_to_expand = if options[:expand] == :all
|
|
78
|
+
new_entity.navigation_property_names
|
|
79
|
+
else
|
|
80
|
+
[ options[:expand] ].compact.flatten
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
query.expand(*properties_to_expand).find(key)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Write supplied entity back to the service.
|
|
87
|
+
# TODO Test this more with CRM2011
|
|
88
|
+
# @param entity [FrOData::Entity] entity to save or update in the service
|
|
89
|
+
# @return [FrOData::Entity]
|
|
90
|
+
def <<(entity)
|
|
91
|
+
url_chunk, options = setup_entity_post_request(entity)
|
|
92
|
+
result = execute_entity_post_request(options, url_chunk)
|
|
93
|
+
if entity.is_new?
|
|
94
|
+
doc = ::Nokogiri::XML(result.body).remove_namespaces!
|
|
95
|
+
primary_key_node = doc.xpath("//content/properties/#{entity.primary_key}").first
|
|
96
|
+
entity[entity.primary_key] = primary_key_node.content unless primary_key_node.nil?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
unless result.code.to_s =~ /^2[0-9][0-9]$/
|
|
100
|
+
entity.errors << ['could not commit entity']
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
entity
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The FrOData::Service this EntitySet is associated with.
|
|
107
|
+
# @return [FrOData::Service]
|
|
108
|
+
# @api private
|
|
109
|
+
def service
|
|
110
|
+
@service ||= FrOData::ServiceRegistry[service_name]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Options used for instantiating a new FrOData::Entity for this set.
|
|
114
|
+
# @return [Hash]
|
|
115
|
+
# @api private
|
|
116
|
+
def entity_options
|
|
117
|
+
{
|
|
118
|
+
service_name: service_name,
|
|
119
|
+
type: type,
|
|
120
|
+
entity_set: self
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def execute_entity_post_request(options, url_chunk)
|
|
127
|
+
result = service.execute(url_chunk, options)
|
|
128
|
+
unless result.code.to_s =~ /^2[0-9][0-9]$/
|
|
129
|
+
service.logger.debug <<-EOS
|
|
130
|
+
[ODATA: #{service_name}]
|
|
131
|
+
An error was encountered committing your entity:
|
|
132
|
+
|
|
133
|
+
POSTed URL:
|
|
134
|
+
#{url_chunk}
|
|
135
|
+
|
|
136
|
+
POSTed Entity:
|
|
137
|
+
#{options[:body]}
|
|
138
|
+
|
|
139
|
+
Result Body:
|
|
140
|
+
#{result.body}
|
|
141
|
+
EOS
|
|
142
|
+
service.logger.info "[ODATA: #{service_name}] Unable to commit data to #{url_chunk}"
|
|
143
|
+
end
|
|
144
|
+
result
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def setup_entity_post_request(entity)
|
|
148
|
+
primary_key = entity.get_property(entity.primary_key).url_value
|
|
149
|
+
chunk = entity.is_new? ? name : "#{name}(#{primary_key})"
|
|
150
|
+
options = {
|
|
151
|
+
method: :post,
|
|
152
|
+
body: entity.to_xml.gsub(/\n\s+/, ''),
|
|
153
|
+
headers: {
|
|
154
|
+
'Accept' => 'application/atom+xml',
|
|
155
|
+
'Content-Type' => 'application/atom+xml'
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return chunk, options
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
# Base class for FrOData errors
|
|
3
|
+
class Error < StandardError
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
# Base class for network errors
|
|
7
|
+
class RequestError < Error
|
|
8
|
+
attr_reader :response
|
|
9
|
+
|
|
10
|
+
def initialize(response, message = nil)
|
|
11
|
+
@message = message
|
|
12
|
+
@response = response
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def http_status
|
|
16
|
+
response.status
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def message
|
|
20
|
+
[default_message, @message].compact.join(': ')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def default_message
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ClientError < RequestError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ServerError < RequestError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module Errors
|
|
35
|
+
ERROR_MAP = []
|
|
36
|
+
|
|
37
|
+
CLIENT_ERRORS = {
|
|
38
|
+
400 => "Bad Request",
|
|
39
|
+
401 => "Access Denied",
|
|
40
|
+
403 => "Forbidden",
|
|
41
|
+
404 => "Not Found",
|
|
42
|
+
405 => "Method Not Allowed",
|
|
43
|
+
406 => "Not Acceptable",
|
|
44
|
+
413 => "Request Entity Too Large"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
CLIENT_ERRORS.each do |code, message|
|
|
48
|
+
klass = Class.new(ClientError) do
|
|
49
|
+
send(:define_method, :default_message) { "#{code} #{message}" }
|
|
50
|
+
end
|
|
51
|
+
const_set(message.delete(' \-\''), klass)
|
|
52
|
+
ERROR_MAP[code] = klass
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
SERVER_ERRORS = {
|
|
56
|
+
500 => "Internal Server Error",
|
|
57
|
+
503 => "Service Unavailable"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
SERVER_ERRORS.each do |code, message|
|
|
61
|
+
klass = Class.new(ServerError) do
|
|
62
|
+
send(:define_method, :default_message) { "#{code} #{message}" }
|
|
63
|
+
end
|
|
64
|
+
const_set(message.delete(' \-\''), klass)
|
|
65
|
+
ERROR_MAP[code] = klass
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'frodata/navigation_property/proxy'
|
|
2
|
+
|
|
3
|
+
module FrOData
|
|
4
|
+
class NavigationProperty
|
|
5
|
+
attr_reader :name, :type, :nullable, :partner
|
|
6
|
+
|
|
7
|
+
def initialize(options)
|
|
8
|
+
@name = options[:name] or raise ArgumentError, 'Name is required'
|
|
9
|
+
@type = options[:type] or raise ArgumentError, 'Type is required'
|
|
10
|
+
@nullable = options[:nullable] || true
|
|
11
|
+
@partner = options[:partner]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def nav_type
|
|
15
|
+
@nav_type ||= type =~ /^Collection/ ? :collection : :entity
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def entity_type
|
|
19
|
+
@entity_type ||= type.split(/[()]/).last
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.build(nav_property_xml)
|
|
23
|
+
options = nav_property_xml.attributes.map do |name, attr|
|
|
24
|
+
[name.downcase.to_sym, attr.value]
|
|
25
|
+
end.to_h
|
|
26
|
+
new(options)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
class NavigationProperty
|
|
3
|
+
class Proxy
|
|
4
|
+
def initialize(entity, nav_name)
|
|
5
|
+
@entity = entity
|
|
6
|
+
@nav_name = nav_name
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def value=(value)
|
|
10
|
+
@value = value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def value
|
|
14
|
+
if link.nil?
|
|
15
|
+
if nav_property.nav_type == :collection
|
|
16
|
+
[]
|
|
17
|
+
else
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
@value ||= fetch_result
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :entity, :nav_name
|
|
28
|
+
|
|
29
|
+
def service
|
|
30
|
+
@service ||= FrOData::ServiceRegistry[entity.service_name]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def namespace
|
|
34
|
+
@namespace ||= service.namespace
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def schema
|
|
38
|
+
@schema ||= service.schemas[namespace]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def entity_type
|
|
42
|
+
@entity_type ||= entity.name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def link
|
|
46
|
+
entity.links[nav_name]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def nav_property
|
|
50
|
+
schema.navigation_properties[entity_type][nav_name]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fetch_result
|
|
54
|
+
raise "Invalid navigation link for #{nav_name}" unless link[:href]
|
|
55
|
+
|
|
56
|
+
options = {
|
|
57
|
+
type: nav_property.entity_type,
|
|
58
|
+
namespace: namespace,
|
|
59
|
+
service_name: entity.service_name
|
|
60
|
+
}
|
|
61
|
+
entity_set = Struct.new(:service, :entity_options)
|
|
62
|
+
.new(entity.service, options)
|
|
63
|
+
|
|
64
|
+
query = FrOData::Query.new(entity_set)
|
|
65
|
+
begin
|
|
66
|
+
result = query.execute(link[:href])
|
|
67
|
+
rescue => ex
|
|
68
|
+
raise ex unless ex.message =~ /Not Found/
|
|
69
|
+
result = []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if nav_property.nav_type == :collection
|
|
73
|
+
result
|
|
74
|
+
else
|
|
75
|
+
result.first
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Modules
|
|
2
|
+
require 'frodata/properties/number'
|
|
3
|
+
|
|
4
|
+
# Implementations
|
|
5
|
+
require 'frodata/properties/binary'
|
|
6
|
+
require 'frodata/properties/boolean'
|
|
7
|
+
require 'frodata/properties/collection'
|
|
8
|
+
require 'frodata/properties/complex'
|
|
9
|
+
require 'frodata/properties/date'
|
|
10
|
+
require 'frodata/properties/date_time'
|
|
11
|
+
require 'frodata/properties/date_time_offset'
|
|
12
|
+
require 'frodata/properties/decimal'
|
|
13
|
+
require 'frodata/properties/enum'
|
|
14
|
+
require 'frodata/properties/float'
|
|
15
|
+
require 'frodata/properties/geography'
|
|
16
|
+
require 'frodata/properties/guid'
|
|
17
|
+
require 'frodata/properties/integer'
|
|
18
|
+
require 'frodata/properties/string'
|
|
19
|
+
require 'frodata/properties/time'
|
|
20
|
+
require 'frodata/properties/time_of_day'
|
|
21
|
+
|
|
22
|
+
FrOData::Properties.constants.each do |property_name|
|
|
23
|
+
klass = FrOData::Properties.const_get(property_name)
|
|
24
|
+
if klass.is_a?(Class)
|
|
25
|
+
begin
|
|
26
|
+
property = klass.new('test', nil)
|
|
27
|
+
FrOData::PropertyRegistry.add(property.type, property.class)
|
|
28
|
+
rescue NotImplementedError
|
|
29
|
+
# Abstract type classes cannot be instantiated, ignore
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|