dhs 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (301) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rubocop.yml +27 -0
  3. data/.github/workflows/test.yml +27 -0
  4. data/.gitignore +39 -0
  5. data/.rubocop.yml +186 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +674 -0
  9. data/README.md +2807 -0
  10. data/Rakefile +26 -0
  11. data/dhs.gemspec +44 -0
  12. data/docs/accessing-data.png +0 -0
  13. data/lib/dhs.rb +67 -0
  14. data/lib/dhs/collection.rb +84 -0
  15. data/lib/dhs/complex.rb +158 -0
  16. data/lib/dhs/concerns/autoload_records.rb +57 -0
  17. data/lib/dhs/concerns/collection/handle_nested.rb +43 -0
  18. data/lib/dhs/concerns/collection/internal_collection.rb +74 -0
  19. data/lib/dhs/concerns/configuration.rb +20 -0
  20. data/lib/dhs/concerns/data/becomes.rb +18 -0
  21. data/lib/dhs/concerns/data/equality.rb +14 -0
  22. data/lib/dhs/concerns/data/extend.rb +87 -0
  23. data/lib/dhs/concerns/data/json.rb +14 -0
  24. data/lib/dhs/concerns/data/to_hash.rb +14 -0
  25. data/lib/dhs/concerns/inspect.rb +70 -0
  26. data/lib/dhs/concerns/is_href.rb +15 -0
  27. data/lib/dhs/concerns/item/destroy.rb +38 -0
  28. data/lib/dhs/concerns/item/endpoint_lookup.rb +27 -0
  29. data/lib/dhs/concerns/item/save.rb +55 -0
  30. data/lib/dhs/concerns/item/update.rb +50 -0
  31. data/lib/dhs/concerns/item/validation.rb +61 -0
  32. data/lib/dhs/concerns/o_auth.rb +25 -0
  33. data/lib/dhs/concerns/option_blocks.rb +26 -0
  34. data/lib/dhs/concerns/proxy/accessors.rb +132 -0
  35. data/lib/dhs/concerns/proxy/create.rb +45 -0
  36. data/lib/dhs/concerns/proxy/link.rb +25 -0
  37. data/lib/dhs/concerns/proxy/problems.rb +27 -0
  38. data/lib/dhs/concerns/record/attribute_assignment.rb +25 -0
  39. data/lib/dhs/concerns/record/batch.rb +40 -0
  40. data/lib/dhs/concerns/record/chainable.rb +465 -0
  41. data/lib/dhs/concerns/record/configuration.rb +103 -0
  42. data/lib/dhs/concerns/record/create.rb +24 -0
  43. data/lib/dhs/concerns/record/custom_setters.rb +22 -0
  44. data/lib/dhs/concerns/record/destroy.rb +18 -0
  45. data/lib/dhs/concerns/record/endpoints.rb +108 -0
  46. data/lib/dhs/concerns/record/equality.rb +14 -0
  47. data/lib/dhs/concerns/record/find.rb +86 -0
  48. data/lib/dhs/concerns/record/find_by.rb +38 -0
  49. data/lib/dhs/concerns/record/first.rb +20 -0
  50. data/lib/dhs/concerns/record/href_for.rb +19 -0
  51. data/lib/dhs/concerns/record/last.rb +27 -0
  52. data/lib/dhs/concerns/record/mapping.rb +25 -0
  53. data/lib/dhs/concerns/record/merge.rb +26 -0
  54. data/lib/dhs/concerns/record/model.rb +23 -0
  55. data/lib/dhs/concerns/record/pagination.rb +49 -0
  56. data/lib/dhs/concerns/record/provider.rb +23 -0
  57. data/lib/dhs/concerns/record/relations.rb +26 -0
  58. data/lib/dhs/concerns/record/request.rb +581 -0
  59. data/lib/dhs/concerns/record/scope.rb +25 -0
  60. data/lib/dhs/concerns/record/tracing.rb +24 -0
  61. data/lib/dhs/concerns/record/update.rb +17 -0
  62. data/lib/dhs/config.rb +24 -0
  63. data/lib/dhs/data.rb +180 -0
  64. data/lib/dhs/endpoint.rb +12 -0
  65. data/lib/dhs/interceptors/auto_oauth/interceptor.rb +33 -0
  66. data/lib/dhs/interceptors/auto_oauth/thread_registry.rb +18 -0
  67. data/lib/dhs/interceptors/extended_rollbar/handler.rb +40 -0
  68. data/lib/dhs/interceptors/extended_rollbar/interceptor.rb +20 -0
  69. data/lib/dhs/interceptors/extended_rollbar/thread_registry.rb +19 -0
  70. data/lib/dhs/interceptors/request_cycle_cache/interceptor.rb +41 -0
  71. data/lib/dhs/interceptors/request_cycle_cache/thread_registry.rb +18 -0
  72. data/lib/dhs/item.rb +59 -0
  73. data/lib/dhs/pagination/base.rb +90 -0
  74. data/lib/dhs/pagination/link.rb +21 -0
  75. data/lib/dhs/pagination/offset.rb +22 -0
  76. data/lib/dhs/pagination/page.rb +18 -0
  77. data/lib/dhs/pagination/start.rb +22 -0
  78. data/lib/dhs/pagination/total_pages.rb +9 -0
  79. data/lib/dhs/problems/base.rb +113 -0
  80. data/lib/dhs/problems/errors.rb +69 -0
  81. data/lib/dhs/problems/nested/base.rb +54 -0
  82. data/lib/dhs/problems/nested/errors.rb +16 -0
  83. data/lib/dhs/problems/nested/warnings.rb +15 -0
  84. data/lib/dhs/problems/warnings.rb +24 -0
  85. data/lib/dhs/proxy.rb +69 -0
  86. data/lib/dhs/railtie.rb +34 -0
  87. data/lib/dhs/record.rb +112 -0
  88. data/lib/dhs/rspec.rb +10 -0
  89. data/lib/dhs/test/stubbable_records.rb +34 -0
  90. data/lib/dhs/unprocessable.rb +6 -0
  91. data/lib/dhs/version.rb +5 -0
  92. data/script/ci/build.sh +18 -0
  93. data/spec/auto_oauth_spec.rb +163 -0
  94. data/spec/autoloading_spec.rb +45 -0
  95. data/spec/collection/accessors_spec.rb +31 -0
  96. data/spec/collection/collection_items_spec.rb +44 -0
  97. data/spec/collection/configurable_spec.rb +43 -0
  98. data/spec/collection/delegate_spec.rb +21 -0
  99. data/spec/collection/enumerable_spec.rb +27 -0
  100. data/spec/collection/href_spec.rb +17 -0
  101. data/spec/collection/meta_data_spec.rb +58 -0
  102. data/spec/collection/respond_to_spec.rb +20 -0
  103. data/spec/collection/to_a_spec.rb +34 -0
  104. data/spec/collection/to_ary_spec.rb +40 -0
  105. data/spec/collection/without_object_items_spec.rb +27 -0
  106. data/spec/complex/reduce_spec.rb +202 -0
  107. data/spec/concerns/record/request_spec.rb +78 -0
  108. data/spec/data/collection_spec.rb +56 -0
  109. data/spec/data/equality_spec.rb +23 -0
  110. data/spec/data/inspect_spec.rb +88 -0
  111. data/spec/data/is_item_or_collection_spec.rb +40 -0
  112. data/spec/data/item_spec.rb +106 -0
  113. data/spec/data/merge_spec.rb +27 -0
  114. data/spec/data/parent_spec.rb +39 -0
  115. data/spec/data/raw_spec.rb +48 -0
  116. data/spec/data/respond_to_spec.rb +26 -0
  117. data/spec/data/root_spec.rb +25 -0
  118. data/spec/data/select_spec.rb +27 -0
  119. data/spec/data/to_ary_spec.rb +28 -0
  120. data/spec/data/to_json_spec.rb +68 -0
  121. data/spec/dummy/Rakefile +8 -0
  122. data/spec/dummy/app/assets/images/.keep +0 -0
  123. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  124. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  125. data/spec/dummy/app/controllers/application_controller.rb +26 -0
  126. data/spec/dummy/app/controllers/automatic_authentication_controller.rb +29 -0
  127. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  128. data/spec/dummy/app/controllers/error_handling_with_chains_controller.rb +36 -0
  129. data/spec/dummy/app/controllers/extended_rollbar_controller.rb +10 -0
  130. data/spec/dummy/app/controllers/option_blocks_controller.rb +15 -0
  131. data/spec/dummy/app/controllers/request_cycle_cache_controller.rb +27 -0
  132. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  133. data/spec/dummy/app/mailers/.keep +0 -0
  134. data/spec/dummy/app/models/.keep +0 -0
  135. data/spec/dummy/app/models/concerns/.keep +0 -0
  136. data/spec/dummy/app/models/concerns/dummy_customer/some_concern.rb +9 -0
  137. data/spec/dummy/app/models/dummy_customer.rb +7 -0
  138. data/spec/dummy/app/models/dummy_record.rb +6 -0
  139. data/spec/dummy/app/models/dummy_record_with_auto_oauth_provider.rb +6 -0
  140. data/spec/dummy/app/models/dummy_record_with_multiple_oauth_providers1.rb +7 -0
  141. data/spec/dummy/app/models/dummy_record_with_multiple_oauth_providers2.rb +7 -0
  142. data/spec/dummy/app/models/dummy_record_with_multiple_providers_per_endpoint.rb +6 -0
  143. data/spec/dummy/app/models/dummy_record_with_oauth.rb +7 -0
  144. data/spec/dummy/app/models/dummy_user.rb +6 -0
  145. data/spec/dummy/app/models/providers/customer_system.rb +7 -0
  146. data/spec/dummy/app/models/providers/internal_services.rb +7 -0
  147. data/spec/dummy/app/views/error_handling_with_chains/error.html.erb +1 -0
  148. data/spec/dummy/app/views/error_handling_with_chains/show.html.erb +3 -0
  149. data/spec/dummy/app/views/form_for.html.erb +5 -0
  150. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  151. data/spec/dummy/bin/bundle +5 -0
  152. data/spec/dummy/bin/rails +6 -0
  153. data/spec/dummy/bin/rake +6 -0
  154. data/spec/dummy/config.ru +6 -0
  155. data/spec/dummy/config/application.rb +16 -0
  156. data/spec/dummy/config/boot.rb +7 -0
  157. data/spec/dummy/config/environment.rb +7 -0
  158. data/spec/dummy/config/environments/development.rb +36 -0
  159. data/spec/dummy/config/environments/production.rb +77 -0
  160. data/spec/dummy/config/environments/test.rb +40 -0
  161. data/spec/dummy/config/initializers/assets.rb +10 -0
  162. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  163. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  164. data/spec/dummy/config/initializers/dhs.rb +5 -0
  165. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  166. data/spec/dummy/config/initializers/inflections.rb +18 -0
  167. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  168. data/spec/dummy/config/initializers/rollbar.rb +9 -0
  169. data/spec/dummy/config/initializers/session_store.rb +5 -0
  170. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  171. data/spec/dummy/config/locales/en.yml +23 -0
  172. data/spec/dummy/config/routes.rb +27 -0
  173. data/spec/dummy/config/secrets.yml +22 -0
  174. data/spec/dummy/lib/assets/.keep +0 -0
  175. data/spec/dummy/public/404.html +67 -0
  176. data/spec/dummy/public/422.html +67 -0
  177. data/spec/dummy/public/500.html +66 -0
  178. data/spec/dummy/public/favicon.ico +0 -0
  179. data/spec/endpoint/for_url_spec.rb +27 -0
  180. data/spec/extended_rollbar_spec.rb +67 -0
  181. data/spec/item/access_errors_spec.rb +31 -0
  182. data/spec/item/accessors_spec.rb +21 -0
  183. data/spec/item/add_error_spec.rb +21 -0
  184. data/spec/item/becomes_spec.rb +38 -0
  185. data/spec/item/blacklisted_keywords_spec.rb +28 -0
  186. data/spec/item/delegate_spec.rb +32 -0
  187. data/spec/item/destroy_spec.rb +113 -0
  188. data/spec/item/dig_spec.rb +29 -0
  189. data/spec/item/error_codes_spec.rb +55 -0
  190. data/spec/item/errors_spec.rb +324 -0
  191. data/spec/item/fetch_spec.rb +39 -0
  192. data/spec/item/getter_spec.rb +24 -0
  193. data/spec/item/internal_data_structure_spec.rb +37 -0
  194. data/spec/item/map_spec.rb +46 -0
  195. data/spec/item/nested_errors_spec.rb +27 -0
  196. data/spec/item/partial_update_spec.rb +168 -0
  197. data/spec/item/respond_to_spec.rb +31 -0
  198. data/spec/item/save_spec.rb +115 -0
  199. data/spec/item/setter_spec.rb +44 -0
  200. data/spec/item/translate_errors_spec.rb +257 -0
  201. data/spec/item/update_spec.rb +161 -0
  202. data/spec/item/validation_spec.rb +131 -0
  203. data/spec/item/warning_codes_spec.rb +55 -0
  204. data/spec/item/warnings_spec.rb +51 -0
  205. data/spec/option_blocks/ensure_reset_between_requests_spec.rb +23 -0
  206. data/spec/option_blocks/main_spec.rb +54 -0
  207. data/spec/pagination/link/current_page_spec.rb +19 -0
  208. data/spec/pagination/link/pages_left_spec.rb +36 -0
  209. data/spec/pagination/link/parallel_spec.rb +19 -0
  210. data/spec/pagination/link/total_spec.rb +45 -0
  211. data/spec/pagination/offset/pages_left_spec.rb +26 -0
  212. data/spec/pagination/parameters_spec.rb +59 -0
  213. data/spec/pagination/total_pages_spec.rb +51 -0
  214. data/spec/proxy/create_sub_resource_spec.rb +182 -0
  215. data/spec/proxy/load_spec.rb +75 -0
  216. data/spec/proxy/record_identification_spec.rb +35 -0
  217. data/spec/rails_helper.rb +13 -0
  218. data/spec/record/all_spec.rb +133 -0
  219. data/spec/record/attribute_assignment_spec.rb +28 -0
  220. data/spec/record/build_spec.rb +26 -0
  221. data/spec/record/cast_nested_data_spec.rb +80 -0
  222. data/spec/record/compact_spec.rb +93 -0
  223. data/spec/record/create_spec.rb +160 -0
  224. data/spec/record/creation_failed_spec.rb +55 -0
  225. data/spec/record/custom_setters_spec.rb +42 -0
  226. data/spec/record/definitions_spec.rb +29 -0
  227. data/spec/record/destroy_spec.rb +38 -0
  228. data/spec/record/dig_configuration_spec.rb +75 -0
  229. data/spec/record/dup_spec.rb +20 -0
  230. data/spec/record/endpoint_inheritance_spec.rb +65 -0
  231. data/spec/record/endpoint_options_spec.rb +51 -0
  232. data/spec/record/endpoint_priorities_spec.rb +24 -0
  233. data/spec/record/endpoints_spec.rb +96 -0
  234. data/spec/record/equality_spec.rb +27 -0
  235. data/spec/record/error_handling_integration_spec.rb +25 -0
  236. data/spec/record/error_handling_spec.rb +40 -0
  237. data/spec/record/expanded_spec.rb +69 -0
  238. data/spec/record/fetch_spec.rb +40 -0
  239. data/spec/record/find_by_chains_spec.rb +21 -0
  240. data/spec/record/find_by_spec.rb +76 -0
  241. data/spec/record/find_each_spec.rb +57 -0
  242. data/spec/record/find_in_batches_spec.rb +122 -0
  243. data/spec/record/find_in_parallel_spec.rb +67 -0
  244. data/spec/record/find_spec.rb +103 -0
  245. data/spec/record/first_spec.rb +39 -0
  246. data/spec/record/force_merge_spec.rb +55 -0
  247. data/spec/record/handle_includes_errors_spec.rb +33 -0
  248. data/spec/record/has_many_spec.rb +118 -0
  249. data/spec/record/has_one_spec.rb +114 -0
  250. data/spec/record/href_for_spec.rb +24 -0
  251. data/spec/record/ignore_errors_spec.rb +137 -0
  252. data/spec/record/immutable_chains_spec.rb +22 -0
  253. data/spec/record/includes_after_expansion_spec.rb +70 -0
  254. data/spec/record/includes_expanded_spec.rb +37 -0
  255. data/spec/record/includes_first_page_spec.rb +738 -0
  256. data/spec/record/includes_missing_spec.rb +57 -0
  257. data/spec/record/includes_spec.rb +690 -0
  258. data/spec/record/includes_warning_spec.rb +46 -0
  259. data/spec/record/item_key_spec.rb +81 -0
  260. data/spec/record/items_created_key_configuration_spec.rb +37 -0
  261. data/spec/record/last_spec.rb +64 -0
  262. data/spec/record/loading_twice_spec.rb +19 -0
  263. data/spec/record/mapping_spec.rb +103 -0
  264. data/spec/record/model_name_spec.rb +17 -0
  265. data/spec/record/new_spec.rb +106 -0
  266. data/spec/record/options_getter_spec.rb +25 -0
  267. data/spec/record/options_spec.rb +164 -0
  268. data/spec/record/paginatable_collection_spec.rb +360 -0
  269. data/spec/record/pagination_chain_spec.rb +101 -0
  270. data/spec/record/pagination_links_spec.rb +72 -0
  271. data/spec/record/pagination_spec.rb +71 -0
  272. data/spec/record/persisted_spec.rb +52 -0
  273. data/spec/record/provider_spec.rb +40 -0
  274. data/spec/record/references_spec.rb +95 -0
  275. data/spec/record/relation_caching_spec.rb +120 -0
  276. data/spec/record/reload_by_id_spec.rb +43 -0
  277. data/spec/record/reload_spec.rb +64 -0
  278. data/spec/record/request_spec.rb +90 -0
  279. data/spec/record/save_spec.rb +40 -0
  280. data/spec/record/scope_chains_spec.rb +39 -0
  281. data/spec/record/select_spec.rb +17 -0
  282. data/spec/record/to_ary_spec.rb +65 -0
  283. data/spec/record/to_hash_spec.rb +22 -0
  284. data/spec/record/to_json_spec.rb +22 -0
  285. data/spec/record/tracing_spec.rb +149 -0
  286. data/spec/record/update_spec.rb +61 -0
  287. data/spec/record/where_chains_spec.rb +57 -0
  288. data/spec/record/where_spec.rb +62 -0
  289. data/spec/record/where_values_hash_spec.rb +32 -0
  290. data/spec/request_cycle_cache_spec.rb +106 -0
  291. data/spec/require_dhs_spec.rb +9 -0
  292. data/spec/spec_helper.rb +6 -0
  293. data/spec/stubs/all_spec.rb +69 -0
  294. data/spec/support/fixtures/json/feedback.json +11 -0
  295. data/spec/support/fixtures/json/feedbacks.json +174 -0
  296. data/spec/support/fixtures/json/localina_content_ad.json +23 -0
  297. data/spec/support/load_json.rb +5 -0
  298. data/spec/support/request_cycle_cache.rb +10 -0
  299. data/spec/support/reset.rb +67 -0
  300. data/spec/views/form_for_spec.rb +20 -0
  301. metadata +783 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+ before do
7
+ class Record < DHS::Record
8
+ endpoint 'http://datastore/records/'
9
+ endpoint 'http://datastore/records/{id}'
10
+ end
11
+ end
12
+
13
+ context 'href_for' do
14
+ it 'injects variables and returns href' do
15
+ expect(Record.href_for(1)).to eq 'http://datastore/records/1'
16
+ expect(Record.href_for('vmasd241')).to eq 'http://datastore/records/vmasd241'
17
+ end
18
+
19
+ it 'also works with url_for (alias)' do
20
+ expect(Record.url_for(1)).to eq 'http://datastore/records/1'
21
+ expect(Record.url_for('vmasd241')).to eq 'http://datastore/records/vmasd241'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+ let(:handler) { spy('handler') }
7
+ let(:record_json) { { color: 'blue' }.to_json }
8
+
9
+ before do
10
+ class Record < DHS::Record
11
+ endpoint 'http://depay.fi/v2/records'
12
+ endpoint 'http://depay.fi/v2/records/{id}'
13
+ end
14
+ end
15
+
16
+ context 'ignore errors' do
17
+ it 'allows to ignore errors' do
18
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(status: 404)
19
+ record = Record
20
+ .where(color: 'blue')
21
+ .ignore(DHC::NotFound)
22
+ .fetch
23
+ expect(record).to eq nil
24
+ end
25
+ end
26
+
27
+ context 'ignore errors during create' do
28
+ it 'allows to ignore errors during create' do
29
+ stub_request(:post, 'http://depay.fi/v2/records')
30
+ .to_return(status: 409)
31
+ record = Record.ignore(DHC::Conflict).create(name: 'Steve')
32
+ expect(record._raw).to eq(name: 'Steve')
33
+ end
34
+ end
35
+
36
+ context 'multiple ignored errors' do
37
+ it 'ignores error if first of them is specified' do
38
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(status: 401)
39
+ record = Record
40
+ .ignore(DHC::Unauthorized)
41
+ .where(color: 'blue')
42
+ .ignore(DHC::NotFound)
43
+ .fetch
44
+ expect(record).to eq nil
45
+ end
46
+
47
+ it 'ignores error if last of them is specified' do
48
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(status: 404)
49
+ record = Record
50
+ .ignore(DHC::Unauthorized)
51
+ .where(color: 'blue')
52
+ .ignore(DHC::NotFound)
53
+ .fetch
54
+ expect(record).to eq nil
55
+ end
56
+ end
57
+
58
+ it 'also can ignore all DHC errors' do
59
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(status: 401)
60
+ record = Record
61
+ .ignore(DHC::Error)
62
+ .where(color: 'blue')
63
+ .fetch
64
+ expect(record).to eq nil
65
+ end
66
+
67
+ it 'can ignore multiple error with one ignore call, on chain start' do
68
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(status: 401)
69
+ record = Record
70
+ .ignore(DHC::Unauthorized, DHC::NotFound)
71
+ .where(color: 'blue')
72
+ .fetch
73
+ expect(record).to eq nil
74
+ end
75
+
76
+ it 'can ignore multiple error with one ignore call, also within the chain' do
77
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(status: 401)
78
+ record = Record
79
+ .where(color: 'blue')
80
+ .ignore(DHC::Unauthorized, DHC::NotFound)
81
+ .fetch
82
+ expect(record).to eq nil
83
+ end
84
+
85
+ it 'returns record when ignoring errors on where' do
86
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(status: 200, body: record_json)
87
+ record = Record
88
+ .ignore(DHC::Error)
89
+ .where(color: 'blue')
90
+ expect(record).not_to eq nil
91
+ end
92
+
93
+ context 'response body' do
94
+ let(:body) { { error_message: 'you are not worthy' }.to_json }
95
+
96
+ it 'returns nil also when ignoring errors on find' do
97
+ stub_request(:get, 'http://depay.fi/v2/records/1').to_return(status: 500, body: body)
98
+ record = Record
99
+ .ignore(DHC::Error)
100
+ .find(1)
101
+ expect(record).to eq nil
102
+ end
103
+
104
+ it 'returns nil also when ignoring errors on find with parameters' do
105
+ stub_request(:get, 'http://depay.fi/v2/records/1').to_return(status: 500, body: body)
106
+ record = Record
107
+ .ignore(DHC::Error)
108
+ .find(id: 1)
109
+ expect(record).to eq nil
110
+ end
111
+
112
+ it 'returns nil also when ignoring errors on fetch' do
113
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(status: 500, body: body)
114
+ record = Record
115
+ .ignore(DHC::Error)
116
+ .where(color: 'blue')
117
+ .fetch
118
+ expect(record).to eq nil
119
+ end
120
+
121
+ it 'returns nil also when ignoring errors on find_by' do
122
+ stub_request(:get, 'http://depay.fi/v2/records?color=blue&limit=1').to_return(status: 500, body: body)
123
+ record = Record
124
+ .ignore(DHC::Error)
125
+ .find_by(color: 'blue')
126
+ expect(record).to eq nil
127
+ end
128
+
129
+ it 'returns record when ignoring errors on find' do
130
+ stub_request(:get, 'http://depay.fi/v2/records/1').to_return(status: 200, body: record_json)
131
+ record = Record
132
+ .ignore(DHC::Error)
133
+ .find(1)
134
+ expect(record).not_to eq nil
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+ before do
7
+ class Record < DHS::Record
8
+ endpoint 'http://depay.fi/v2/records'
9
+ end
10
+ end
11
+
12
+ it 'always returns a new chain and does not mutate the original' do
13
+ blue_request = stub_request(:get, 'http://depay.fi/v2/records?color=blue').to_return(body: [].to_json)
14
+ blue_records = Record.where(color: 'blue')
15
+ blue_active_request = stub_request(:get, 'http://depay.fi/v2/records?color=blue&active=true').to_return(body: [].to_json)
16
+ active_blue_records = blue_records.where(active: true)
17
+ blue_records.first
18
+ active_blue_records.first
19
+ assert_requested(blue_request)
20
+ assert_requested(blue_active_request)
21
+ end
22
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+ context 'includes records after expansion' do
7
+ before do
8
+ class User < DHS::Record
9
+ endpoint 'http://users/{id}'
10
+ end
11
+
12
+ class Places < DHS::Record
13
+ endpoint 'http://users/{id}/places'
14
+ endpoint 'http://places/{id}'
15
+ end
16
+
17
+ class Contracts < DHS::Record
18
+ endpoint 'http://places/{place_id}/contracts'
19
+ end
20
+
21
+ stub_request(:get, 'http://users/1')
22
+ .to_return(
23
+ body: {
24
+ places: {
25
+ href: 'http://users/1/places'
26
+ }
27
+ }.to_json
28
+ )
29
+
30
+ stub_request(:get, 'http://users/1/places?limit=100')
31
+ .to_return(
32
+ body: {
33
+ items: [
34
+ { href: 'http://places/345' }
35
+ ],
36
+ total: 1,
37
+ offset: 0,
38
+ limit: 10
39
+ }.to_json
40
+ )
41
+
42
+ stub_request(:get, 'http://places/345')
43
+ .to_return(
44
+ body: {
45
+ contracts: {
46
+ href: 'http://places/345/contracts?offset=0&limit=10'
47
+ }
48
+ }.to_json
49
+ )
50
+
51
+ stub_request(:get, 'http://places/345/contracts?offset=0&limit=10')
52
+ .to_return(
53
+ body: {
54
+ items: [
55
+ {
56
+ product: { name: 'OPL' }
57
+ }
58
+ ]
59
+ }.to_json
60
+ )
61
+ end
62
+
63
+ it 'includes resources after expanding plain links' do
64
+ user = User.includes(places: :contracts).find(1)
65
+ expect(
66
+ user.places.first.contracts.first.product.name
67
+ ).to eq 'OPL'
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+ context 'includes all (expanded)' do
7
+ before do
8
+ class Record < DHS::Record
9
+ endpoint 'http://records/{id}'
10
+ end
11
+
12
+ stub_request(:get, 'http://records/1')
13
+ .to_return(
14
+ body: {
15
+ car: { href: 'http://records/1/car' }
16
+ }.to_json
17
+ )
18
+
19
+ stub_request(:get, 'http://records/1/car?color=blue&limit=100')
20
+ .to_return(
21
+ body: { href: 'http://records/cars/1' }.to_json
22
+ )
23
+
24
+ stub_request(:get, 'http://records/cars/1?color=blue&limit=100')
25
+ .to_return(
26
+ body: { name: 'wrum wrum' }.to_json
27
+ )
28
+ end
29
+
30
+ it 'expands linked resources and forwards proper reference' do
31
+ record = Record.includes(:car).references(car: { params: { color: :blue } }).find(1)
32
+ expect(
33
+ record.car.name
34
+ ).to eq 'wrum wrum'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,738 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+ let(:datastore) { 'http://depay.fi/v2' }
7
+ before { DHC.config.placeholder('datastore', datastore) }
8
+
9
+ let(:stub_campaign_request) do
10
+ stub_request(:get, "#{datastore}/content-ads/51dfc5690cf271c375c5a12d")
11
+ .to_return(body: {
12
+ 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d",
13
+ 'entry' => { 'href' => "#{datastore}/local-entries/lakj35asdflkj1203va" },
14
+ 'user' => { 'href' => "#{datastore}/users/lakj35asdflkj1203va" }
15
+ }.to_json)
16
+ end
17
+
18
+ let(:stub_entry_request) do
19
+ stub_request(:get, "#{datastore}/local-entries/lakj35asdflkj1203va")
20
+ .to_return(body: { 'name' => 'Casa Ferlin' }.to_json)
21
+ end
22
+
23
+ let(:stub_user_request) do
24
+ stub_request(:get, "#{datastore}/users/lakj35asdflkj1203va")
25
+ .to_return(body: { 'name' => 'Mario' }.to_json)
26
+ end
27
+
28
+ context 'singlelevel includes' do
29
+ before do
30
+ class LocalEntry < DHS::Record
31
+ endpoint '{+datastore}/local-entries'
32
+ endpoint '{+datastore}/local-entries/{id}'
33
+ end
34
+
35
+ class User < DHS::Record
36
+ endpoint '{+datastore}/users'
37
+ endpoint '{+datastore}/users/{id}'
38
+ end
39
+
40
+ class Favorite < DHS::Record
41
+ endpoint '{+datastore}/favorites'
42
+ endpoint '{+datastore}/favorites/{id}'
43
+ end
44
+ stub_request(:get, "#{datastore}/local-entries/1")
45
+ .to_return(body: { company_name: 'depay.fi' }.to_json)
46
+ stub_request(:get, "#{datastore}/users/1")
47
+ .to_return(body: { name: 'Mario' }.to_json)
48
+ stub_request(:get, "#{datastore}/favorites/1")
49
+ .to_return(body: {
50
+ local_entry: { href: "#{datastore}/local-entries/1" },
51
+ user: { href: "#{datastore}/users/1" }
52
+ }.to_json)
53
+ end
54
+
55
+ it 'includes a resource' do
56
+ favorite = Favorite.includes_first_page(:local_entry).find(1)
57
+ expect(favorite.local_entry.company_name).to eq 'depay.fi'
58
+ end
59
+
60
+ it 'duplicates a class' do
61
+ expect(Favorite.object_id).not_to eq(Favorite.includes_first_page(:local_entry).object_id)
62
+ end
63
+
64
+ it 'includes a list of resources' do
65
+ favorite = Favorite.includes_first_page(:local_entry, :user).find(1)
66
+ expect(favorite.local_entry).to be_kind_of LocalEntry
67
+ expect(favorite.local_entry.company_name).to eq 'depay.fi'
68
+ expect(favorite.user.name).to eq 'Mario'
69
+ end
70
+
71
+ it 'includes an array of resources' do
72
+ favorite = Favorite.includes_first_page(%i[local_entry user]).find(1)
73
+ expect(favorite.local_entry.company_name).to eq 'depay.fi'
74
+ expect(favorite.user.name).to eq 'Mario'
75
+ end
76
+ end
77
+
78
+ context 'multilevel includes' do
79
+ before do
80
+ class Feedback < DHS::Record
81
+ endpoint '{+datastore}/feedbacks'
82
+ endpoint '{+datastore}/feedbacks/{id}'
83
+ end
84
+ stub_campaign_request
85
+ stub_entry_request
86
+ stub_user_request
87
+ end
88
+
89
+ it 'includes linked resources while fetching multiple resources from one service' do
90
+ stub_request(:get, "#{datastore}/feedbacks?has_reviews=true")
91
+ .to_return(status: 200, body: {
92
+ items: [
93
+ {
94
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
95
+ 'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
96
+ }
97
+ ]
98
+ }.to_json)
99
+
100
+ feedbacks = Feedback.includes_first_page(campaign: :entry).where(has_reviews: true)
101
+ expect(feedbacks.first.campaign.entry.name).to eq 'Casa Ferlin'
102
+ end
103
+
104
+ it 'includes linked resources while fetching a single resource from one service' do
105
+ stub_request(:get, "#{datastore}/feedbacks/123")
106
+ .to_return(status: 200, body: {
107
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
108
+ 'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
109
+ }.to_json)
110
+
111
+ feedbacks = Feedback.includes_first_page(campaign: :entry).find(123)
112
+ expect(feedbacks.campaign.entry.name).to eq 'Casa Ferlin'
113
+ end
114
+
115
+ it 'includes linked resources with array while fetching a single resource from one service' do
116
+ stub_request(:get, "#{datastore}/feedbacks/123")
117
+ .to_return(status: 200, body: {
118
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
119
+ 'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
120
+ }.to_json)
121
+
122
+ feedbacks = Feedback.includes_first_page(campaign: %i[entry user]).find(123)
123
+ expect(feedbacks.campaign.entry.name).to eq 'Casa Ferlin'
124
+ expect(feedbacks.campaign.user.name).to eq 'Mario'
125
+ end
126
+
127
+ it 'includes list of linked resources while fetching a single resource from one service' do
128
+ stub_request(:get, "#{datastore}/feedbacks/123")
129
+ .to_return(status: 200, body: {
130
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
131
+ 'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" },
132
+ 'user' => { 'href' => "#{datastore}/users/lakj35asdflkj1203va" }
133
+ }.to_json)
134
+
135
+ feedbacks = Feedback.includes_first_page(:user, campaign: %i[entry user]).find(123)
136
+ expect(feedbacks.campaign.entry.name).to eq 'Casa Ferlin'
137
+ expect(feedbacks.campaign.user.name).to eq 'Mario'
138
+ expect(feedbacks.user.name).to eq 'Mario'
139
+ end
140
+
141
+ context 'include objects from known services' do
142
+ let(:stub_feedback_request) do
143
+ stub_request(:get, "#{datastore}/feedbacks")
144
+ .to_return(status: 200, body: {
145
+ items: [
146
+ {
147
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
148
+ 'entry' => {
149
+ 'href' => "#{datastore}/local-entries/lakj35asdflkj1203va"
150
+ }
151
+ }
152
+ ]
153
+ }.to_json)
154
+ end
155
+
156
+ let(:interceptor) { spy('interceptor') }
157
+
158
+ before do
159
+ class Entry < DHS::Record
160
+ endpoint '{+datastore}/local-entries/{id}'
161
+ end
162
+ DHC.config.interceptors = [interceptor]
163
+ end
164
+
165
+ it 'uses interceptors for included links from known services' do
166
+ stub_feedback_request
167
+ stub_entry_request
168
+ expect(Feedback.includes_first_page(:entry).where.first.entry.name).to eq 'Casa Ferlin'
169
+ expect(interceptor).to have_received(:before_request).twice
170
+ end
171
+ end
172
+
173
+ context 'includes not present in response' do
174
+ before do
175
+ class Parent < DHS::Record
176
+ endpoint '{+datastore}/local-parents'
177
+ endpoint '{+datastore}/local-parents/{id}'
178
+ end
179
+
180
+ class OptionalChild < DHS::Record
181
+ endpoint '{+datastore}/DePayFiildren/{id}'
182
+ end
183
+ end
184
+
185
+ it 'handles missing but included fields in single object response' do
186
+ stub_request(:get, "#{datastore}/local-parents/1")
187
+ .to_return(status: 200, body: {
188
+ 'href' => "#{datastore}/local-parents/1",
189
+ 'name' => 'RspecName'
190
+ }.to_json)
191
+
192
+ parent = Parent.includes_first_page(:optional_children).find(1)
193
+ expect(parent).not_to be nil
194
+ expect(parent.name).to eq 'RspecName'
195
+ expect(parent.optional_children).to be nil
196
+ end
197
+
198
+ it 'handles missing but included fields in collection response' do
199
+ stub_request(:get, "#{datastore}/local-parents")
200
+ .to_return(status: 200, body: {
201
+ items: [
202
+ {
203
+ 'href' => "#{datastore}/local-parents/1",
204
+ 'name' => 'RspecParent'
205
+ }, {
206
+ 'href' => "#{datastore}/local-parents/2",
207
+ 'name' => 'RspecParent2',
208
+ 'optional_child' => {
209
+ 'href' => "#{datastore}/DePayFiildren/1"
210
+ }
211
+ }
212
+ ]
213
+ }.to_json)
214
+
215
+ stub_request(:get, "#{datastore}/DePayFiildren/1")
216
+ .to_return(status: 200, body: {
217
+ href: "#{datastore}/local_children/1",
218
+ name: 'RspecOptionalChild1'
219
+ }.to_json)
220
+
221
+ child = Parent.includes_first_page(:optional_child).where[1].optional_child
222
+ expect(child).not_to be nil
223
+ expect(child.name).to eq 'RspecOptionalChild1'
224
+ end
225
+ end
226
+ end
227
+
228
+ context 'links pointing to nowhere' do
229
+ before do
230
+ class Feedback < DHS::Record
231
+ endpoint '{+datastore}/feedbacks'
232
+ endpoint '{+datastore}/feedbacks/{id}'
233
+ end
234
+
235
+ stub_request(:get, "#{datastore}/feedbacks/123")
236
+ .to_return(status: 200, body: {
237
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
238
+ 'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
239
+ }.to_json)
240
+
241
+ stub_request(:get, "#{datastore}/content-ads/51dfc5690cf271c375c5a12d")
242
+ .to_return(status: 404)
243
+ end
244
+
245
+ it 'raises DHC::NotFound for links that cannot be included' do
246
+ expect(-> {
247
+ Feedback.includes_first_page(campaign: :entry).find(123)
248
+ }).to raise_error DHC::NotFound
249
+ end
250
+
251
+ it 'ignores DHC::NotFound for links that cannot be included if configured so with reference options' do
252
+ feedback = Feedback
253
+ .includes_first_page(campaign: :entry)
254
+ .references(campaign: { ignore: [DHC::NotFound] })
255
+ .find(123)
256
+ expect(feedback.campaign._raw.keys.length).to eq 1
257
+ end
258
+ end
259
+
260
+ context 'modules' do
261
+ before do
262
+ module Services
263
+ class LocalEntry < DHS::Record
264
+ endpoint '{+datastore}/local-entries'
265
+ end
266
+
267
+ class Feedback < DHS::Record
268
+ endpoint '{+datastore}/feedbacks'
269
+ end
270
+ end
271
+ stub_request(:get, 'http://depay.fi/v2/feedbacks?id=123')
272
+ .to_return(body: [].to_json)
273
+ end
274
+
275
+ it 'works with modules' do
276
+ Services::Feedback.includes_first_page(campaign: :entry).find(123)
277
+ end
278
+ end
279
+
280
+ context 'arrays' do
281
+ before do
282
+ class Place < DHS::Record
283
+ endpoint '{+datastore}/place'
284
+ endpoint '{+datastore}/place/{id}'
285
+ end
286
+ end
287
+
288
+ let!(:place_request) do
289
+ stub_request(:get, "#{datastore}/place/1")
290
+ .to_return(body: {
291
+ 'relations' => [
292
+ { 'href' => "#{datastore}/place/relations/2" },
293
+ { 'href' => "#{datastore}/place/relations/3" }
294
+ ]
295
+ }.to_json)
296
+ end
297
+
298
+ let!(:relation_request_1) do
299
+ stub_request(:get, "#{datastore}/place/relations/2")
300
+ .to_return(body: { name: 'Category' }.to_json)
301
+ end
302
+
303
+ let!(:relation_request_2) do
304
+ stub_request(:get, "#{datastore}/place/relations/3")
305
+ .to_return(body: { name: 'ZeFrank' }.to_json)
306
+ end
307
+
308
+ it 'includes items of arrays' do
309
+ place = Place.includes_first_page(:relations).find(1)
310
+ expect(place.relations.first.name).to eq 'Category'
311
+ expect(place.relations[1].name).to eq 'ZeFrank'
312
+ end
313
+
314
+ context 'parallel with empty links' do
315
+ let!(:place_request_2) do
316
+ stub_request(:get, "#{datastore}/place/2")
317
+ .to_return(body: {
318
+ 'relations' => []
319
+ }.to_json)
320
+ end
321
+
322
+ it 'loads places in parallel and merges included data properly' do
323
+ place = Place.includes_first_page(:relations).find(2, 1)
324
+ expect(place[0].relations.empty?).to be true
325
+ expect(place[1].relations[0].name).to eq 'Category'
326
+ expect(place[1].relations[1].name).to eq 'ZeFrank'
327
+ end
328
+ end
329
+ end
330
+
331
+ context 'empty collections' do
332
+ it 'skips including empty collections' do
333
+ class Place < DHS::Record
334
+ endpoint '{+datastore}/place'
335
+ endpoint '{+datastore}/place/{id}'
336
+ end
337
+
338
+ stub_request(:get, "#{datastore}/place/1")
339
+ .to_return(body: {
340
+ 'available_products' => {
341
+ 'url' => "#{datastore}/place/1/products",
342
+ 'items' => []
343
+ }
344
+ }.to_json)
345
+
346
+ place = Place.includes_first_page(:available_products).find(1)
347
+ expect(place.available_products.empty?).to eq true
348
+ end
349
+ end
350
+
351
+ context 'extend items with arrays' do
352
+ it 'extends base items with arrays' do
353
+ class Place < DHS::Record
354
+ endpoint '{+datastore}/place'
355
+ endpoint '{+datastore}/place/{id}'
356
+ end
357
+
358
+ stub_request(:get, "#{datastore}/place/1")
359
+ .to_return(body: {
360
+ 'contracts' => {
361
+ 'items' => [{ 'href' => "#{datastore}/place/1/contacts/1" }]
362
+ }
363
+ }.to_json)
364
+
365
+ stub_request(:get, "#{datastore}/place/1/contacts/1")
366
+ .to_return(body: {
367
+ 'products' => { 'href' => "#{datastore}/place/1/contacts/1/products" }
368
+ }.to_json)
369
+
370
+ place = Place.includes_first_page(:contracts).find(1)
371
+ expect(place.contracts.first.products.href).to eq "#{datastore}/place/1/contacts/1/products"
372
+ end
373
+ end
374
+
375
+ context 'unexpanded response when requesting the included collection' do
376
+ before do
377
+ class Customer < DHS::Record
378
+ endpoint '{+datastore}/customer/{id}'
379
+ end
380
+ end
381
+
382
+ let!(:customer_request) do
383
+ stub_request(:get, "#{datastore}/customer/1")
384
+ .to_return(body: {
385
+ places: {
386
+ href: "#{datastore}/places"
387
+ }
388
+ }.to_json)
389
+ end
390
+
391
+ let!(:places_request) do
392
+ stub_request(:get, "#{datastore}/places")
393
+ .to_return(body: {
394
+ items: [{ href: "#{datastore}/places/1" }]
395
+ }.to_json)
396
+ end
397
+
398
+ let!(:place_request) do
399
+ stub_request(:get, "#{datastore}/places/1")
400
+ .to_return(body: {
401
+ name: 'Casa Ferlin'
402
+ }.to_json)
403
+ end
404
+
405
+ it 'loads the collection and the single items, if not already expanded' do
406
+ place = Customer.includes_first_page(:places).find(1).places.first
407
+ assert_requested(place_request)
408
+ expect(place.name).to eq 'Casa Ferlin'
409
+ end
410
+
411
+ context 'forwarding options' do
412
+ let!(:places_request) do
413
+ stub_request(:get, "#{datastore}/places")
414
+ .with(headers: { 'Authorization' => 'Bearer 123' })
415
+ .to_return(
416
+ body: {
417
+ items: [{ href: "#{datastore}/places/1" }]
418
+ }.to_json
419
+ )
420
+ end
421
+
422
+ let!(:place_request) do
423
+ stub_request(:get, "#{datastore}/places/1")
424
+ .with(headers: { 'Authorization' => 'Bearer 123' })
425
+ .to_return(
426
+ body: {
427
+ name: 'Casa Ferlin'
428
+ }.to_json
429
+ )
430
+ end
431
+
432
+ it 'forwards options used to expand those unexpanded items' do
433
+ place = Customer
434
+ .includes_first_page(:places)
435
+ .references(places: { headers: { 'Authorization' => 'Bearer 123' } })
436
+ .find(1)
437
+ .places.first
438
+ assert_requested(place_request)
439
+ expect(place.name).to eq 'Casa Ferlin'
440
+ end
441
+ end
442
+ end
443
+
444
+ context 'includes with options' do
445
+ before do
446
+ class Customer < DHS::Record
447
+ endpoint '{+datastore}/customers/{id}'
448
+ endpoint '{+datastore}/customers'
449
+ end
450
+
451
+ class Place < DHS::Record
452
+ endpoint '{+datastore}/places'
453
+ end
454
+
455
+ stub_request(:get, "#{datastore}/places?forwarded_params=123")
456
+ .to_return(body: {
457
+ 'items' => [{ id: 1 }]
458
+ }.to_json)
459
+ end
460
+
461
+ it 'forwards includes options to requests made for those includes' do
462
+ stub_request(:get, "#{datastore}/customers/1")
463
+ .to_return(body: {
464
+ 'places' => {
465
+ 'href' => "#{datastore}/places"
466
+ }
467
+ }.to_json)
468
+ customer = Customer
469
+ .includes_first_page(:places)
470
+ .references(places: { params: { forwarded_params: 123 } })
471
+ .find(1)
472
+ expect(customer.places.first.id).to eq 1
473
+ end
474
+
475
+ it 'is chain-able' do
476
+ stub_request(:get, "#{datastore}/customers?name=Steve")
477
+ .to_return(body: [
478
+ 'places' => {
479
+ 'href' => "#{datastore}/places"
480
+ }
481
+ ].to_json)
482
+ customers = Customer
483
+ .where(name: 'Steve')
484
+ .references(places: { params: { forwarded_params: 123 } })
485
+ .includes_first_page(:places)
486
+ expect(customers.first.places.first.id).to eq 1
487
+ end
488
+ end
489
+
490
+ context 'more complex examples' do
491
+ before do
492
+ class Place < DHS::Record
493
+ endpoint 'http://datastore/places/{id}'
494
+ end
495
+ end
496
+
497
+ it 'forwards complex references' do
498
+ stub_request(:get, 'http://datastore/places/123?limit=1&forwarded_params=for_place')
499
+ .to_return(body: {
500
+ 'contracts' => {
501
+ 'href' => 'http://datastore/places/123/contracts'
502
+ }
503
+ }.to_json)
504
+ stub_request(:get, 'http://datastore/places/123/contracts?forwarded_params=for_contracts')
505
+ .to_return(body: {
506
+ href: 'http://datastore/places/123/contracts?forwarded_params=for_contracts',
507
+ items: [
508
+ { product: { 'href' => 'http://datastore/products/llo' } }
509
+ ]
510
+ }.to_json)
511
+ stub_request(:get, 'http://datastore/products/llo?forwarded_params=for_product')
512
+ .to_return(body: {
513
+ 'href' => 'http://datastore/products/llo',
514
+ 'name' => 'Local Logo'
515
+ }.to_json)
516
+ place = Place
517
+ .options(params: { forwarded_params: 'for_place' })
518
+ .includes_first_page(contracts: :product)
519
+ .references(
520
+ contracts: {
521
+ params: { forwarded_params: 'for_contracts' },
522
+ product: { params: { forwarded_params: 'for_product' } }
523
+ }
524
+ )
525
+ .find_by(id: '123')
526
+ expect(
527
+ place.contracts.first.product.name
528
+ ).to eq 'Local Logo'
529
+ end
530
+
531
+ it 'expands empty arrays' do
532
+ stub_request(:get, 'http://datastore/places/123')
533
+ .to_return(body: {
534
+ 'contracts' => {
535
+ 'href' => 'http://datastore/places/123/contracts'
536
+ }
537
+ }.to_json)
538
+ stub_request(:get, 'http://datastore/places/123/contracts')
539
+ .to_return(body: {
540
+ href: 'http://datastore/places/123/contracts',
541
+ items: []
542
+ }.to_json)
543
+ place = Place.includes_first_page(:contracts).find('123')
544
+ expect(place.contracts.collection?).to eq true
545
+ expect(
546
+ place.contracts.as_json
547
+ ).to eq('href' => 'http://datastore/places/123/contracts', 'items' => [])
548
+ expect(place.contracts.to_a).to eq([])
549
+ end
550
+ end
551
+
552
+ context 'include and merge arrays when calling find in parallel' do
553
+ before do
554
+ class Place < DHS::Record
555
+ endpoint 'http://datastore/places/{id}'
556
+ end
557
+ stub_request(:get, 'http://datastore/places/1')
558
+ .to_return(body: {
559
+ category_relations: [{ href: 'http://datastore/category/1' }, { href: 'http://datastore/category/2' }]
560
+ }.to_json)
561
+ stub_request(:get, 'http://datastore/places/2')
562
+ .to_return(body: {
563
+ category_relations: [{ href: 'http://datastore/category/2' }, { href: 'http://datastore/category/1' }]
564
+ }.to_json)
565
+ stub_request(:get, 'http://datastore/category/1').to_return(body: { name: 'Food' }.to_json)
566
+ stub_request(:get, 'http://datastore/category/2').to_return(body: { name: 'Drinks' }.to_json)
567
+ end
568
+
569
+ it 'includes and merges linked resources in case of an array of links' do
570
+ places = Place
571
+ .includes_first_page(:category_relations)
572
+ .find(1, 2)
573
+ expect(places[0].category_relations[0].name).to eq 'Food'
574
+ expect(places[1].category_relations[0].name).to eq 'Drinks'
575
+ end
576
+ end
577
+
578
+ context 'single href with array response' do
579
+ it 'extends base items with arrays' do
580
+ class Sector < DHS::Record
581
+ endpoint '{+datastore}/sectors'
582
+ endpoint '{+datastore}/sectors/{id}'
583
+ end
584
+
585
+ stub_request(:get, "#{datastore}/sectors")
586
+ .with(query: hash_including(key: 'my_service'))
587
+ .to_return(body: [
588
+ {
589
+ href: "#{datastore}/sectors/1",
590
+ services: {
591
+ href: "#{datastore}/sectors/1/services"
592
+ },
593
+ keys: [
594
+ {
595
+ key: 'my_service',
596
+ language: 'de'
597
+ }
598
+ ]
599
+ }
600
+ ].to_json)
601
+
602
+ stub_request(:get, "#{datastore}/sectors/1/services")
603
+ .to_return(body: [
604
+ {
605
+ href: "#{datastore}/services/s1",
606
+ price_in_cents: 9900,
607
+ key: 'my_service_service_1'
608
+ },
609
+ {
610
+ href: "#{datastore}/services/s2",
611
+ price_in_cents: 19900,
612
+ key: 'my_service_service_2'
613
+ }
614
+ ].to_json)
615
+
616
+ sector = Sector.includes_first_page(:services).find_by(key: 'my_service')
617
+ expect(sector.services.length).to eq 2
618
+ expect(sector.services.first.key).to eq 'my_service_service_1'
619
+ end
620
+ end
621
+
622
+ context 'include for POST/create' do
623
+ before do
624
+ class Record < DHS::Record
625
+ endpoint 'https://records'
626
+ end
627
+ stub_request(:post, 'https://records/')
628
+ .with(body: { color: 'blue' }.to_json)
629
+ .to_return(
630
+ body: {
631
+ color: 'blue',
632
+ alternative_categories: [
633
+ { href: 'https://categories/blue' }
634
+ ]
635
+ }.to_json
636
+ )
637
+ stub_request(:get, 'https://categories/blue')
638
+ .to_return(
639
+ body: {
640
+ name: 'blue'
641
+ }.to_json
642
+ )
643
+ end
644
+
645
+ it 'includes the resources from the post response' do
646
+ records = Record.includes_first_page(:alternative_categories).create(color: 'blue')
647
+ expect(records.alternative_categories.first.name).to eq 'blue'
648
+ end
649
+ end
650
+
651
+ context 'nested within another structure' do
652
+ before do
653
+ class Place < DHS::Record
654
+ endpoint 'https://places/{id}'
655
+ end
656
+ stub_request(:get, 'https://places/1')
657
+ .to_return(body: {
658
+ customer: {
659
+ salesforce: {
660
+ href: 'https://salesforce/customers/1'
661
+ }
662
+ }
663
+ }.to_json)
664
+ end
665
+
666
+ let!(:nested_request) do
667
+ stub_request(:get, 'https://salesforce/customers/1')
668
+ .to_return(body: {
669
+ name: 'Steve'
670
+ }.to_json)
671
+ end
672
+
673
+ it 'includes data that has been nested in an additional structure' do
674
+ place = Place.includes_first_page(customer: :salesforce).find(1)
675
+ expect(nested_request).to have_been_requested
676
+ expect(place.customer.salesforce.name).to eq 'Steve'
677
+ end
678
+
679
+ context 'included data has a configured record endpoint option' do
680
+ before do
681
+ class SalesforceCustomer < DHS::Record
682
+ endpoint 'https://salesforce/customers/{id}', headers: { 'Authorization': 'Bearer 123' }
683
+ end
684
+ end
685
+
686
+ let!(:nested_request) do
687
+ stub_request(:get, 'https://salesforce/customers/1')
688
+ .with(headers: { 'Authorization' => 'Bearer 123' })
689
+ .to_return(body: {
690
+ name: 'Steve'
691
+ }.to_json)
692
+ end
693
+
694
+ it 'includes data that has been nested in an additional structure' do
695
+ place = Place.includes_first_page(customer: :salesforce).find(1)
696
+ expect(nested_request).to have_been_requested
697
+ expect(place.customer.salesforce.name).to eq 'Steve'
698
+ end
699
+ end
700
+ end
701
+
702
+ context 'include empty structures' do
703
+ before do
704
+ class Place < DHS::Record
705
+ endpoint 'https://places/{id}'
706
+ end
707
+ stub_request(:get, 'https://places/1')
708
+ .to_return(body: {
709
+ id: '123'
710
+ }.to_json)
711
+ end
712
+
713
+ it 'skips includes when there is nothing and also does not raise an exception' do
714
+ expect(-> {
715
+ Place.includes_first_page(contracts: :product).find(1)
716
+ }).not_to raise_exception
717
+ end
718
+ end
719
+
720
+ context 'include partially empty structures' do
721
+ before do
722
+ class Place < DHS::Record
723
+ endpoint 'https://places/{id}'
724
+ end
725
+ stub_request(:get, 'https://places/1')
726
+ .to_return(body: {
727
+ id: '123',
728
+ customer: {}
729
+ }.to_json)
730
+ end
731
+
732
+ it 'skips includes when there is nothing and also does not raise an exception' do
733
+ expect(-> {
734
+ Place.includes_first_page(customer: :salesforce).find(1)
735
+ }).not_to raise_exception
736
+ end
737
+ end
738
+ end