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.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +2 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +2 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +75 -0
  8. data/CHANGELOG.md +150 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE.txt +23 -0
  11. data/README.md +427 -0
  12. data/Rakefile +7 -0
  13. data/TODO.md +55 -0
  14. data/frodata.gemspec +34 -0
  15. data/lib/frodata.rb +36 -0
  16. data/lib/frodata/entity.rb +332 -0
  17. data/lib/frodata/entity_container.rb +75 -0
  18. data/lib/frodata/entity_set.rb +161 -0
  19. data/lib/frodata/errors.rb +68 -0
  20. data/lib/frodata/navigation_property.rb +29 -0
  21. data/lib/frodata/navigation_property/proxy.rb +80 -0
  22. data/lib/frodata/properties.rb +32 -0
  23. data/lib/frodata/properties/binary.rb +50 -0
  24. data/lib/frodata/properties/boolean.rb +37 -0
  25. data/lib/frodata/properties/collection.rb +50 -0
  26. data/lib/frodata/properties/complex.rb +114 -0
  27. data/lib/frodata/properties/date.rb +27 -0
  28. data/lib/frodata/properties/date_time.rb +83 -0
  29. data/lib/frodata/properties/date_time_offset.rb +17 -0
  30. data/lib/frodata/properties/decimal.rb +50 -0
  31. data/lib/frodata/properties/enum.rb +62 -0
  32. data/lib/frodata/properties/float.rb +67 -0
  33. data/lib/frodata/properties/geography.rb +13 -0
  34. data/lib/frodata/properties/geography/base.rb +162 -0
  35. data/lib/frodata/properties/geography/line_string.rb +33 -0
  36. data/lib/frodata/properties/geography/point.rb +31 -0
  37. data/lib/frodata/properties/geography/polygon.rb +38 -0
  38. data/lib/frodata/properties/guid.rb +17 -0
  39. data/lib/frodata/properties/integer.rb +107 -0
  40. data/lib/frodata/properties/number.rb +14 -0
  41. data/lib/frodata/properties/string.rb +72 -0
  42. data/lib/frodata/properties/time.rb +40 -0
  43. data/lib/frodata/properties/time_of_day.rb +27 -0
  44. data/lib/frodata/property.rb +139 -0
  45. data/lib/frodata/property_registry.rb +41 -0
  46. data/lib/frodata/query.rb +233 -0
  47. data/lib/frodata/query/criteria.rb +92 -0
  48. data/lib/frodata/query/criteria/comparison_operators.rb +49 -0
  49. data/lib/frodata/query/criteria/date_functions.rb +61 -0
  50. data/lib/frodata/query/criteria/geography_functions.rb +21 -0
  51. data/lib/frodata/query/criteria/lambda_operators.rb +27 -0
  52. data/lib/frodata/query/criteria/string_functions.rb +40 -0
  53. data/lib/frodata/query/in_batches.rb +58 -0
  54. data/lib/frodata/railtie.rb +19 -0
  55. data/lib/frodata/schema.rb +155 -0
  56. data/lib/frodata/schema/complex_type.rb +79 -0
  57. data/lib/frodata/schema/enum_type.rb +95 -0
  58. data/lib/frodata/service.rb +254 -0
  59. data/lib/frodata/service/request.rb +85 -0
  60. data/lib/frodata/service/response.rb +162 -0
  61. data/lib/frodata/service/response/atom.rb +40 -0
  62. data/lib/frodata/service/response/json.rb +41 -0
  63. data/lib/frodata/service/response/plain.rb +36 -0
  64. data/lib/frodata/service/response/xml.rb +40 -0
  65. data/lib/frodata/service_registry.rb +52 -0
  66. data/lib/frodata/version.rb +3 -0
  67. data/spec/fixtures/files/entity_to_xml.xml +17 -0
  68. data/spec/fixtures/files/error.xml +5 -0
  69. data/spec/fixtures/files/metadata.xml +150 -0
  70. data/spec/fixtures/files/product_0.json +10 -0
  71. data/spec/fixtures/files/product_0.xml +28 -0
  72. data/spec/fixtures/files/products.json +106 -0
  73. data/spec/fixtures/files/products.xml +308 -0
  74. data/spec/fixtures/files/supplier_0.json +26 -0
  75. data/spec/fixtures/files/supplier_0.xml +32 -0
  76. data/spec/fixtures/vcr_cassettes/entity_set_specs.yml +1635 -0
  77. data/spec/fixtures/vcr_cassettes/entity_set_specs/bad_entry.yml +183 -0
  78. data/spec/fixtures/vcr_cassettes/entity_set_specs/existing_entry.yml +256 -0
  79. data/spec/fixtures/vcr_cassettes/entity_set_specs/new_entry.yml +185 -0
  80. data/spec/fixtures/vcr_cassettes/entity_specs.yml +285 -0
  81. data/spec/fixtures/vcr_cassettes/navigation_property_proxy_specs.yml +346 -0
  82. data/spec/fixtures/vcr_cassettes/query/result_specs.yml +189 -0
  83. data/spec/fixtures/vcr_cassettes/query_specs.yml +1060 -0
  84. data/spec/fixtures/vcr_cassettes/schema/complex_type_specs.yml +127 -0
  85. data/spec/fixtures/vcr_cassettes/service/request_specs.yml +193 -0
  86. data/spec/fixtures/vcr_cassettes/service_registry_specs.yml +129 -0
  87. data/spec/fixtures/vcr_cassettes/service_specs.yml +127 -0
  88. data/spec/fixtures/vcr_cassettes/usage_example_specs.yml +1330 -0
  89. data/spec/frodata/entity/shared_examples.rb +82 -0
  90. data/spec/frodata/entity_container_spec.rb +38 -0
  91. data/spec/frodata/entity_set_spec.rb +168 -0
  92. data/spec/frodata/entity_spec.rb +151 -0
  93. data/spec/frodata/errors_spec.rb +48 -0
  94. data/spec/frodata/navigation_property/proxy_spec.rb +44 -0
  95. data/spec/frodata/navigation_property_spec.rb +55 -0
  96. data/spec/frodata/properties/binary_spec.rb +50 -0
  97. data/spec/frodata/properties/boolean_spec.rb +72 -0
  98. data/spec/frodata/properties/collection_spec.rb +44 -0
  99. data/spec/frodata/properties/date_spec.rb +23 -0
  100. data/spec/frodata/properties/date_time_offset_spec.rb +30 -0
  101. data/spec/frodata/properties/date_time_spec.rb +23 -0
  102. data/spec/frodata/properties/decimal_spec.rb +51 -0
  103. data/spec/frodata/properties/float_spec.rb +45 -0
  104. data/spec/frodata/properties/geography/line_string_spec.rb +33 -0
  105. data/spec/frodata/properties/geography/point_spec.rb +29 -0
  106. data/spec/frodata/properties/geography/polygon_spec.rb +55 -0
  107. data/spec/frodata/properties/geography/shared_examples.rb +72 -0
  108. data/spec/frodata/properties/guid_spec.rb +17 -0
  109. data/spec/frodata/properties/integer_spec.rb +58 -0
  110. data/spec/frodata/properties/string_spec.rb +46 -0
  111. data/spec/frodata/properties/time_of_day_spec.rb +23 -0
  112. data/spec/frodata/properties/time_spec.rb +15 -0
  113. data/spec/frodata/property_registry_spec.rb +16 -0
  114. data/spec/frodata/property_spec.rb +71 -0
  115. data/spec/frodata/query/criteria_spec.rb +229 -0
  116. data/spec/frodata/query_spec.rb +199 -0
  117. data/spec/frodata/schema/complex_type_spec.rb +96 -0
  118. data/spec/frodata/schema/enum_type_spec.rb +112 -0
  119. data/spec/frodata/schema_spec.rb +97 -0
  120. data/spec/frodata/service/request_spec.rb +49 -0
  121. data/spec/frodata/service/response_spec.rb +85 -0
  122. data/spec/frodata/service_registry_spec.rb +18 -0
  123. data/spec/frodata/service_spec.rb +191 -0
  124. data/spec/frodata/usage_example_spec.rb +188 -0
  125. data/spec/spec_helper.rb +32 -0
  126. data/spec/support/coverage.rb +2 -0
  127. data/spec/support/vcr.rb +9 -0
  128. metadata +401 -0
@@ -0,0 +1,50 @@
1
+ module FrOData
2
+ module Properties
3
+ # Defines the Binary FrOData type.
4
+ class Binary < FrOData::Property
5
+ # Returns the property value, properly typecast
6
+ # @return [Integer,nil]
7
+ def value
8
+ if (@value.nil? || @value.empty?) && allows_nil?
9
+ nil
10
+ else
11
+ @value.to_i
12
+ end
13
+ end
14
+
15
+ # Sets the property value
16
+ # @params new_value [0,1,Boolean]
17
+ def value=(new_value)
18
+ validate(new_value)
19
+ @value = parse_value(new_value)
20
+ end
21
+
22
+ # The FrOData type name
23
+ def type
24
+ 'Edm.Binary'
25
+ end
26
+
27
+ # Value to be used in URLs.
28
+ # @return [String]
29
+ def url_value
30
+ "binary'#{value}'"
31
+ end
32
+
33
+ private
34
+
35
+ def parse_value(value)
36
+ if value == 0 || value == '0' || value == false
37
+ '0'
38
+ else
39
+ '1'
40
+ end
41
+ end
42
+
43
+ def validate(value)
44
+ unless [0,1,'0','1',true,false].include?(value)
45
+ validation_error 'Value is outside accepted range: 0 or 1'
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,37 @@
1
+ module FrOData
2
+ module Properties
3
+ # Defines the Boolean FrOData type.
4
+ class Boolean < FrOData::Property
5
+ # Returns the property value, properly typecast
6
+ # @return [Boolean, nil]
7
+ def value
8
+ if (@value.nil? || @value.empty?) && allows_nil?
9
+ nil
10
+ else
11
+ (@value == 'true' || @value == '1')
12
+ end
13
+ end
14
+
15
+ # Sets the property value
16
+ # @params new_value [Boolean]
17
+ def value=(new_value)
18
+ validate(new_value)
19
+ @value = new_value.to_s
20
+ end
21
+
22
+ # The FrOData type name
23
+ def type
24
+ 'Edm.Boolean'
25
+ end
26
+
27
+ private
28
+
29
+ def validate(value)
30
+ return if value.nil? && allows_nil?
31
+ unless [0,1,'0','1','true','false',true,false].include?(value)
32
+ validation_error 'Value is outside accepted range: true or false'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ module FrOData
2
+ module Properties
3
+ # Defines the Collection FrOData type.
4
+ class Collection < FrOData::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
+ FrOData::PropertyRegistry[value_type]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,114 @@
1
+ module FrOData
2
+ module Properties
3
+ # Abstract base class for FrOData ComplexTypes
4
+ # @see [FrOData::Schema::ComplexType]
5
+ class Complex < FrOData::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 [FrOData::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 'frodata/properties/date_time'
2
+
3
+ module FrOData
4
+ module Properties
5
+ # Defines the Date FrOData type.
6
+ class Date < FrOData::Properties::DateTime
7
+ # The FrOData 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 FrOData
2
+ module Properties
3
+ # Defines the DateTime FrOData type.
4
+ class DateTime < FrOData::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 FrOData 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 FrOData
2
+ module Properties
3
+ # Defines the DateTimeOffset FrOData type.
4
+ class DateTimeOffset < FrOData::Properties::DateTime
5
+ # The FrOData 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,50 @@
1
+ module FrOData
2
+ module Properties
3
+ # Defines the Decimal FrOData type.
4
+ class Decimal < FrOData::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
+ validate(BigDecimal(new_value.to_s))
19
+ @value = new_value.to_s
20
+ end
21
+
22
+ # The FrOData type name
23
+ def type
24
+ 'Edm.Decimal'
25
+ end
26
+
27
+ # Value to be used in URLs.
28
+ # @return [String]
29
+ def url_value
30
+ "#{value.to_f}"
31
+ end
32
+
33
+ private
34
+
35
+ def validate(value)
36
+ if value > max_value || value < min_value || value.precs.first > 29
37
+ validation_error "Value is outside accepted range: #{min_value} to #{max_value}, or has more than 29 significant digits"
38
+ end
39
+ end
40
+
41
+ def min_value
42
+ @min ||= BigDecimal(-7.9 * (10**28), 2)
43
+ end
44
+
45
+ def max_value
46
+ @max ||= BigDecimal(7.9 * (10**28), 2)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,62 @@
1
+ module FrOData
2
+ module Properties
3
+ # Abstract base class for FrOData EnumTypes
4
+ # @see [FrOData::Schema::EnumType]
5
+ class Enum < FrOData::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