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.
Files changed (153) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +2 -0
  3. data/.circleci/config.yml +54 -0
  4. data/.gitignore +24 -0
  5. data/.gitlab-ci.yml +9 -0
  6. data/.rspec +2 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/.travis.yml +75 -0
  10. data/CHANGELOG.md +163 -0
  11. data/Gemfile +4 -0
  12. data/LICENSE.txt +23 -0
  13. data/README.md +479 -0
  14. data/Rakefile +7 -0
  15. data/TODO.md +55 -0
  16. data/frodo.gemspec +39 -0
  17. data/images/frodo.jpg +0 -0
  18. data/lib/frodo/abstract_client.rb +11 -0
  19. data/lib/frodo/client.rb +6 -0
  20. data/lib/frodo/concerns/api.rb +292 -0
  21. data/lib/frodo/concerns/authentication.rb +32 -0
  22. data/lib/frodo/concerns/base.rb +84 -0
  23. data/lib/frodo/concerns/caching.rb +26 -0
  24. data/lib/frodo/concerns/connection.rb +79 -0
  25. data/lib/frodo/concerns/verbs.rb +68 -0
  26. data/lib/frodo/config.rb +143 -0
  27. data/lib/frodo/entity.rb +335 -0
  28. data/lib/frodo/entity_container.rb +75 -0
  29. data/lib/frodo/entity_set.rb +131 -0
  30. data/lib/frodo/errors.rb +70 -0
  31. data/lib/frodo/middleware/authentication/token.rb +13 -0
  32. data/lib/frodo/middleware/authentication.rb +87 -0
  33. data/lib/frodo/middleware/authorization.rb +18 -0
  34. data/lib/frodo/middleware/caching.rb +30 -0
  35. data/lib/frodo/middleware/custom_headers.rb +14 -0
  36. data/lib/frodo/middleware/gzip.rb +33 -0
  37. data/lib/frodo/middleware/instance_url.rb +20 -0
  38. data/lib/frodo/middleware/logger.rb +42 -0
  39. data/lib/frodo/middleware/multipart.rb +64 -0
  40. data/lib/frodo/middleware/odata_headers.rb +13 -0
  41. data/lib/frodo/middleware/raise_error.rb +47 -0
  42. data/lib/frodo/middleware.rb +33 -0
  43. data/lib/frodo/navigation_property/proxy.rb +80 -0
  44. data/lib/frodo/navigation_property.rb +29 -0
  45. data/lib/frodo/properties/binary.rb +50 -0
  46. data/lib/frodo/properties/boolean.rb +37 -0
  47. data/lib/frodo/properties/collection.rb +50 -0
  48. data/lib/frodo/properties/complex.rb +114 -0
  49. data/lib/frodo/properties/date.rb +27 -0
  50. data/lib/frodo/properties/date_time.rb +83 -0
  51. data/lib/frodo/properties/date_time_offset.rb +17 -0
  52. data/lib/frodo/properties/decimal.rb +54 -0
  53. data/lib/frodo/properties/enum.rb +62 -0
  54. data/lib/frodo/properties/float.rb +67 -0
  55. data/lib/frodo/properties/geography/base.rb +162 -0
  56. data/lib/frodo/properties/geography/line_string.rb +33 -0
  57. data/lib/frodo/properties/geography/point.rb +31 -0
  58. data/lib/frodo/properties/geography/polygon.rb +38 -0
  59. data/lib/frodo/properties/geography.rb +13 -0
  60. data/lib/frodo/properties/guid.rb +17 -0
  61. data/lib/frodo/properties/integer.rb +107 -0
  62. data/lib/frodo/properties/number.rb +14 -0
  63. data/lib/frodo/properties/string.rb +72 -0
  64. data/lib/frodo/properties/time.rb +40 -0
  65. data/lib/frodo/properties/time_of_day.rb +27 -0
  66. data/lib/frodo/properties.rb +32 -0
  67. data/lib/frodo/property.rb +139 -0
  68. data/lib/frodo/property_registry.rb +41 -0
  69. data/lib/frodo/query/criteria/comparison_operators.rb +49 -0
  70. data/lib/frodo/query/criteria/date_functions.rb +61 -0
  71. data/lib/frodo/query/criteria/geography_functions.rb +21 -0
  72. data/lib/frodo/query/criteria/lambda_operators.rb +27 -0
  73. data/lib/frodo/query/criteria/string_functions.rb +40 -0
  74. data/lib/frodo/query/criteria.rb +92 -0
  75. data/lib/frodo/query/in_batches.rb +58 -0
  76. data/lib/frodo/query.rb +221 -0
  77. data/lib/frodo/railtie.rb +19 -0
  78. data/lib/frodo/schema/complex_type.rb +79 -0
  79. data/lib/frodo/schema/enum_type.rb +95 -0
  80. data/lib/frodo/schema.rb +164 -0
  81. data/lib/frodo/service.rb +199 -0
  82. data/lib/frodo/service_registry.rb +52 -0
  83. data/lib/frodo/version.rb +3 -0
  84. data/lib/frodo.rb +67 -0
  85. data/spec/fixtures/auth_success_response.json +11 -0
  86. data/spec/fixtures/error.json +11 -0
  87. data/spec/fixtures/files/entity_to_xml.xml +18 -0
  88. data/spec/fixtures/files/error.xml +5 -0
  89. data/spec/fixtures/files/metadata.xml +150 -0
  90. data/spec/fixtures/files/metadata_with_error.xml +157 -0
  91. data/spec/fixtures/files/product_0.json +10 -0
  92. data/spec/fixtures/files/product_0.xml +28 -0
  93. data/spec/fixtures/files/products.json +106 -0
  94. data/spec/fixtures/files/products.xml +308 -0
  95. data/spec/fixtures/files/supplier_0.json +26 -0
  96. data/spec/fixtures/files/supplier_0.xml +32 -0
  97. data/spec/fixtures/leads.json +923 -0
  98. data/spec/fixtures/refresh_error_response.json +8 -0
  99. data/spec/frodo/abstract_client_spec.rb +13 -0
  100. data/spec/frodo/client_spec.rb +57 -0
  101. data/spec/frodo/concerns/authentication_spec.rb +79 -0
  102. data/spec/frodo/concerns/base_spec.rb +68 -0
  103. data/spec/frodo/concerns/caching_spec.rb +40 -0
  104. data/spec/frodo/concerns/connection_spec.rb +65 -0
  105. data/spec/frodo/config_spec.rb +127 -0
  106. data/spec/frodo/entity/shared_examples.rb +83 -0
  107. data/spec/frodo/entity_container_spec.rb +38 -0
  108. data/spec/frodo/entity_set_spec.rb +169 -0
  109. data/spec/frodo/entity_spec.rb +153 -0
  110. data/spec/frodo/errors_spec.rb +48 -0
  111. data/spec/frodo/middleware/authentication/token_spec.rb +87 -0
  112. data/spec/frodo/middleware/authentication_spec.rb +83 -0
  113. data/spec/frodo/middleware/authorization_spec.rb +17 -0
  114. data/spec/frodo/middleware/custom_headers_spec.rb +21 -0
  115. data/spec/frodo/middleware/gzip_spec.rb +68 -0
  116. data/spec/frodo/middleware/instance_url_spec.rb +27 -0
  117. data/spec/frodo/middleware/logger_spec.rb +21 -0
  118. data/spec/frodo/middleware/odata_headers_spec.rb +15 -0
  119. data/spec/frodo/middleware/raise_error_spec.rb +66 -0
  120. data/spec/frodo/navigation_property/proxy_spec.rb +46 -0
  121. data/spec/frodo/navigation_property_spec.rb +55 -0
  122. data/spec/frodo/properties/binary_spec.rb +50 -0
  123. data/spec/frodo/properties/boolean_spec.rb +72 -0
  124. data/spec/frodo/properties/collection_spec.rb +44 -0
  125. data/spec/frodo/properties/date_spec.rb +23 -0
  126. data/spec/frodo/properties/date_time_offset_spec.rb +30 -0
  127. data/spec/frodo/properties/date_time_spec.rb +23 -0
  128. data/spec/frodo/properties/decimal_spec.rb +50 -0
  129. data/spec/frodo/properties/float_spec.rb +45 -0
  130. data/spec/frodo/properties/geography/line_string_spec.rb +33 -0
  131. data/spec/frodo/properties/geography/point_spec.rb +29 -0
  132. data/spec/frodo/properties/geography/polygon_spec.rb +55 -0
  133. data/spec/frodo/properties/geography/shared_examples.rb +72 -0
  134. data/spec/frodo/properties/guid_spec.rb +17 -0
  135. data/spec/frodo/properties/integer_spec.rb +58 -0
  136. data/spec/frodo/properties/string_spec.rb +46 -0
  137. data/spec/frodo/properties/time_of_day_spec.rb +23 -0
  138. data/spec/frodo/properties/time_spec.rb +15 -0
  139. data/spec/frodo/property_registry_spec.rb +16 -0
  140. data/spec/frodo/property_spec.rb +71 -0
  141. data/spec/frodo/query/criteria_spec.rb +229 -0
  142. data/spec/frodo/query_spec.rb +156 -0
  143. data/spec/frodo/schema/complex_type_spec.rb +97 -0
  144. data/spec/frodo/schema/enum_type_spec.rb +112 -0
  145. data/spec/frodo/schema_spec.rb +113 -0
  146. data/spec/frodo/service_registry_spec.rb +19 -0
  147. data/spec/frodo/service_spec.rb +153 -0
  148. data/spec/frodo/usage_example_spec.rb +161 -0
  149. data/spec/spec_helper.rb +35 -0
  150. data/spec/support/coverage.rb +2 -0
  151. data/spec/support/fixture_helpers.rb +14 -0
  152. data/spec/support/middleware.rb +19 -0
  153. metadata +479 -0
data/README.md ADDED
@@ -0,0 +1,479 @@
1
+ # Frodo - Free OData V4.0 library for Ruby
2
+
3
+ [![CircleCI](https://circleci.com/gh/getoutreach/frodo.svg?style=svg)](https://circleci.com/gh/getoutreach/frodo)
4
+
5
+ ### One API to rule them all
6
+
7
+ <img src="images/frodo.jpg" alt="Frodo" width="192" height="192" align="right">
8
+
9
+ Frodo is the little gem that access your precious OData Version 4.0 from the land of Microsoft Dynamics or other OData compliant API.
10
+
11
+ It has the ability to automatically inspect compliant APIs and expose the relevant Ruby objects dynamically.
12
+
13
+ Features include:
14
+
15
+ A clean and modular architecture using Faraday middleware responses.
16
+
17
+ - Support for interacting with multiple users from different orgs.
18
+ - Support for schema discovery.
19
+ - Support for queryable interface.
20
+ - Support for GZIP compression.
21
+ - Support for Oauth authentication.
22
+
23
+ **This gem supports [OData Version 4.0](http://www.odata.org/documentation/). Support for older versions is not a goal.**
24
+
25
+ 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. It is also is based on a Fork from (https://github.com/wrstudios/frodo) who was an attempt to OData Version 4 but seemed unfinished. Finally
26
+ it uses code taken from Restforce for the client [Restforce gem](https://github.com/restforce/restforce)
27
+
28
+ [![Gem Version](https://badge.fury.io/rb/frodo.svg)](https://badge.fury.io/rb/frodo)
29
+ [![Build Status](https://app.codeship.com/projects/da1eb540-ce3f-0135-2ddc-161d5c3cc5fd/status?branch=master)](https://app.codeship.com/projects/262148)
30
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/2425311d859408ef8798/test_coverage)](https://codeclimate.com/github/wrstudios/frodo/test_coverage)
31
+
32
+ ## Installation
33
+
34
+ Add this line to your application's `Gemfile`:
35
+
36
+ gem 'frodo'
37
+
38
+ And then execute:
39
+
40
+ $ bundle
41
+
42
+ Or install it yourself as:
43
+
44
+ $ gem install frodo
45
+
46
+ This gem is versioned using [Semantic Versioning](http://semver.org/), so you can be confident when updating that there will not be breaking changes outside of a major version (following format MAJOR.MINOR.PATCH, so for instance moving from 3.1.0 to 4.0.0 would be allowed to include incompatible API changes). See the [changelog](https://github.com/restforce/restforce/tree/master/CHANGELOG.md) for details on what has changed in each version.
47
+
48
+ ## Usage
49
+
50
+ Which authentication method you use really depends on your use case. If you're
51
+ building an application where many users from different orgs are authenticated
52
+ through oauth and you need to interact with data in their org on their behalf,
53
+ you should use the OAuth token authentication method.
54
+
55
+ This is currently the only supported method. This may change overtime
56
+
57
+ It is also important to note that the client object should not be reused across different threads, otherwise you may encounter [thread-safety issues](https://www.youtube.com/watch?v=p5zQOkyCACc).
58
+
59
+ #### OAuth token authentication
60
+
61
+ ```ruby
62
+ client = Frodo.new(oauth_token: 'access_token',
63
+ instance_url: 'instance url',
64
+ base_path: '/path/to/service')
65
+ ```
66
+
67
+ Although the above will work, you'll probably want to take advantage of the (re)authentication middleware by specifying `refresh_token`, `client_id`, `client_secret`, and `authentication_callback`:
68
+
69
+ ```ruby
70
+ client = Frodo.new(oauth_token: 'access_token',
71
+ refresh_token: 'refresh token',
72
+ instance_url: 'instance url',
73
+ client_id: 'client_id',
74
+ client_secret: 'client_secret',
75
+ authentication_callback: Proc.new { |x| Rails.logger.debug x.to_s },
76
+ base_path: '/path/to/service')
77
+ ```
78
+
79
+ The middleware will use the `refresh_token` automatically to acquire a new `access_token` if the existing `access_token` is invalid.
80
+
81
+ `authentication_callback` is a proc that handles the response from Salesforce when the `refresh_token` is used to obtain a new `access_token`. This allows the `access_token` to be saved for re-use later - otherwise subsequent API calls will continue the cycle of "auth failure/issue new access_token/auth success".
82
+
83
+ The proc is passed one argument, a `Hash` of the response, similar than the one for [Dynamics API](https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#refreshing-the-access-tokens):
84
+
85
+ ```ruby
86
+ {
87
+ "token_type"=>"Bearer",
88
+ "scope"=>"user_impersonation",
89
+ "expires_in"=>"3600",
90
+ "ext_expires_in"=>"3600",
91
+ "expires_on"=>"1552087545",
92
+ "not_before"=>"1552083645",
93
+ "resource"=>"https://myinstance.crm.dynamics.com",
94
+ "access_token"=>"token",
95
+ "refresh_token"=>"refresh token"
96
+ }
97
+ ```
98
+
99
+ The `id` field can be used to [uniquely identify](https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#refreshing-the-access-tokens) the user that the `access_token` and `refresh_token` belong to.
100
+
101
+ ### Proxy Support
102
+
103
+ You can specify a HTTP proxy using the `proxy_uri` option, as follows, or by setting the `FRODATA_PROXY_URI` environment variable:
104
+
105
+ ```ruby
106
+ client = Frodo.new(username: 'foo',
107
+ password: 'bar',
108
+ security_token: 'security token',
109
+ client_id: 'client_id',
110
+ client_secret: 'client_secret',
111
+ proxy_uri: 'http://proxy.example.com:123',
112
+ base_path: '/path/to/service')
113
+ ```
114
+
115
+ You may specify a username and password for the proxy with a URL along the lines of 'http://user:password@proxy.example.com:123'.
116
+
117
+ #### Global configuration
118
+
119
+ You can set any of the options passed into `Frodo.new` globally:
120
+
121
+ ```ruby
122
+ Frodo.configure do |config|
123
+ config.client_id = 'foo'
124
+ config.client_secret = 'bar'
125
+ end
126
+ ```
127
+
128
+ ### Bang! methods
129
+
130
+ All the CRUD methods (`create`, `update`, `upsert`, `destroy`) have equivalent methods with
131
+ a ! at the end (`create!`, `update!`, `upsert!`, `destroy!`), which can be used if you need
132
+ to do some custom error handling. The bang methods will raise exceptions, while the
133
+ non-bang methods will return false in the event that an exception is raised. This
134
+ works similarly to ActiveRecord.
135
+
136
+ ### Custom Headers
137
+
138
+ You service may need custom headers. Frodo allows the addition of
139
+ custom headers in REST API requests to trigger specific logic. In order to pass any custom headers along with API requests,
140
+ you can specify a hash of `:request_headers` upon client initialization. The example below demonstrates how
141
+ to include the `myheader` header in all client HTTP requests:
142
+
143
+ ```ruby
144
+ client = Frodo.new(oauth_token: 'access_token',
145
+ instance_url: 'instance url',
146
+ request_headers: { 'myheader' => 'FALSE' })
147
+
148
+ ```
149
+
150
+ ## Client API
151
+
152
+ ### metadata
153
+
154
+ This will provide the XML schema for the service. This is also called automatically the first time you access most of the api in the client and cached in memory. For better performance, see the section below on [Services & the Service Registry](#)
155
+
156
+ ```ruby
157
+ # Get the global describe for all sobjects
158
+ client.metadata
159
+ # => <xml>...</xml>
160
+
161
+ ```
162
+
163
+ ### Queries
164
+
165
+ Queries in general can be speficied directly as a string as such
166
+
167
+ or you can use the `Frodo::Query`. `Frodo::Query` instances form the base for finding specific entities within an `Frodo::EntitySet`.
168
+ A query object exposes a number of capabilities based on
169
+ 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.
170
+ Below is just a partial example of what is possible:
171
+
172
+ ```ruby
173
+ query = client.service['Products'].query
174
+ query.where(query[:Price].lt(15))
175
+ query.where(query[:Rating].gt(3))
176
+ query.limit(3)
177
+ query.skip(2)
178
+ query.order_by("Name")
179
+ query.select("Name,CreatedBy")
180
+ query.inline_count
181
+ results = query.execute
182
+ results.each {|product| puts product['Name']}
183
+ ```
184
+
185
+ The process of querying is kept purposely verbose to allow for lazy behavior to be implemented at higher layers.
186
+ Internally, `Frodo::Query` relies on the `Frodo::Query::Criteria` for the way the `where` method works.
187
+ You should refer to the published RubyDocs for full details on the various capabilities:
188
+
189
+ - [Frodo::Query](http://rubydoc.info/github/wrstudios/frodo/master/Frodo/Query)
190
+ - [Frodo::Query::Criteria](http://rubydoc.info/github/wrstudios/frodo/master/Frodo/Query/Criteria)
191
+
192
+ [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
193
+ [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
194
+
195
+ ```ruby
196
+ products = client.query("Products?$filter=name eq 'somename'")
197
+ # => [#<Frodo::Entity>]
198
+
199
+ # or the equivalent using a query object
200
+ query_object = client.service['Products'].query
201
+ query_object.where("name eq 'yo'")
202
+ products = client.query(query_object)
203
+ # => [#<Frodo::Entity>]
204
+ ```
205
+
206
+ ### Find
207
+
208
+ ```ruby
209
+ # Select an account from an Accounts set with primary key set to '001D000000INjVe'
210
+
211
+ client.find('Accounts', '001D000000INjVe')
212
+ # => #<Frodo::Entity accountid="001D000000INjVe" name="Test" ... >
213
+ ```
214
+
215
+ ### select
216
+
217
+ `select` allows the fetching of a specific list of fields from a single object. Only selected fields will be populated is much faster.
218
+
219
+ ```ruby
220
+ # Select the `name` column from an Account entity in the Accounts set with primary key set to '001D000000INjVe'
221
+
222
+ client.select('Accounts', '001D000000INjVe', ["name"])
223
+ # => # => #<Frodo::Entity accountid="001D000000INjVe" name="Name" other_field="nil" ... >
224
+
225
+ ```
226
+
227
+ ### create
228
+
229
+ ```ruby
230
+ # Add a new account
231
+ client.create('Accounts', Name: 'Foobar Inc.')
232
+ # => '0016000000MRatd'
233
+ ```
234
+
235
+ ### update
236
+
237
+ ```ruby
238
+ # Update the Account with `Id` '0016000000MRatd'
239
+ client.update('Accounts', Id: '0016000000MRatd', Name: 'Whizbang Corp')
240
+ # => true
241
+ ```
242
+
243
+ ### destroy
244
+
245
+ ```ruby
246
+ # Delete the Account with `Id` '0016000000MRatd'
247
+ client.destroy('Accounts', '0016000000MRatd')
248
+ # => true
249
+ ```
250
+
251
+ #### Count
252
+
253
+ ```ruby
254
+ client.count('Accounts')
255
+ # => 3
256
+ ```
257
+
258
+ ### Services & the Service Registry
259
+
260
+ The Frodo gem provides a number of core classes, the two most basic ones are the `Frodo::Service` and the `Frodo::ServiceRegistry`.
261
+ The only time you will need to worry about the `Frodo::ServiceRegistry` is when you have multiple Frodo
262
+ services you are interacting with that you want to keep straight easily.
263
+ The nice thing about `Frodo::Service` is that it automatically registers with the registry on creation, so there is no manual interaction with the registry necessary.
264
+
265
+ To create an `Frodo::Service` simply provide the location of a service endpoint to it like this:
266
+
267
+ ```ruby
268
+ Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc')
269
+ ```
270
+
271
+ You may also provide an options hash after the URL.
272
+ It is suggested that you supply a name for the service via this hash like so:
273
+
274
+ ```ruby
275
+ Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo')
276
+ ```
277
+
278
+ For more information regarding available options and how to configure a service instance, refer to [Service Configuration](#service-configuration) below.
279
+
280
+ This one call will setup the service and allow for the discovery of everything the other parts of the Frodo gem need to function.
281
+ The two methods you will want to remember from `Frodo::Service` are `#service_url` and `#name`.
282
+ Both of these methods are available on instances and will allow for lookup in the `Frodo::ServiceRegistry`, should you need it.
283
+
284
+ Using either the service URL or the name provided as an option when creating an `Frodo::Service` will allow for quick lookup in the `Frodo::ServiceRegistry` like such:
285
+
286
+ ```ruby
287
+ Frodo::ServiceRegistry['http://services.odata.org/V4/OData/OData.svc']
288
+ Frodo::ServiceRegistry['ODataDemo']
289
+ ```
290
+
291
+ Both of the above calls would retrieve the same service from the registry.
292
+ At the moment there is no protection against name collisions provided in `Frodo::ServiceRegistry`.
293
+ So, looking up services by their service URL is the most exact method, but lookup by name is provided for convenience.
294
+
295
+ ### Service Configuration
296
+
297
+ #### Metadata File
298
+
299
+ Typically the metadata file of a service can be quite large.
300
+ You can speed your load time by forcing the service to load the metadata from a file rather than a URL.
301
+ This is only recommended for testing purposes, as the metadata file can change.
302
+
303
+ ```ruby
304
+ service = Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', {
305
+ name: 'ODataDemo',
306
+ metadata_file: "metadata.xml",
307
+ })
308
+ ```
309
+
310
+ #### Metadata Data
311
+
312
+ Typically the metadata file of a service can be quite large.
313
+ You can speed your load time by forcing the service to load the metadata from a file rather than a URL.
314
+ This is only recommended for testing purposes, as the metadata file can change.
315
+
316
+ ```ruby
317
+ service = Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', {
318
+ name: 'ODataDemo',
319
+ metadata_data: "metadata.xml",
320
+ })
321
+ ```
322
+
323
+ ### Exploring a Service
324
+
325
+ 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.
326
+
327
+ For example:
328
+
329
+ Get a list of available entity types
330
+
331
+ ```ruby
332
+ client.service.entity_types
333
+ # => [
334
+ # "ODataDemo.Product",
335
+ # "ODataDemo.FeaturedProduct",
336
+ # "ODataDemo.ProductDetail",
337
+ # "ODataDemo.Category",
338
+ # "ODataDemo.Supplier",
339
+ # "ODataDemo.Person",
340
+ # "ODataDemo.Customer",
341
+ # "ODataDemo.Employee",
342
+ # "ODataDemo.PersonDetail",
343
+ # "ODataDemo.Advertisement"
344
+ # ]
345
+ ```
346
+
347
+ Get a list of entity sets
348
+
349
+ ```ruby
350
+ client.service.entity_sets
351
+ # => {
352
+ # "Products" => "ODataDemo.Product",
353
+ # "ProductDetails" => "ODataDemo.ProductDetail",
354
+ # "Categories" => "ODataDemo.Category",
355
+ # "Suppliers" => "ODataDemo.Supplier",
356
+ # "Persons" => "ODataDemo.Person",
357
+ # "PersonDetails" => "ODataDemo.PersonDetail",
358
+ # "Advertisements" => "ODataDemo.Advertisement"
359
+ # }
360
+ ```
361
+
362
+ Get a list of complex types
363
+
364
+ ```ruby
365
+ client.service.complex_types
366
+ # => ["ODataDemo.Address"]
367
+ ```
368
+
369
+ Get a list of enum types
370
+
371
+ ```ruby
372
+ client.service.enum_types
373
+ # => ["ODataDemo.ProductStatus"]
374
+ ```
375
+
376
+ For more examples, refer to [usage_example_specs.rb](spec/frodo/usage_example_specs.rb).
377
+
378
+ ### Entity Sets
379
+
380
+ When it comes to reading data from an OData service the most typical way will be via `Frodo::EntitySet` instances.
381
+ Under normal circumstances you should never need to worry about an `Frodo::EntitySet` directly.
382
+ For example, to get an `Frodo::EntitySet` for the products in the ODataDemo service simply access the entity set through the service like this:
383
+
384
+ ```ruby
385
+ service = Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc')
386
+ products = service['ProductsSet'] # => Frodo::EntitySet
387
+ ```
388
+
389
+ You can get a list of all your entity sets like this:
390
+
391
+ ```ruby
392
+ service.entity_sets
393
+ ```
394
+
395
+ ### Entities
396
+
397
+ `Frodo::Entity` instances represent individual entities, or records, in a given service.
398
+ They are returned primarily through interaction with instances of `Frodo::EntitySet`.
399
+ You can access individual properties on an `Frodo::Entity` like so:
400
+
401
+ ```ruby
402
+ product = products.first # => Frodo::Entity
403
+ product['Name'] # => 'Bread'
404
+ product['Price'] # => 2.5 (Float)
405
+ ```
406
+
407
+ Individual properties on an `Frodo::Entity` are automatically typecast by the gem, so you don't have to worry about too much when working with entities.
408
+
409
+ You can get a list of all your entities like this:
410
+
411
+ ```ruby
412
+ service.entity_types
413
+ ```
414
+
415
+ #### Entity Properties
416
+
417
+ 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.
418
+ To speed this process up all properties are lazy loaded.
419
+ Which means it will store the name of the property, but will not parse and instantiate the property until you want to use it.
420
+
421
+ You can find all the property names of your entity with
422
+
423
+ ```ruby
424
+ product.property_names
425
+ ```
426
+
427
+ You can grab the parsed value of the property as follows:
428
+
429
+ ```ruby
430
+ product["Name"]
431
+ ```
432
+
433
+ or, you can get a hold of the property class instance using
434
+
435
+ ```ruby
436
+ product.get_property("Name")
437
+ ```
438
+
439
+ This will parse and instantiate the property if it hasn't done so yet.
440
+
441
+ ##### Lenient Property Validation
442
+
443
+ By default, we use strict property validation, meaning that any property validation errors in the data will raise an exception.
444
+ However, you may encounter OData implementations in the wild that break the specs in strange and surprising ways (shocking, I know!).
445
+
446
+ Since it's often better to get _some_ data instead of nothing at all, you can optionally make the property validation lenient.
447
+ Simply add `strict: false` to the service constructor options.
448
+ 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).
449
+
450
+ ```ruby
451
+ service = Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', strict: false)
452
+ # -- alternatively, for an existing service instance --
453
+ service.options[:strict] = false
454
+ ```
455
+
456
+ ## Contributing
457
+
458
+ 1. Fork it (`https://github.com/[my-github-username]/frodo/fork`)
459
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
460
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
461
+ 4. Push to the branch (`git push origin my-new-feature`)
462
+ 5. Create a new Pull Request
463
+
464
+ ## Credits
465
+
466
+ Many thanks go to [James Thompson][@plainprogrammer], who wrote the [original OData (Version 3.0) gem][ruby-odata].
467
+
468
+ [@plainprogrammer]: https://github.com/plainprogrammer
469
+ [ruby-odata]: https://github.com/ruby-odata/odata
470
+
471
+ Many thanks go to [James Thompson][@pandawhisperer], who started the work on the [OData (Version 4.0) gem][frodo].
472
+
473
+ [@plainprogrammer]: https://github.com/PandaWhisperer
474
+ [frodo]: https://github.com/wrstudios/frodo
475
+
476
+ Also, I would like to thank [Outreach][outreach] 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][outreachcareers].
477
+
478
+ [outreach]: http://outreach.io/
479
+ [outreachcareers]: http://outreach.io/careers
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
data/TODO.md ADDED
@@ -0,0 +1,55 @@
1
+ # OData V4 To-Do
2
+
3
+ This is a non-complete list of things that need to be done in order to achieve OData V4 compatibility. It will be updated regularly to keep track with current development.
4
+
5
+ ## Tasks
6
+
7
+ [x] `DataServiceVersion` headers changes to `OData-Version`
8
+ [x] Atom: update namespace URIs
9
+ [x] Implement JSON data format
10
+ [x] with batch processing
11
+ [ ] Implement missing/new OData V4 types
12
+ [x] `Edm.Date` (V4/RESO)
13
+ [ ] `Edm.Duration` (V4)
14
+ [x] `Edm.TimeOfDay` (V4/RESO)
15
+ [x] `Edm.EnumType` (V4/RESO)
16
+ [ ] `Edm.Geography` subtypes (RESO)
17
+ [x] `Edm.GeographyPoint`
18
+ [ ] `Edm.GeographyMultiPoint`
19
+ [x] `Edm.GeographyLineString`
20
+ [ ] `Edm.GeographyMultiLineString`
21
+ [x] `Edm.GeographyPolygon` (see note below)
22
+ [ ] Support for holes
23
+ [ ] Support for other serialization formats
24
+ [ ] `Edm.GeopgrahyMultiPolygon`
25
+
26
+ ##### NOTE
27
+
28
+ Due to the lack of library support for GeoXML/GML in Ruby, Geography support is somewhat limited. For instance, [there are more than 3 different ways to represent a polygon in GML][gml-madness], all of which are equivalent and interchangeable. However, due to the lack of GML libraries, we currently only support a single serialization format (`<gml:LinearRing>` with `<gml:pos>` elements, see [polygon_spec.rb][polygon_spec]).
29
+
30
+ [gml-madness]: http://erouault.blogspot.com/2014/04/gml-madness.html
31
+ [polygon_spec]: spec/odata/v4/properties/geography/polygon_spec.rb
32
+
33
+ [x] Changes to `NavigationProperty`
34
+ [x] No more associations (but we probably still need a proxy class)
35
+ [x] New `Type` property
36
+ [x] New `Nullable` property
37
+ [x] New `Partner` property
38
+ [ ] New `ContainsTarget` property
39
+
40
+ [ ] Changes to querying
41
+ [x] `$count=true` replaces `$inlinecount=allpages`
42
+ [x] New `$search` param for fulltext search
43
+ [x] String functions
44
+ [x] Date/time functions
45
+ [x] Geospatial functions
46
+ [x] [Lambda operators][1]
47
+
48
+ [ ] Logging
49
+
50
+ [1]: http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part2-url-conventions/odata-v4.0-errata02-os-part2-url-conventions-complete.html#_Toc406398149
51
+
52
+ ## Questions / Thoughts
53
+
54
+ [ ] Use standard JSON parser or OJ (or offer choice?)
55
+ [x] Continue to support XML data format (JSON is recommended for V4)? -> We'll support both, ATOM first, JSON to be added later.
data/frodo.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'frodo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'frodo'
8
+ spec.version = Frodo::VERSION
9
+ spec.authors = ['Emmanuel Pinault']
10
+ spec.email = %w{emmanuel.pinault@outreach.io}
11
+ spec.summary = %q{Simple OData library}
12
+ spec.description = %q{Provides a simple interface for working with OData V4 APIs.}
13
+ spec.homepage = 'https://github.com/getoutreach/frodo'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = %w{lib}
20
+
21
+ spec.required_ruby_version = '>= 2.2.0'
22
+
23
+ spec.add_dependency 'nokogiri', '~> 1.8'
24
+ spec.add_dependency 'faraday', '= 0.12.2'
25
+ spec.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 1.0']
26
+ spec.add_dependency 'andand', '~> 1.3'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.6'
29
+ spec.add_development_dependency 'rake', '~> 0'
30
+ spec.add_development_dependency 'simplecov', '~> 0.15'
31
+ spec.add_development_dependency 'rspec', '~> 3.7'
32
+ spec.add_development_dependency 'rspec_junit_formatter'
33
+ spec.add_development_dependency 'rspec-autotest', '~> 1.0'
34
+ spec.add_development_dependency 'autotest', '~> 4.4'
35
+ spec.add_development_dependency 'vcr', '~> 4.0'
36
+ spec.add_development_dependency 'webmock', '~> 3.4.0'
37
+ spec.add_development_dependency 'timecop', '~> 0.9'
38
+ spec.add_development_dependency 'equivalent-xml', '~> 0.6'
39
+ end
data/images/frodo.jpg ADDED
Binary file
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frodo
4
+ class AbstractClient
5
+ include Frodo::Concerns::Base
6
+ include Frodo::Concerns::Connection
7
+ include Frodo::Concerns::Authentication
8
+ include Frodo::Concerns::Caching
9
+ include Frodo::Concerns::API
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frodo
4
+ class Client < AbstractClient
5
+ end
6
+ end