dhs 1.0.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 (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