frodata 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 95944116dffa1ce9127d4836d239f21e1344952a
4
+ data.tar.gz: 9078d6e3859ff61e1c7300806a9603c23783be33
5
+ SHA512:
6
+ metadata.gz: 7969fbe5c4b221bbb47cd270f9247754d286dd55c13cba397e80c6c4149674e2e2086dede8699f6ad1775d230d030c268ffa0ce684966c9755c2cdedbf28f303
7
+ data.tar.gz: cbdc81d7bb914049756edb595e8dc306a3e0551cd945bef1bffcd7f7bbfcff23c3952ae4c96034949b480c0e51bf9527f0d79baa866cc071571e4fdf9da0a081
data/.autotest ADDED
@@ -0,0 +1,2 @@
1
+ require 'autotest/bundler'
2
+
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .idea
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *.bundle
20
+ *.so
21
+ *.o
22
+ *.a
23
+ mkmf.log
24
+ private
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ odata
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2
data/.travis.yml ADDED
@@ -0,0 +1,75 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.1
7
+ - 2.1.2
8
+ - 2.1.3
9
+ - 2.1.4
10
+ - 2.1.5
11
+ - jruby
12
+ - jruby-head
13
+ jdk:
14
+ - openjdk6
15
+ - openjdk7
16
+ - oraclejdk7
17
+ - oraclejdk8
18
+ branches:
19
+ only:
20
+ - master
21
+ matrix:
22
+ exclude:
23
+ - rvm: 1.9.3
24
+ jdk: openjdk6
25
+ - rvm: 1.9.3
26
+ jdk: oraclejdk7
27
+ - rvm: 1.9.3
28
+ jdk: oraclejdk8
29
+ - rvm: 2.0.0
30
+ jdk: openjdk6
31
+ - rvm: 2.0.0
32
+ jdk: oraclejdk7
33
+ - rvm: 2.0.0
34
+ jdk: oraclejdk8
35
+ - rvm: 2.1.0
36
+ jdk: openjdk6
37
+ - rvm: 2.1.0
38
+ jdk: oraclejdk7
39
+ - rvm: 2.1.0
40
+ jdk: oraclejdk8
41
+ - rvm: 2.1.1
42
+ jdk: openjdk6
43
+ - rvm: 2.1.1
44
+ jdk: oraclejdk7
45
+ - rvm: 2.1.1
46
+ jdk: oraclejdk8
47
+ - rvm: 2.1.2
48
+ jdk: openjdk6
49
+ - rvm: 2.1.2
50
+ jdk: oraclejdk7
51
+ - rvm: 2.1.2
52
+ jdk: oraclejdk8
53
+ - rvm: 2.1.3
54
+ jdk: openjdk6
55
+ - rvm: 2.1.3
56
+ jdk: oraclejdk7
57
+ - rvm: 2.1.3
58
+ jdk: oraclejdk8
59
+ - rvm: 2.1.4
60
+ jdk: openjdk6
61
+ - rvm: 2.1.4
62
+ jdk: oraclejdk7
63
+ - rvm: 2.1.4
64
+ jdk: oraclejdk8
65
+ - rvm: 2.1.5
66
+ jdk: openjdk6
67
+ - rvm: 2.1.5
68
+ jdk: oraclejdk7
69
+ - rvm: 2.1.5
70
+ jdk: oraclejdk8
71
+ allow_failures:
72
+ - rvm: jruby-head
73
+ addons:
74
+ code_climate:
75
+ repo_token: 56e2763b5bfe138f566c5f2bf23c14deee4f8990324436dbde07ee2a34bb87f8
data/CHANGELOG.md ADDED
@@ -0,0 +1,150 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.9.1
4
+
5
+ * [New] Raising specific error classes instead of `RuntimeError` when request fails
6
+
7
+ ## 0.9.0
8
+
9
+ * [Breaking] Use Faraday instead of Typhoeus as HTTP connection adapter.
10
+ * [Breaking] Deprecate `Service.open` in favor of using constructor directly.
11
+
12
+ ## 0.8.2
13
+
14
+ * [Refactor] Moved `ComplexType` and `EnumType` class into `Schema`, respective property types into `Properties` namespace
15
+
16
+ ## 0.8.1
17
+
18
+ * [New Feature] Basic support for `Collection` property type
19
+ * [Refactor] Moved all HTTP-related code into `Service::Request`,
20
+ renamed `Query::Result` to `Service::Response`
21
+ * [Bugfix] Fixed incorrect `OData-Version` header being sent
22
+ * [Bugfix] Fixed duplicate namespace in Atom serialization
23
+
24
+ ## 0.8.0
25
+
26
+ * [New Feature] Support for multiple schemas
27
+ * [Breaking] `Service#complex_types`, `Service#entity_types`,
28
+ `Service#enum_types` and `Service#entity_sets` now return fully qualified
29
+ type names
30
+ * [New Feature] Optional lenient property validation
31
+ * [Fixed] Incorrect URL representation for Decimal properties
32
+
33
+ ## 0.7.0
34
+
35
+ Major rewrite
36
+
37
+ * Added support for OData 4.0
38
+ * Dropped support for OData 3.0
39
+
40
+ ## 0.6.18
41
+
42
+ * Minor internal fixes to OData::Query::Criteria.
43
+
44
+ ## 0.6.17
45
+
46
+ * Added more graceful handling of manually passed advanced queries to
47
+ OData::Query::Criteria.
48
+
49
+ ## 0.6.16
50
+
51
+ * Implemented OData::Query#empty? and fixed OData::Query#count.
52
+
53
+ ## 0.6.15
54
+
55
+ * Fixed minor bug in last release.
56
+
57
+ ## 0.6.14
58
+
59
+ * Changed implementation of OData::Association::Proxy#[] to properly handle
60
+ empty associations.
61
+
62
+ ## 0.6.13
63
+
64
+ * Minor bug fix in OData::Query::Result#each implementation.
65
+
66
+ ## 0.6.12
67
+
68
+ * Minor bug fix in OData::Query::Result#each implementation.
69
+
70
+ ## 0.6.11
71
+
72
+ * Added logic to allow OData::Query::Result#each to handle paginated results.
73
+
74
+ ## 0.6.10
75
+
76
+ * Changed how associations behave with mulitiplicity of one.
77
+
78
+ ## 0.6.9
79
+
80
+ * Changed how OData::Entity#from_xml functions to better work with feed results.
81
+
82
+ ## 0.6.8
83
+
84
+ * Added empty checking when checking for a nil value.
85
+
86
+ ## 0.6.7
87
+
88
+ * Changed how commit failures are handled to use logging instead of raising an
89
+ error.
90
+ * Added errors array to OData::Entity.
91
+
92
+ ## 0.6.6
93
+
94
+ * Updated OData::EntitySet#setup_entity_post_request to properly format primary
95
+ key values when posting an entity.
96
+
97
+ ## 0.6.5
98
+
99
+ * Fixed problem in OData::ComplexType#to_xml implementation.
100
+
101
+ ## 0.6.4
102
+
103
+ * Added implementation of OData::ComplexType#type.
104
+
105
+ ## 0.6.3
106
+
107
+ * Added OData::ComplexType#to_xml to make entity saving work correctly.
108
+
109
+ ## 0.6.1
110
+
111
+ * Made a minor change to internals of OData::Query::Criteria.
112
+
113
+ ## 0.6.0
114
+
115
+ * Added ability to handle associations in a reasonable way.
116
+
117
+ ## 0.5.1-8
118
+
119
+ * Tons of changes throughout the code base
120
+
121
+ ## 0.5.0
122
+
123
+ * Stopped using namespace from OData service as unique identifier in favor of
124
+ a supplied name option when opening a service.
125
+
126
+ ## 0.4.0
127
+
128
+ * Added OData::Query#execute to run query and return a result.
129
+ * Added OData::Query::Result to handle enumeration of query results.
130
+
131
+ ## 0.3.2
132
+
133
+ * Refactored internals of the query interface.
134
+
135
+ ## 0.3.1
136
+
137
+ * Resolved issues causing failure on Ruby 1.9 and JRuby.
138
+
139
+ ## 0.3.0
140
+
141
+ * Removed dependency on ActiveSupport
142
+
143
+ ## 0.2.0
144
+
145
+ * Added query interface for [System Query Options](http://www.odata.org/documentation/odata-version-3-0/odata-version-3-0-core-protocol#queryingcollections)
146
+ * Refactored internal uses of System Query Options
147
+
148
+ ## 0.1.0
149
+
150
+ * Core read/write behavior for OData v1-3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in odata.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2018 Christoph Wagner <christoph@wrstudios.com>
2
+ 2014 James Thompson <james@plainprograms.com>
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,427 @@
1
+ # FrOData
2
+
3
+ The FrOData gem provides a simple wrapper around the OData Version 4.0 API protocol.
4
+ It has the ability to automatically inspect compliant APIs and expose the relevant Ruby objects dynamically.
5
+ It also provides a set of code generation tools for quickly bootstrapping more custom service libraries.
6
+
7
+ **This gem supports [OData Version 4.0](http://www.odata.org/documentation/). Support for older versions is not a goal.**
8
+
9
+ If you need a gem to integration with OData Version 3, you can use James Thompson's [original OData gem][ruby-odata], upon which this gem is based.
10
+
11
+ [![Build Status](https://app.codeship.com/projects/da1eb540-ce3f-0135-2ddc-161d5c3cc5fd/status?branch=master)](https://app.codeship.com/projects/262148)
12
+ [![Maintainability](https://api.codeclimate.com/v1/badges/f151944dc05b2c7268e5/maintainability)](https://codeclimate.com/github/wrstudios/frodata/maintainability)
13
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/f151944dc05b2c7268e5/test_coverage)](https://codeclimate.com/github/wrstudios/frodata/test_coverage)
14
+ [![Dependency Status](https://gemnasium.com/badges/github.com/wrstudios/frodata.svg)](https://gemnasium.com/github.com/wrstudios/frodata)
15
+ [![Documentation](http://inch-ci.org/github/wrstudios/frodata.png?branch=master)](http://www.rubydoc.info/github/wrstudios/frodata/master)
16
+ [![Gem Version](https://badge.fury.io/rb/frodata.svg)](https://badge.fury.io/rb/frodata)
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's `Gemfile`:
21
+
22
+ gem 'frodata'
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install frodata
31
+
32
+ ## Usage
33
+
34
+ ### Services & the Service Registry
35
+
36
+ The FrOData gem provides a number of core classes, the two most basic ones are the `FrOData::Service` and the `FrOData::ServiceRegistry`.
37
+ The only time you will need to worry about the `FrOData::ServiceRegistry` is when you have multiple FrOData
38
+ services you are interacting with that you want to keep straight easily.
39
+ The nice thing about `FrOData::Service` is that it automatically registers with the registry on creation, so there is no manual interaction with the registry necessary.
40
+
41
+ To create an `FrOData::Service` simply provide the location of a service endpoint to it like this:
42
+
43
+ ```ruby
44
+ FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc')
45
+ ```
46
+
47
+ You may also provide an options hash after the URL.
48
+ It is suggested that you supply a name for the service via this hash like so:
49
+
50
+ ```ruby
51
+ FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo')
52
+ ```
53
+
54
+ For more information regarding available options and how to configure a service instance, refer to [Service Configuration](#service-configuration) below.
55
+
56
+ This one call will setup the service and allow for the discovery of everything the other parts of the FrOData gem need to function.
57
+ The two methods you will want to remember from `FrOData::Service` are `#service_url` and `#name`.
58
+ Both of these methods are available on instances and will allow for lookup in the `FrOData::ServiceRegistry`, should you need it.
59
+
60
+ Using either the service URL or the name provided as an option when creating an `FrOData::Service` will allow for quick lookup in the `FrOData::ServiceRegistry` like such:
61
+
62
+ ```ruby
63
+ FrOData::ServiceRegistry['http://services.odata.org/V4/OData/OData.svc']
64
+ FrOData::ServiceRegistry['ODataDemo']
65
+ ```
66
+
67
+ Both of the above calls would retrieve the same service from the registry.
68
+ At the moment there is no protection against name collisions provided in `FrOData::ServiceRegistry`.
69
+ So, looking up services by their service URL is the most exact method, but lookup by name is provided for convenience.
70
+
71
+ ### Service Configuration
72
+
73
+ #### Metadata File
74
+
75
+ Typically the metadata file of a service can be quite large.
76
+ You can speed your load time by forcing the service to load the metadata from a file rather than a URL.
77
+ This is only recommended for testing purposes, as the metadata file can change.
78
+
79
+ ```ruby
80
+ service = FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', {
81
+ name: 'ODataDemo',
82
+ metadata_file: "metadata.xml",
83
+ })
84
+ ```
85
+
86
+ #### Headers & Authorization
87
+
88
+ The OData protocol does not deal with authentication and authorization at all, nor does it need to, since [HTTP already provides many different options][http-auth] for this, such as HTTP Basic or token authorization.
89
+ Hence, this gem does not implement any special authentication mechanisms either, and relies on the underlying HTTP library ([Faraday][faraday]) to take care of this.
90
+
91
+ ##### Setting Custom Headers
92
+
93
+ You can customize request headers with the **:connection** option key.
94
+ This allows you to e.g. set custom headers (such as `Authorization`) that may be required by your service.
95
+
96
+ ```ruby
97
+ service = FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', {
98
+ name: 'ODataDemo',
99
+ connection: {
100
+ headers: {
101
+ "Authorization" => "Bearer #{access_token}"
102
+ }
103
+ }
104
+ })
105
+ ```
106
+
107
+ ##### Using Authentication Helpers
108
+
109
+ You may also set up authorization by directly accessing the underlying `Faraday::Connection` object (as explained in [Advanced Customization](#advanced-connection-customization) below).
110
+ This allows you to make use of Faraday's [authentication helpers][faraday-auth], such as `basic_auth` or `token_auth`.
111
+
112
+ For instance, if your service requires HTTP basic authentication:
113
+
114
+ ```ruby
115
+ service = FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', {
116
+ name: 'ODataDemo'
117
+ })
118
+ service.connection.basic_auth('username', 'password')
119
+ ```
120
+
121
+ You may also use these helpers when passing a block to the constructor (see second example [below](#passing-a-block-to-the-constructor)).
122
+
123
+ [http-auth]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
124
+ [faraday]: https://github.com/lostisland/faraday
125
+ [faraday-auth]: https://github.com/lostisland/faraday#authentication
126
+
127
+ #### Advanced Connection Customization
128
+
129
+ Under the hood, the gem uses the [Faraday][faraday] HTTP library to provide flexible
130
+ integration of various Ruby HTTP backends.
131
+
132
+ There are several ways to access the underlying `Faraday::Connection`:
133
+
134
+ ##### As a service option
135
+
136
+ If you already have a `Faraday::Connection` instance that you want the service to use, you can simply pass it to the constructor *instead* of the service URL as first parameter.
137
+ In this case, you'll be setting the service URL on the connection object, as shown below:
138
+
139
+ ```ruby
140
+ conn = Faraday.new('http://services.odata.org/V4/OData/OData.svc') do |conn|
141
+ # ... customize connection ...
142
+ end
143
+
144
+ service = FrOData::Service.new(conn, name: 'ODataDemo')
145
+ ```
146
+
147
+ ##### Passing a block to the constructor
148
+
149
+ Alternatively, the connection object is also `yield`ed by the constructor, so you may customize it by passing a block argument.
150
+ For instance, if you wanted to use [Typhoeus][typhoeus] as your HTTP library:
151
+
152
+ ```ruby
153
+ service = FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', {
154
+ name: 'ODataDemo'
155
+ }) do |conn|
156
+ conn.adapter :typhoeus
157
+ end
158
+ ```
159
+
160
+ **IMPORTANT**
161
+
162
+ Please be aware that if you use this method to customize the connection, you must ALWAYS specify an adapter:
163
+
164
+ ```ruby
165
+ service = FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', {
166
+ name: 'ODataDemo'
167
+ }) do |conn|
168
+ conn.basic_auth('username', 'password')
169
+ conn.adapter Faraday.default_adapter
170
+ end
171
+ ```
172
+
173
+ Otherwise, your requests WILL fail!
174
+
175
+ [typhoeus]: https://github.com/typhoeus/typhoeus
176
+
177
+ ### Exploring a Service
178
+
179
+ Once instantiated, you can request various information about the service, such as the names and types of entity sets it exposes, or the names of the entity types (and custom datatypes) it defines.
180
+
181
+ For example:
182
+
183
+ Get a list of available entity types
184
+
185
+ ```ruby
186
+ service.entity_types
187
+ # => [
188
+ # "ODataDemo.Product",
189
+ # "ODataDemo.FeaturedProduct",
190
+ # "ODataDemo.ProductDetail",
191
+ # "ODataDemo.Category",
192
+ # "ODataDemo.Supplier",
193
+ # "ODataDemo.Person",
194
+ # "ODataDemo.Customer",
195
+ # "ODataDemo.Employee",
196
+ # "ODataDemo.PersonDetail",
197
+ # "ODataDemo.Advertisement"
198
+ # ]
199
+ ```
200
+
201
+ Get a list of entity sets
202
+
203
+ ```ruby
204
+ service.entity_sets
205
+ # => {
206
+ # "Products" => "ODataDemo.Product",
207
+ # "ProductDetails" => "ODataDemo.ProductDetail",
208
+ # "Categories" => "ODataDemo.Category",
209
+ # "Suppliers" => "ODataDemo.Supplier",
210
+ # "Persons" => "ODataDemo.Person",
211
+ # "PersonDetails" => "ODataDemo.PersonDetail",
212
+ # "Advertisements" => "ODataDemo.Advertisement"
213
+ # }
214
+ ```
215
+
216
+ Get a list of complex types
217
+
218
+ ```ruby
219
+ service.complex_types
220
+ # => ["ODataDemo.Address"]
221
+ ```
222
+
223
+ Get a list of enum types
224
+
225
+ ```ruby
226
+ service.enum_types
227
+ # => ["ODataDemo.ProductStatus"]
228
+ ```
229
+
230
+ For more examples, refer to [usage_example_specs.rb](spec/frodata/usage_example_specs.rb).
231
+
232
+
233
+ ### Entity Sets
234
+
235
+ When it comes to reading data from an OData service the most typical way will be via `FrOData::EntitySet` instances.
236
+ Under normal circumstances you should never need to worry about an `FrOData::EntitySet` directly.
237
+ For example, to get an `FrOData::EntitySet` for the products in the ODataDemo service simply access the entity set through the service like this:
238
+
239
+ ```ruby
240
+ service = FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc')
241
+ products = service['ProductsSet'] # => FrOData::EntitySet
242
+ ```
243
+
244
+ `FrOData::EntitySet` instances implement the `Enumerable` module, meaning you can work with them very naturally, like this:
245
+
246
+ ```ruby
247
+ products.each do |entity|
248
+ entity # => FrOData::Entity for type Product
249
+ end
250
+ ```
251
+
252
+ You can get a list of all your entity sets like this:
253
+
254
+ ```ruby
255
+ service.entity_sets
256
+ ```
257
+
258
+ #### Count
259
+ Some versions of Microsoft CRM do not support count.
260
+
261
+ ```ruby
262
+ products.count
263
+ ```
264
+
265
+ #### Collections
266
+ You can you the following methods to grab a collection of Entities:
267
+
268
+ ```ruby
269
+ products.each do |entity|
270
+ ...
271
+ end
272
+ ```
273
+
274
+ The first entity object returns a single entity object.
275
+
276
+ ```ruby
277
+ products.first
278
+ ```
279
+
280
+ `first(x)` returns an array of entity objects.
281
+
282
+ ```ruby
283
+ products.first(x)
284
+ ```
285
+
286
+ #### Find a certain Entity
287
+
288
+ ```ruby
289
+ service['ProductsSet']['<primary key of entity>']
290
+ ```
291
+
292
+ With certain navigation properties expanded (i.e. eagerly loaded):
293
+
294
+ ```ruby
295
+ # Eagerly load a single navigation property
296
+ service['ProductsSet', expand: 'Categories']
297
+
298
+ # Eagerly load multiple navigation properties
299
+ service['ProductsSet', expand: ['Categories', 'Supplier']]
300
+
301
+ # Eagerly load ALL navigation properties
302
+ service['ProductsSet', expand: :all]
303
+ ```
304
+
305
+ ### Entities
306
+
307
+ `FrOData::Entity` instances represent individual entities, or records, in a given service.
308
+ They are returned primarily through interaction with instances of `FrOData::EntitySet`.
309
+ You can access individual properties on an `FrOData::Entity` like so:
310
+
311
+ ```ruby
312
+ product = products.first # => FrOData::Entity
313
+ product['Name'] # => 'Bread'
314
+ product['Price'] # => 2.5 (Float)
315
+ ```
316
+
317
+ Individual properties on an `FrOData::Entity` are automatically typecast by the gem, so you don't have to worry about too much when working with entities.
318
+ The way this is implemented internally guarantees that an `FrOData::Entity` is always ready to save back to the service or `FrOData::EntitySet`, which you do like so:
319
+
320
+ ```ruby
321
+ service['Products'] << product # Write back to the service
322
+ products << product # Write back to the Entity Set
323
+ ```
324
+
325
+ You can get a list of all your entities like this:
326
+
327
+ ```ruby
328
+ service.entity_types
329
+ ```
330
+
331
+ #### Entity Properties
332
+ Reading, parsing and instantiating all properties of an entity can add up to a significant amount of time, particularly for those entities with a large number of properties.
333
+ To speed this process up all properties are lazy loaded.
334
+ Which means it will store the name of the property, but will not parse and instantiate the property until you want to use it.
335
+
336
+ You can find all the property names of your entity with
337
+
338
+ ```ruby
339
+ product.property_names
340
+ ```
341
+
342
+ You can grab the parsed value of the property as follows:
343
+
344
+ ```ruby
345
+ product["Name"]
346
+ ```
347
+
348
+ or, you can get a hold of the property class instance using
349
+
350
+ ```ruby
351
+ product.get_property("Name")
352
+ ```
353
+
354
+ This will parse and instantiate the property if it hasn't done so yet.
355
+
356
+ ##### Lenient Property Validation
357
+
358
+ By default, we use strict property validation, meaning that any property validation errors in the data will raise an exception.
359
+ However, you may encounter OData implementations in the wild that break the specs in strange and surprising ways (shocking, I know!).
360
+
361
+ Since it's often better to get *some* data instead of nothing at all, you can optionally make the property validation lenient.
362
+ Simply add `strict: false` to the service constructor options.
363
+ In this mode, any property validation error will log a warning instead of raising an exception. The corresponding property value will be `nil` (even if the property is declared as not allowing NULL values).
364
+
365
+ ```ruby
366
+ service = FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', strict: false)
367
+ # -- alternatively, for an existing service instance --
368
+ service.options[:strict] = false
369
+ ```
370
+
371
+ ### Queries
372
+
373
+ `FrOData::Query` instances form the base for finding specific entities within an `FrOData::EntitySet`.
374
+ A query object exposes a number of capabilities based on
375
+ the [System Query Options](http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_Toc453752288) provided for in the OData V4.0 specification.
376
+ Below is just a partial example of what is possible:
377
+
378
+ ```ruby
379
+ query = service['Products'].query
380
+ query.where(query[:Price].lt(15))
381
+ query.where(query[:Rating].gt(3))
382
+ query.limit(3)
383
+ query.skip(2)
384
+ query.order_by("Name")
385
+ query.select("Name,CreatedBy")
386
+ query.inline_count
387
+ results = query.execute
388
+ results.each {|product| puts product['Name']}
389
+ ```
390
+
391
+ The process of querying is kept purposely verbose to allow for lazy behavior to be implemented at higher layers.
392
+ Internally, `FrOData::Query` relies on the `FrOData::Query::Criteria` for the way the `where` method works.
393
+ You should refer to the published RubyDocs for full details on the various capabilities:
394
+
395
+ * [FrOData::Query](http://rubydoc.info/github/wrstudios/frodata/master/FrOData/Query)
396
+ * [FrOData::Query::Criteria](http://rubydoc.info/github/wrstudios/frodata/master/FrOData/Query/Criteria)
397
+
398
+ ## To Do
399
+
400
+ [x] ~Lenient property validation~
401
+ [ ] Write support (create/update/delete)
402
+ [ ] Support for invoking [Operations][odata-ops] (Functions/Actions)
403
+ [ ] [Property facets][odata-facets]
404
+ [ ] Annotations
405
+
406
+ [odata-facets]: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_Toc453752528
407
+ [odata-ops]: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_Toc453752307
408
+
409
+ ## Contributing
410
+
411
+ 1. Fork it (`https://github.com/[my-github-username]/odata/fork`)
412
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
413
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
414
+ 4. Push to the branch (`git push origin my-new-feature`)
415
+ 5. Create a new Pull Request
416
+
417
+ ## Credits
418
+
419
+ Many thanks go to [James Thompson][@plainprogrammer], who wrote the [original OData (Version 3.0) gem][ruby-odata].
420
+
421
+ [@plainprogrammer]: https://github.com/plainprogrammer
422
+ [ruby-odata]: https://github.com/ruby-odata/odata
423
+
424
+ Also, I would like to thank [W+R Studios][wrstudios] for generously allowing me to work on Open Source software like this. If you want to work on interesting challenges with an awesome team, check out our [open positions][wrcareers].
425
+
426
+ [wrstudios]: http://wrstudios.com/
427
+ [wrcareers]: http://wrstudios.com/careers