frodo 0.10.0
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/.circleci/config.yml +54 -0
- data/.gitignore +24 -0
- data/.gitlab-ci.yml +9 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +75 -0
- data/CHANGELOG.md +163 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +479 -0
- data/Rakefile +7 -0
- data/TODO.md +55 -0
- data/frodo.gemspec +39 -0
- data/images/frodo.jpg +0 -0
- data/lib/frodo/abstract_client.rb +11 -0
- data/lib/frodo/client.rb +6 -0
- data/lib/frodo/concerns/api.rb +292 -0
- data/lib/frodo/concerns/authentication.rb +32 -0
- data/lib/frodo/concerns/base.rb +84 -0
- data/lib/frodo/concerns/caching.rb +26 -0
- data/lib/frodo/concerns/connection.rb +79 -0
- data/lib/frodo/concerns/verbs.rb +68 -0
- data/lib/frodo/config.rb +143 -0
- data/lib/frodo/entity.rb +335 -0
- data/lib/frodo/entity_container.rb +75 -0
- data/lib/frodo/entity_set.rb +131 -0
- data/lib/frodo/errors.rb +70 -0
- data/lib/frodo/middleware/authentication/token.rb +13 -0
- data/lib/frodo/middleware/authentication.rb +87 -0
- data/lib/frodo/middleware/authorization.rb +18 -0
- data/lib/frodo/middleware/caching.rb +30 -0
- data/lib/frodo/middleware/custom_headers.rb +14 -0
- data/lib/frodo/middleware/gzip.rb +33 -0
- data/lib/frodo/middleware/instance_url.rb +20 -0
- data/lib/frodo/middleware/logger.rb +42 -0
- data/lib/frodo/middleware/multipart.rb +64 -0
- data/lib/frodo/middleware/odata_headers.rb +13 -0
- data/lib/frodo/middleware/raise_error.rb +47 -0
- data/lib/frodo/middleware.rb +33 -0
- data/lib/frodo/navigation_property/proxy.rb +80 -0
- data/lib/frodo/navigation_property.rb +29 -0
- data/lib/frodo/properties/binary.rb +50 -0
- data/lib/frodo/properties/boolean.rb +37 -0
- data/lib/frodo/properties/collection.rb +50 -0
- data/lib/frodo/properties/complex.rb +114 -0
- data/lib/frodo/properties/date.rb +27 -0
- data/lib/frodo/properties/date_time.rb +83 -0
- data/lib/frodo/properties/date_time_offset.rb +17 -0
- data/lib/frodo/properties/decimal.rb +54 -0
- data/lib/frodo/properties/enum.rb +62 -0
- data/lib/frodo/properties/float.rb +67 -0
- data/lib/frodo/properties/geography/base.rb +162 -0
- data/lib/frodo/properties/geography/line_string.rb +33 -0
- data/lib/frodo/properties/geography/point.rb +31 -0
- data/lib/frodo/properties/geography/polygon.rb +38 -0
- data/lib/frodo/properties/geography.rb +13 -0
- data/lib/frodo/properties/guid.rb +17 -0
- data/lib/frodo/properties/integer.rb +107 -0
- data/lib/frodo/properties/number.rb +14 -0
- data/lib/frodo/properties/string.rb +72 -0
- data/lib/frodo/properties/time.rb +40 -0
- data/lib/frodo/properties/time_of_day.rb +27 -0
- data/lib/frodo/properties.rb +32 -0
- data/lib/frodo/property.rb +139 -0
- data/lib/frodo/property_registry.rb +41 -0
- data/lib/frodo/query/criteria/comparison_operators.rb +49 -0
- data/lib/frodo/query/criteria/date_functions.rb +61 -0
- data/lib/frodo/query/criteria/geography_functions.rb +21 -0
- data/lib/frodo/query/criteria/lambda_operators.rb +27 -0
- data/lib/frodo/query/criteria/string_functions.rb +40 -0
- data/lib/frodo/query/criteria.rb +92 -0
- data/lib/frodo/query/in_batches.rb +58 -0
- data/lib/frodo/query.rb +221 -0
- data/lib/frodo/railtie.rb +19 -0
- data/lib/frodo/schema/complex_type.rb +79 -0
- data/lib/frodo/schema/enum_type.rb +95 -0
- data/lib/frodo/schema.rb +164 -0
- data/lib/frodo/service.rb +199 -0
- data/lib/frodo/service_registry.rb +52 -0
- data/lib/frodo/version.rb +3 -0
- data/lib/frodo.rb +67 -0
- data/spec/fixtures/auth_success_response.json +11 -0
- data/spec/fixtures/error.json +11 -0
- data/spec/fixtures/files/entity_to_xml.xml +18 -0
- data/spec/fixtures/files/error.xml +5 -0
- data/spec/fixtures/files/metadata.xml +150 -0
- data/spec/fixtures/files/metadata_with_error.xml +157 -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/leads.json +923 -0
- data/spec/fixtures/refresh_error_response.json +8 -0
- data/spec/frodo/abstract_client_spec.rb +13 -0
- data/spec/frodo/client_spec.rb +57 -0
- data/spec/frodo/concerns/authentication_spec.rb +79 -0
- data/spec/frodo/concerns/base_spec.rb +68 -0
- data/spec/frodo/concerns/caching_spec.rb +40 -0
- data/spec/frodo/concerns/connection_spec.rb +65 -0
- data/spec/frodo/config_spec.rb +127 -0
- data/spec/frodo/entity/shared_examples.rb +83 -0
- data/spec/frodo/entity_container_spec.rb +38 -0
- data/spec/frodo/entity_set_spec.rb +169 -0
- data/spec/frodo/entity_spec.rb +153 -0
- data/spec/frodo/errors_spec.rb +48 -0
- data/spec/frodo/middleware/authentication/token_spec.rb +87 -0
- data/spec/frodo/middleware/authentication_spec.rb +83 -0
- data/spec/frodo/middleware/authorization_spec.rb +17 -0
- data/spec/frodo/middleware/custom_headers_spec.rb +21 -0
- data/spec/frodo/middleware/gzip_spec.rb +68 -0
- data/spec/frodo/middleware/instance_url_spec.rb +27 -0
- data/spec/frodo/middleware/logger_spec.rb +21 -0
- data/spec/frodo/middleware/odata_headers_spec.rb +15 -0
- data/spec/frodo/middleware/raise_error_spec.rb +66 -0
- data/spec/frodo/navigation_property/proxy_spec.rb +46 -0
- data/spec/frodo/navigation_property_spec.rb +55 -0
- data/spec/frodo/properties/binary_spec.rb +50 -0
- data/spec/frodo/properties/boolean_spec.rb +72 -0
- data/spec/frodo/properties/collection_spec.rb +44 -0
- data/spec/frodo/properties/date_spec.rb +23 -0
- data/spec/frodo/properties/date_time_offset_spec.rb +30 -0
- data/spec/frodo/properties/date_time_spec.rb +23 -0
- data/spec/frodo/properties/decimal_spec.rb +50 -0
- data/spec/frodo/properties/float_spec.rb +45 -0
- data/spec/frodo/properties/geography/line_string_spec.rb +33 -0
- data/spec/frodo/properties/geography/point_spec.rb +29 -0
- data/spec/frodo/properties/geography/polygon_spec.rb +55 -0
- data/spec/frodo/properties/geography/shared_examples.rb +72 -0
- data/spec/frodo/properties/guid_spec.rb +17 -0
- data/spec/frodo/properties/integer_spec.rb +58 -0
- data/spec/frodo/properties/string_spec.rb +46 -0
- data/spec/frodo/properties/time_of_day_spec.rb +23 -0
- data/spec/frodo/properties/time_spec.rb +15 -0
- data/spec/frodo/property_registry_spec.rb +16 -0
- data/spec/frodo/property_spec.rb +71 -0
- data/spec/frodo/query/criteria_spec.rb +229 -0
- data/spec/frodo/query_spec.rb +156 -0
- data/spec/frodo/schema/complex_type_spec.rb +97 -0
- data/spec/frodo/schema/enum_type_spec.rb +112 -0
- data/spec/frodo/schema_spec.rb +113 -0
- data/spec/frodo/service_registry_spec.rb +19 -0
- data/spec/frodo/service_spec.rb +153 -0
- data/spec/frodo/usage_example_spec.rb +161 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/coverage.rb +2 -0
- data/spec/support/fixture_helpers.rb +14 -0
- data/spec/support/middleware.rb +19 -0
- metadata +479 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
# Defines the Collection Frodo type.
|
|
4
|
+
class Collection < Frodo::Property
|
|
5
|
+
# Overriding default constructor to avoid converting
|
|
6
|
+
# value to string.
|
|
7
|
+
# TODO: Make this the default for all property types?
|
|
8
|
+
def initialize(name, value, options = {})
|
|
9
|
+
super(name, value, options)
|
|
10
|
+
self.value = value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def value
|
|
14
|
+
if @value.nil?
|
|
15
|
+
nil
|
|
16
|
+
else
|
|
17
|
+
@value.map(&:value)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def value=(value)
|
|
22
|
+
if value.nil? && allows_nil?
|
|
23
|
+
@value = nil
|
|
24
|
+
elsif value.respond_to?(:map)
|
|
25
|
+
@value = value.map.with_index do |element, index|
|
|
26
|
+
type_class.new("#{name}[#{index}]", element)
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
validation_error 'Value must be an array'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def url_value
|
|
34
|
+
'[' + @value.map(&:url_value).join(',') + ']'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def type
|
|
38
|
+
"Collection(#{value_type})"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def value_type
|
|
42
|
+
options[:value_type] || 'Edm.String'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def type_class
|
|
46
|
+
Frodo::PropertyRegistry[value_type]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
# Abstract base class for Frodo ComplexTypes
|
|
4
|
+
# @see [Frodo::Schema::ComplexType]
|
|
5
|
+
class Complex < Frodo::Property
|
|
6
|
+
def initialize(name, value, options = {})
|
|
7
|
+
super(name, value, options)
|
|
8
|
+
init_properties
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Returns the property value, properly typecast
|
|
12
|
+
# @return [Hash, nil]
|
|
13
|
+
def value
|
|
14
|
+
if allows_nil? && properties.values.all?(&:nil?)
|
|
15
|
+
nil
|
|
16
|
+
else
|
|
17
|
+
Hash[properties.map { |key, value| [key, value.value] }]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Sets the property value
|
|
22
|
+
# @params new_value [Hash]
|
|
23
|
+
def value=(new_value)
|
|
24
|
+
validate(new_value)
|
|
25
|
+
if new_value.nil?
|
|
26
|
+
property_names.each { |name| self[name] = nil }
|
|
27
|
+
else
|
|
28
|
+
property_names.each { |name| self[name] = new_value[name] }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns a list of this ComplexType's property names.
|
|
33
|
+
# @return [Array<String>]
|
|
34
|
+
def property_names
|
|
35
|
+
@property_names ||= properties.keys
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns the value of the requested property.
|
|
39
|
+
# @param property_name [to_s]
|
|
40
|
+
# @return [*]
|
|
41
|
+
def [](property_name)
|
|
42
|
+
properties[property_name.to_s].value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Sets the value of the named property.
|
|
46
|
+
# @param property_name [to_s]
|
|
47
|
+
# @param value [*]
|
|
48
|
+
# @return [*]
|
|
49
|
+
def []=(property_name, value)
|
|
50
|
+
properties[property_name.to_s].value = value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the XML representation of the property to the supplied XML
|
|
54
|
+
# builder.
|
|
55
|
+
# @param xml_builder [Nokogiri::XML::Builder]
|
|
56
|
+
def to_xml(xml_builder)
|
|
57
|
+
attributes = {
|
|
58
|
+
'metadata:type' => type,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
xml_builder['data'].send(name.to_sym, attributes) do
|
|
62
|
+
properties.each do |name, property|
|
|
63
|
+
property.to_xml(xml_builder)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Creates a new property instance from an XML element
|
|
69
|
+
# @param property_xml [Nokogiri::XML::Element]
|
|
70
|
+
# @param options [Hash]
|
|
71
|
+
# @return [Frodo::Property]
|
|
72
|
+
def self.from_xml(property_xml, options = {})
|
|
73
|
+
nodes = property_xml.element_children
|
|
74
|
+
props = Hash[nodes.map { |el| [el.name, el.content] }]
|
|
75
|
+
new(property_xml.name, props.to_json, options)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def complex_type
|
|
81
|
+
raise NotImplementedError, 'Subclass must override'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def properties
|
|
85
|
+
@properties
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def init_properties
|
|
89
|
+
@properties = complex_type.send(:collect_properties)
|
|
90
|
+
set_properties(JSON.parse(@value)) unless @value.nil?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def set_properties(new_properties)
|
|
94
|
+
property_names.each do |prop_name|
|
|
95
|
+
self[prop_name] = new_properties[prop_name]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def validate(value)
|
|
100
|
+
return if value.nil? && allows_nil?
|
|
101
|
+
raise ArgumentError, 'Value must be a Hash' unless value.is_a?(Hash)
|
|
102
|
+
value.keys.each do |name|
|
|
103
|
+
unless property_names.include?(name) || name =~ /@odata/
|
|
104
|
+
raise ArgumentError, "Invalid property #{name}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def validate_options(options)
|
|
110
|
+
raise ArgumentError, 'Type is required' unless options[:type]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'frodo/properties/date_time'
|
|
2
|
+
|
|
3
|
+
module Frodo
|
|
4
|
+
module Properties
|
|
5
|
+
# Defines the Date Frodo type.
|
|
6
|
+
class Date < Frodo::Properties::DateTime
|
|
7
|
+
# The Frodo type name
|
|
8
|
+
def type
|
|
9
|
+
'Edm.Date'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def url_value
|
|
13
|
+
@value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def date_class
|
|
19
|
+
::Date
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def strptime_format
|
|
23
|
+
'%Y-%m-%d'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
# Defines the DateTime Frodo type.
|
|
4
|
+
class DateTime < Frodo::Property
|
|
5
|
+
# Returns the property value, properly typecast
|
|
6
|
+
# @return [DateTime, nil]
|
|
7
|
+
def value
|
|
8
|
+
if (@value.nil? || @value.empty?) && allows_nil?
|
|
9
|
+
nil
|
|
10
|
+
else
|
|
11
|
+
begin
|
|
12
|
+
date_class.strptime(@value, strptime_format)
|
|
13
|
+
rescue ArgumentError
|
|
14
|
+
date_class.parse(@value)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Sets the property value
|
|
20
|
+
# @params new_value [DateTime]
|
|
21
|
+
def value=(new_value)
|
|
22
|
+
validate(new_value)
|
|
23
|
+
@value = parse_value(new_value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The Frodo type name
|
|
27
|
+
def type
|
|
28
|
+
'Edm.DateTime'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Value to be used in JSON.
|
|
32
|
+
# @return [String]
|
|
33
|
+
def xml_value
|
|
34
|
+
@value.andand.sub(/[\+\-]00:00$/, 'Z')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Value to be used in JSON.
|
|
38
|
+
# @return [String]
|
|
39
|
+
def json_value
|
|
40
|
+
xml_value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Value to be used in URLs.
|
|
44
|
+
# @return [String]
|
|
45
|
+
def url_value
|
|
46
|
+
xml_value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
protected
|
|
50
|
+
|
|
51
|
+
# Specifies date/time implementation to use
|
|
52
|
+
def date_class
|
|
53
|
+
::DateTime
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Specifies the date/time format string used for `strptime`
|
|
57
|
+
def strptime_format
|
|
58
|
+
#'%Y-%m-%dT%H:%M:%S.%L'
|
|
59
|
+
"%Y-%m-%dT%H:%M:%SZ"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def validate(value)
|
|
63
|
+
begin
|
|
64
|
+
return if value.nil? && allows_nil?
|
|
65
|
+
return if value.is_a?(date_class)
|
|
66
|
+
date_class.parse(value)
|
|
67
|
+
rescue
|
|
68
|
+
validation_error "Value '#{value}' is not a date time format that can be parsed"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse_value(value)
|
|
73
|
+
return value if value.nil? && allows_nil?
|
|
74
|
+
if value.is_a?(date_class)
|
|
75
|
+
parsed_value = value
|
|
76
|
+
else
|
|
77
|
+
parsed_value = date_class.parse(value)
|
|
78
|
+
end
|
|
79
|
+
parsed_value.strftime(strptime_format)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
# Defines the DateTimeOffset Frodo type.
|
|
4
|
+
class DateTimeOffset < Frodo::Properties::DateTime
|
|
5
|
+
# The Frodo type name
|
|
6
|
+
def type
|
|
7
|
+
'Edm.DateTimeOffset'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
protected
|
|
11
|
+
|
|
12
|
+
def strptime_format
|
|
13
|
+
'%Y-%m-%dT%H:%M:%S%:z'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
# Defines the Decimal Frodo type.
|
|
4
|
+
class Decimal < Frodo::Property
|
|
5
|
+
# Returns the property value, properly typecast
|
|
6
|
+
# @return [BigDecimal,nil]
|
|
7
|
+
def value
|
|
8
|
+
if (@value.nil? || @value.empty?) && (!strict? && allows_nil?)
|
|
9
|
+
nil
|
|
10
|
+
else
|
|
11
|
+
BigDecimal(@value)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Sets the property value
|
|
16
|
+
# @params new_value something BigDecimal() can parse
|
|
17
|
+
def value=(new_value)
|
|
18
|
+
@value = if (new_value.nil? && !strict? && allows_nil?)
|
|
19
|
+
nil
|
|
20
|
+
else
|
|
21
|
+
validate(BigDecimal(new_value.to_s))
|
|
22
|
+
new_value.to_s
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The Frodo type name
|
|
27
|
+
def type
|
|
28
|
+
'Edm.Decimal'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Value to be used in URLs.
|
|
32
|
+
# @return [String]
|
|
33
|
+
def url_value
|
|
34
|
+
"#{value.to_f}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def validate(value)
|
|
40
|
+
if value > max_value || value < min_value || value.precs.first > 29
|
|
41
|
+
validation_error "Value is outside accepted range: #{min_value} to #{max_value}, or has more than 29 significant digits"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def min_value
|
|
46
|
+
@min ||= BigDecimal(-7.9 * (10**28), 2)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def max_value
|
|
50
|
+
@max ||= BigDecimal(7.9 * (10**28), 2)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
# Abstract base class for Frodo EnumTypes
|
|
4
|
+
# @see [Frodo::Schema::EnumType]
|
|
5
|
+
class Enum < Frodo::Property
|
|
6
|
+
# Returns the property value, properly typecast
|
|
7
|
+
# @return [String, nil]
|
|
8
|
+
def value
|
|
9
|
+
if @value.nil? && allows_nil?
|
|
10
|
+
nil
|
|
11
|
+
else
|
|
12
|
+
@value
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Sets the property value
|
|
17
|
+
# @params new_value [String]
|
|
18
|
+
def value=(new_value)
|
|
19
|
+
parsed_value = validate(new_value)
|
|
20
|
+
@value = is_flags? ? parsed_value : parsed_value.first
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Value to be used in URLs.
|
|
24
|
+
# @return [String]
|
|
25
|
+
def url_value
|
|
26
|
+
"#{type}'#{@value}'"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def members
|
|
32
|
+
raise NotImplementedError, 'Subclass must override'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate(value)
|
|
36
|
+
return [] if value.nil? && allows_nil?
|
|
37
|
+
values = parse_value(value)
|
|
38
|
+
|
|
39
|
+
if values.length > 1 && !is_flags?
|
|
40
|
+
raise ArgumentError, 'Multiple values are not allowed for this property'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
values.map do |value|
|
|
44
|
+
if members.keys.include?(value)
|
|
45
|
+
members[value]
|
|
46
|
+
elsif members.values.include?(value)
|
|
47
|
+
value
|
|
48
|
+
else
|
|
49
|
+
validation_error "Value must be one of #{members.to_a}, but was: '#{value}'"
|
|
50
|
+
end
|
|
51
|
+
end.compact
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parse_value(value)
|
|
55
|
+
return nil if value.nil?
|
|
56
|
+
value.to_s.split(',').map(&:strip).map do |val|
|
|
57
|
+
val =~ /^[0-9]+$/ ? val.to_i : val
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
# Defines the Float Frodo type.
|
|
4
|
+
class Float < Frodo::Property
|
|
5
|
+
include Frodo::Properties::Number
|
|
6
|
+
|
|
7
|
+
# Returns the property value, properly typecast
|
|
8
|
+
# @return [Float,nil]
|
|
9
|
+
def value
|
|
10
|
+
if (@value.nil? || @value.empty?) && allows_nil?
|
|
11
|
+
nil
|
|
12
|
+
else
|
|
13
|
+
@value.to_f
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Sets the property value
|
|
18
|
+
# @params new_value [to_f]
|
|
19
|
+
def value=(new_value)
|
|
20
|
+
validate(new_value.to_f)
|
|
21
|
+
@value = new_value.to_f.to_s
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The Frodo type name
|
|
25
|
+
def type
|
|
26
|
+
'Edm.Double'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def min_value
|
|
32
|
+
@min ||= -(1.7 * (10**308))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def max_value
|
|
36
|
+
@max ||= (1.7 * (10**308))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Defines the Double (Float) Frodo type.
|
|
41
|
+
class Double < Frodo::Properties::Float; end
|
|
42
|
+
|
|
43
|
+
# Defines the Single (Float) Frodo type.
|
|
44
|
+
class Single < Frodo::Properties::Float
|
|
45
|
+
# The Frodo type name
|
|
46
|
+
def type
|
|
47
|
+
'Edm.Single'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Value to be used in URLs.
|
|
51
|
+
# @return [String]
|
|
52
|
+
def url_value
|
|
53
|
+
"#{value}F"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def min_value
|
|
59
|
+
@min ||= -(3.4 * (10**38))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def max_value
|
|
63
|
+
@max ||= (3.4 * (10**38))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
module Geography
|
|
4
|
+
class Base < Frodo::Property
|
|
5
|
+
# The SRID (Spatial Reference ID) of this property.
|
|
6
|
+
attr_accessor :srid
|
|
7
|
+
|
|
8
|
+
# The default SRID (same as used by GPS)
|
|
9
|
+
DEFAULT_SRID = 4326
|
|
10
|
+
|
|
11
|
+
# Initializes a geography property.
|
|
12
|
+
#
|
|
13
|
+
# Special options available for geographic types:
|
|
14
|
+
#
|
|
15
|
+
# +srid+: the SRID (spatial reference ID) of the
|
|
16
|
+
# coordinate system being used.
|
|
17
|
+
# Defaults to 4326 (same as GPS).
|
|
18
|
+
#
|
|
19
|
+
# @param name [to_s]
|
|
20
|
+
# @param value [Hash|Array|to_s|nil]
|
|
21
|
+
# @param options [Hash]
|
|
22
|
+
def initialize(name, value, options = {})
|
|
23
|
+
super(name, value, options)
|
|
24
|
+
self.value = value
|
|
25
|
+
self.srid = srid || options[:srid] || DEFAULT_SRID
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Sets the value of the property.
|
|
29
|
+
# @param value [Hash|Array|to_s|nil]
|
|
30
|
+
def value=(value)
|
|
31
|
+
if value.nil? && allows_nil?
|
|
32
|
+
@value = nil
|
|
33
|
+
elsif value.is_a?(Hash)
|
|
34
|
+
@value = value['coordinates']
|
|
35
|
+
elsif value.is_a?(Array)
|
|
36
|
+
@value = value
|
|
37
|
+
else
|
|
38
|
+
@value = parse_wkt(value.to_s)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Value to be used in URLs.
|
|
43
|
+
# @return [String]
|
|
44
|
+
def url_value
|
|
45
|
+
"geography'SRID=#{srid};#{type_name}(#{coords_to_s})'"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Value to be used in JSON.
|
|
49
|
+
# @return [Hash]
|
|
50
|
+
def json_value
|
|
51
|
+
{
|
|
52
|
+
type: type_name,
|
|
53
|
+
coordinates: value,
|
|
54
|
+
crs: crs
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The name of the SRS (Spatial Reference System) used.
|
|
59
|
+
# Basically, the SRID in URI/URL form.
|
|
60
|
+
# @return [String]
|
|
61
|
+
def srs_name
|
|
62
|
+
if srid == DEFAULT_SRID
|
|
63
|
+
"http://www.opengis.net/def/crs/EPSG/0/#{srid}"
|
|
64
|
+
else
|
|
65
|
+
raise NotImplementedError, "Unsupported SRID #{srid}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The name of the CRS (Coordinate Reference System) used.
|
|
70
|
+
# Used in GeoJSON representation
|
|
71
|
+
# @return [String]
|
|
72
|
+
def crs_name
|
|
73
|
+
if srid == DEFAULT_SRID
|
|
74
|
+
"EPSG:#{srid}"
|
|
75
|
+
else
|
|
76
|
+
raise NotImplementedError, "Unsupported SRID #{srid}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The full CRS representation as used by GeoJSON
|
|
81
|
+
# @return [Hash]
|
|
82
|
+
def crs
|
|
83
|
+
{
|
|
84
|
+
type: 'name',
|
|
85
|
+
properties: { name: crs_name }
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns the XML representation of the property to the supplied XML
|
|
90
|
+
# builder.
|
|
91
|
+
# @param xml_builder [Nokogiri::XML::Builder]
|
|
92
|
+
def to_xml(xml_builder)
|
|
93
|
+
attributes = { 'metadata:type' => type }
|
|
94
|
+
type_attrs = { 'gml:srsName' => srs_name }
|
|
95
|
+
|
|
96
|
+
xml_builder['data'].send(name.to_sym, attributes) do
|
|
97
|
+
xml_builder['gml'].send(type_name, type_attrs) do
|
|
98
|
+
value_to_xml(xml_value, xml_builder)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Creates a new property instance from an XML element
|
|
104
|
+
# @param property_xml [Nokogiri::XML::Element]
|
|
105
|
+
# @param options [Hash]
|
|
106
|
+
# @return [Frodo::Properties::Geography]
|
|
107
|
+
def self.from_xml(property_xml, options = {})
|
|
108
|
+
if property_xml.attributes['null'].andand.value == 'true'
|
|
109
|
+
content = nil
|
|
110
|
+
else
|
|
111
|
+
content = parse_xml(property_xml)
|
|
112
|
+
options.merge!(srid: srid_from_xml(property_xml))
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
new(property_xml.name, content, options)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
protected
|
|
119
|
+
|
|
120
|
+
def type_name
|
|
121
|
+
self.class.name.split('::').last
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def parse_wkt(value)
|
|
125
|
+
if value =~ /^geography'(SRID=(\d+);)+(\w+)\((.+)\)'$/
|
|
126
|
+
$3 == type_name or raise ArgumentError, "Invalid geography type '#{$3}'"
|
|
127
|
+
self.srid = $1 ? $2.to_i : DEFAULT_SRID
|
|
128
|
+
coords_from_s($4)
|
|
129
|
+
else
|
|
130
|
+
raise ArgumentError, "Invalid geography value '#{value}'"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Recursively turn a JSON-like data structure into XML
|
|
135
|
+
def value_to_xml(value, xml_builder)
|
|
136
|
+
if value.is_a?(Hash)
|
|
137
|
+
value.each do |key, val|
|
|
138
|
+
xml_builder['gml'].send(key) do
|
|
139
|
+
value_to_xml(val, xml_builder)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
elsif value.is_a?(Array)
|
|
143
|
+
value.each do |val|
|
|
144
|
+
value_to_xml(val, xml_builder)
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
xml_builder.text(value)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Extract the SRID from a GML element's `srsName` attribute
|
|
152
|
+
def self.srid_from_xml(property_xml)
|
|
153
|
+
gml_elem = property_xml.element_children.first
|
|
154
|
+
srs_attr = gml_elem.attributes['srsName']
|
|
155
|
+
if srs_attr
|
|
156
|
+
srs_attr.value.split(/[\/:]/).last.to_i
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
module Geography
|
|
4
|
+
class LineString < Base
|
|
5
|
+
def type
|
|
6
|
+
'Edm.GeographyLineString'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def coords_to_s
|
|
10
|
+
value.map { |pos| pos.join(' ') }.join(',')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def coords_from_s(str)
|
|
14
|
+
str.split(',').map { |pos| pos.split(' ').map(&:to_f) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def xml_value
|
|
18
|
+
value.map do |coords|
|
|
19
|
+
{ pos: coords.join(' ') }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def self.parse_xml(property_xml)
|
|
26
|
+
property_xml.xpath('//pos').map do |el|
|
|
27
|
+
el.content.split(' ').map(&:to_f)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Frodo
|
|
2
|
+
module Properties
|
|
3
|
+
module Geography
|
|
4
|
+
class Point < Base
|
|
5
|
+
def type
|
|
6
|
+
'Edm.GeographyPoint'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def coords_to_s
|
|
10
|
+
value.join(' ')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def coords_from_s(str)
|
|
14
|
+
str.split(' ').map(&:to_f)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def xml_value
|
|
18
|
+
{ pos: coords_to_s }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def self.parse_xml(property_xml)
|
|
24
|
+
property_xml.xpath('//pos').map do |el|
|
|
25
|
+
el.content.split(' ').map(&:to_f)
|
|
26
|
+
end.flatten
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|