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,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+ context 'merge request options ' 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(body: {
14
+ place_attributes: [
15
+ { href: 'https://attributes/bar' },
16
+ { href: 'https://attributes/restaurant' },
17
+ { href: 'https://attributes/cafe' }
18
+ ]
19
+ }.to_json)
20
+
21
+ stub_request(:get, 'https://attributes/restaurant?limit=100')
22
+ .to_return(body: {}.to_json)
23
+ stub_request(:get, 'https://attributes/restaurant')
24
+ .to_return(body: {}.to_json)
25
+ stub_request(:get, 'https://attributes/bar?limit=100')
26
+ .to_return(body: {
27
+ group: {
28
+ href: 'https://group/general'
29
+ }
30
+ }.to_json)
31
+ stub_request(:get, 'https://attributes/cafe?limit=100')
32
+ .to_return(body: {
33
+ group: {
34
+ href: 'https://group/general'
35
+ }
36
+ }.to_json)
37
+ stub_request(:get, 'https://group/general?limit=100&status=active')
38
+ .to_return(body: {
39
+ name: 'General'
40
+ }.to_json)
41
+ end
42
+
43
+ context 'missing referenced options due to none existance of include' do
44
+ it 'does not raise when trying to merge options with the options block' do
45
+ DHS.options(throttle: { break: '80%' }) do
46
+ record = Record
47
+ .references(place_attributes: { group: { params: { status: 'active' } } })
48
+ .includes([{ place_attributes: :group }])
49
+ .find(1)
50
+ expect(record.place_attributes[0].group.name).to eq 'General'
51
+ expect(record.place_attributes[1].group).to eq nil
52
+ expect(record.place_attributes[2].group.name).to eq 'General'
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,690 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+ context 'includes all' do
7
+ before do
8
+ class Customer < DHS::Record
9
+ endpoint 'http://datastore/customers/{id}'
10
+ end
11
+
12
+ class User < DHS::Record
13
+ configuration pagination_strategy: 'link'
14
+ endpoint 'http://datastore/users'
15
+ end
16
+ end
17
+
18
+ let(:amount_of_contracts) { 33 }
19
+ let(:amount_of_products) { 22 }
20
+ let(:amount_of_users_1st_page) { 10 }
21
+ let(:amount_of_users_2nd_page) { 3 }
22
+ let(:amount_of_users) { amount_of_users_1st_page + amount_of_users_2nd_page }
23
+
24
+ let!(:customer_request) do
25
+ stub_request(:get, 'http://datastore/customers/1')
26
+ .to_return(
27
+ body: {
28
+ contracts: { href: 'http://datastore/customers/1/contracts' },
29
+ users: { href: 'http://datastore/users?limit=10' }
30
+ }.to_json
31
+ )
32
+ end
33
+
34
+ #
35
+ # Contracts
36
+ #
37
+
38
+ let!(:contracts_request) do
39
+ stub_request(:get, 'http://datastore/customers/1/contracts?limit=100')
40
+ .to_return(
41
+ body: {
42
+ items: 10.times.map do
43
+ {
44
+ products: { href: 'http://datastore/products' }
45
+ }
46
+ end,
47
+ limit: 10,
48
+ offset: 0,
49
+ total: amount_of_contracts
50
+ }.to_json
51
+ )
52
+ end
53
+
54
+ def additional_contracts_request(offset, amount)
55
+ stub_request(:get, "http://datastore/customers/1/contracts?limit=10&offset=#{offset}")
56
+ .to_return(
57
+ body: {
58
+ items: amount.times.map do
59
+ {
60
+ products: { href: 'http://datastore/products' }
61
+ }
62
+ end,
63
+ limit: 10,
64
+ offset: offset,
65
+ total: amount_of_contracts
66
+ }.to_json
67
+ )
68
+ end
69
+
70
+ let!(:contracts_request_page_2) do
71
+ additional_contracts_request(10, 10)
72
+ end
73
+
74
+ let!(:contracts_request_page_3) do
75
+ additional_contracts_request(20, 10)
76
+ end
77
+
78
+ let!(:contracts_request_page_4) do
79
+ additional_contracts_request(30, 3)
80
+ end
81
+
82
+ #
83
+ # Products
84
+ #
85
+
86
+ let!(:products_request) do
87
+ stub_request(:get, 'http://datastore/products?limit=100')
88
+ .to_return(
89
+ body: {
90
+ items: 10.times.map do
91
+ { name: 'LBC' }
92
+ end,
93
+ limit: 10,
94
+ offset: 0,
95
+ total: amount_of_products
96
+ }.to_json
97
+ )
98
+ end
99
+
100
+ def additional_products_request(offset, amount)
101
+ stub_request(:get, "http://datastore/products?limit=10&offset=#{offset}")
102
+ .to_return(
103
+ body: {
104
+ items: amount.times.map do
105
+ { name: 'LBC' }
106
+ end,
107
+ limit: 10,
108
+ offset: offset,
109
+ total: amount_of_products
110
+ }.to_json
111
+ )
112
+ end
113
+
114
+ let!(:products_request_page_2) do
115
+ additional_products_request(10, 10)
116
+ end
117
+
118
+ let!(:products_request_page_3) do
119
+ additional_products_request(20, 2)
120
+ end
121
+
122
+ #
123
+ # Users
124
+ #
125
+
126
+ let!(:users_request) do
127
+ stub_request(:get, 'http://datastore/users?limit=10')
128
+ .to_return(
129
+ body: {
130
+ items: amount_of_users_1st_page.times.map do
131
+ { name: 'Hans Muster' }
132
+ end,
133
+ limit: 10,
134
+ next: { href: 'http://datastore/users?for_user_id=10&limit=10' }
135
+ }.to_json
136
+ )
137
+ end
138
+
139
+ let!(:users_request_page_2) do
140
+ stub_request(:get, 'http://datastore/users?for_user_id=10&limit=10')
141
+ .to_return(
142
+ body: {
143
+ items: amount_of_users_2nd_page.times.map do
144
+ { name: 'Lisa Müller' }
145
+ end,
146
+ limit: 10,
147
+ next: { href: 'http://datastore/users?for_user_id=13&limit=10' }
148
+ }.to_json
149
+ )
150
+ end
151
+
152
+ let!(:users_request_page_3) do
153
+ stub_request(:get, 'http://datastore/users?for_user_id=13&limit=10')
154
+ .to_return(
155
+ body: {
156
+ items: [],
157
+ limit: 10
158
+ }.to_json
159
+ )
160
+ end
161
+
162
+ it 'includes all linked business objects no matter pagination' do
163
+ customer = nil
164
+
165
+ expect(lambda do
166
+ customer = Customer
167
+ .includes(:users, contracts: :products)
168
+ .find(1)
169
+ end).to output(
170
+ %r{\[WARNING\] You are loading all pages from a resource paginated with links only. As this is performed sequentially, it can result in very poor performance! \(https://github.com/DePayFi/dhs#pagination-strategy-link\).}
171
+ ).to_stderr
172
+
173
+ expect(customer.users.length).to eq amount_of_users
174
+ expect(customer.contracts.length).to eq amount_of_contracts
175
+ expect(customer.contracts.first.products.length).to eq amount_of_products
176
+ expect(customer_request).to have_been_requested.at_least_once
177
+ expect(contracts_request).to have_been_requested.at_least_once
178
+ expect(contracts_request_page_2).to have_been_requested.at_least_once
179
+ expect(contracts_request_page_3).to have_been_requested.at_least_once
180
+ expect(contracts_request_page_4).to have_been_requested.at_least_once
181
+ expect(products_request).to have_been_requested.at_least_once
182
+ expect(products_request_page_2).to have_been_requested.at_least_once
183
+ expect(products_request_page_3).to have_been_requested.at_least_once
184
+ expect(users_request).to have_been_requested.at_least_once
185
+ expect(users_request_page_2).to have_been_requested.at_least_once
186
+ expect(users_request_page_3).to have_been_requested.at_least_once
187
+ end
188
+
189
+ context 'links already contain pagination parameters' do
190
+ let!(:customer_request) do
191
+ stub_request(:get, 'http://datastore/customers/1')
192
+ .to_return(
193
+ body: {
194
+ contracts: { href: 'http://datastore/customers/1/contracts?limit=5&offset=0' }
195
+ }.to_json
196
+ )
197
+ end
198
+
199
+ let!(:contracts_request) do
200
+ stub_request(:get, 'http://datastore/customers/1/contracts?limit=100')
201
+ .to_return(
202
+ body: {
203
+ items: 10.times.map do
204
+ {
205
+ products: { href: 'http://datastore/products' }
206
+ }
207
+ end,
208
+ limit: 10,
209
+ offset: 0,
210
+ total: amount_of_contracts
211
+ }.to_json
212
+ )
213
+ end
214
+
215
+ it 'overwrites existing pagination paramters if they are already contained in a string' do
216
+ expect(DHC).to receive(:request)
217
+ .with(url: 'http://datastore/customers/1').and_call_original
218
+
219
+ expect(DHC).to receive(:request)
220
+ .with(url: 'http://datastore/customers/1/contracts',
221
+ all: true,
222
+ params: { limit: 100 }).and_call_original
223
+
224
+ expect(DHC).to receive(:request)
225
+ .with([{ url: 'http://datastore/customers/1/contracts',
226
+ all: true,
227
+ params: { limit: 10, offset: 10 } },
228
+ { url: 'http://datastore/customers/1/contracts',
229
+ all: true,
230
+ params: { limit: 10, offset: 20 } },
231
+ { url: 'http://datastore/customers/1/contracts',
232
+ all: true,
233
+ params: { limit: 10, offset: 30 } }]).and_call_original
234
+
235
+ customer = Customer
236
+ .includes(:contracts)
237
+ .find(1)
238
+ expect(customer.contracts.length).to eq amount_of_contracts
239
+ end
240
+ end
241
+
242
+ context 'includes for an empty array' do
243
+ before do
244
+ class Contract < DHS::Record
245
+ endpoint 'http://datastore/contracts/{id}'
246
+ end
247
+ stub_request(:get, %r{http://datastore/contracts/\d})
248
+ .to_return(body: {
249
+ options: nested_resources
250
+ }.to_json)
251
+ end
252
+
253
+ context 'empty array' do
254
+ let(:nested_resources) { [] }
255
+
256
+ it 'includes all in case of an empty array' do
257
+ expect(
258
+ -> { Contract.includes(:product).includes(:options).find(1) }
259
+ ).not_to raise_error
260
+ expect(
261
+ -> { Contract.includes(:product).includes(:options).find(1, 2) }
262
+ ).not_to raise_error
263
+ end
264
+ end
265
+
266
+ context 'weird array without hrefs' do
267
+ before do
268
+ stub_request(:get, 'http://datastore/options/1?limit=100')
269
+ .to_return(body: { type: 'REACH_EXT' }.to_json)
270
+ end
271
+
272
+ let(:nested_resources) { [{ href: 'http://datastore/options/1' }, { type: 'E_COMMERCE' }] }
273
+
274
+ it 'includes in case of an unexpect objects within array' do
275
+ expect(
276
+ -> { Contract.includes(:product).includes(:options).find(1) }
277
+ ).not_to raise_error
278
+ expect(
279
+ -> { Contract.includes(:product).includes(:options).find(1, 2) }
280
+ ).not_to raise_error
281
+ end
282
+ end
283
+ end
284
+
285
+ context 'include a known/identifiable record' do
286
+ before do
287
+ class Contract < DHS::Record
288
+ endpoint 'http://datastore/contracts/{id}'
289
+ end
290
+
291
+ class Entry < DHS::Record
292
+ endpoint '{+datastore}/entry/v1/{id}.json'
293
+ end
294
+
295
+ DHC.config.placeholder(:datastore, 'http://datastore')
296
+ end
297
+
298
+ let!(:customer_request) do
299
+ stub_request(:get, %r{http://datastore/customers/\d+})
300
+ .to_return(
301
+ body: {
302
+ contracts: [{ href: 'http://datastore/contracts/1' }, { href: 'http://datastore/contracts/2' }]
303
+ }.to_json
304
+ )
305
+ end
306
+
307
+ let!(:contracts_request) do
308
+ stub_request(:get, %r{http://datastore/contracts/\d+})
309
+ .to_return(
310
+ body: {
311
+ type: 'contract',
312
+ entry: { href: 'http://datastore/entry/v1/1.json' }
313
+ }.to_json
314
+ )
315
+ end
316
+
317
+ let!(:entry_request) do
318
+ stub_request(:get, %r{http://datastore/entry/v1/\d+.json})
319
+ .to_return(
320
+ body: {
321
+ name: 'Casa Ferlin'
322
+ }.to_json
323
+ )
324
+ end
325
+
326
+ it 'loads included identifiable records without raising exceptions' do
327
+ customer = Customer.includes(contracts: :entry).find(1, 2).first
328
+ expect(customer.contracts.first.href).to eq 'http://datastore/contracts/1'
329
+ expect(customer.contracts.first.type).to eq 'contract'
330
+ expect(customer.contracts.first.entry.name).to eq 'Casa Ferlin'
331
+ end
332
+ end
333
+
334
+ context 'includes all for parallel loaded ids' do
335
+ before do
336
+ class Place < DHS::Record
337
+ endpoint 'http://datastore/places/{id}'
338
+ end
339
+ end
340
+
341
+ let!(:place_request_1) do
342
+ stub_request(:get, %r{http://datastore/places/1})
343
+ .to_return(
344
+ body: {
345
+ category_relations: [
346
+ { href: 'http://datastore/category_relations/1' },
347
+ { href: 'http://datastore/category_relations/2' }
348
+ ]
349
+ }.to_json
350
+ )
351
+ end
352
+
353
+ let!(:place_request_2) do
354
+ stub_request(:get, %r{http://datastore/places/2})
355
+ .to_return(
356
+ body: {
357
+ category_relations: []
358
+ }.to_json
359
+ )
360
+ end
361
+
362
+ let!(:place_request_3) do
363
+ stub_request(:get, %r{http://datastore/places/3})
364
+ .to_return(
365
+ body: {
366
+ category_relations: [
367
+ { href: 'http://datastore/category_relations/1' },
368
+ { href: 'http://datastore/category_relations/3' }
369
+ ]
370
+ }.to_json
371
+ )
372
+ end
373
+
374
+ let!(:category_relation_request_1) do
375
+ stub_request(:get, %r{http://datastore/category_relations/1})
376
+ .to_return(
377
+ body: {
378
+ name: 'Category 1'
379
+ }.to_json
380
+ )
381
+ end
382
+
383
+ let!(:category_relation_request_2) do
384
+ stub_request(:get, %r{http://datastore/category_relations/2})
385
+ .to_return(
386
+ body: {
387
+ name: 'Category 2'
388
+ }.to_json
389
+ )
390
+ end
391
+
392
+ let!(:category_relation_request_3) do
393
+ stub_request(:get, %r{http://datastore/category_relations/3})
394
+ .to_return(
395
+ body: {
396
+ name: 'Category 3'
397
+ }.to_json
398
+ )
399
+ end
400
+
401
+ let(:category_name) { 'Category Relation' }
402
+
403
+ it 'requests places in parallel and includes category relation' do
404
+ places = Place.includes(:category_relations).find(1, 2, 3)
405
+ expect(places[0].category_relations[0].name).to eq 'Category 1'
406
+ expect(places[0].category_relations[1].name).to eq 'Category 2'
407
+ expect(places[2].category_relations[0].name).to eq 'Category 1'
408
+ expect(places[2].category_relations[1].name).to eq 'Category 3'
409
+ end
410
+ end
411
+ end
412
+
413
+ context 'Linked resources' do
414
+ before do
415
+ stub_request(:get, 'http://datastore/places/1/contracts?offset=0&limit=10')
416
+ .to_return(
417
+ body: {
418
+ href: 'http://datastore/v2/places/1/contracts?offset=0&limit=10',
419
+ items: [{ href: 'http://datastore/v2/contracts/1' }],
420
+ offset: 0,
421
+ limit: 10,
422
+ total: 10
423
+ }.to_json
424
+ )
425
+
426
+ stub_request(:get, 'http://datastore/v2/contracts/1')
427
+ .to_return(
428
+ body: {
429
+ customer: { name: 'Swisscom Directories AG' }
430
+ }.to_json
431
+ )
432
+
433
+ stub_request(:get, 'http://datastore/places/1?limit=1')
434
+ .to_return(
435
+ body: { href: 'http://datastore/places/1', contracts: { href: 'http://datastore/places/1/contracts?offset=0&limit=10' } }.to_json
436
+ )
437
+
438
+ class Place < DHS::Record
439
+ endpoint 'http://datastore/places/{id}'
440
+ end
441
+
442
+ class Contract < DHS::Record
443
+ endpoint 'http://datastore/places/{place_id}/contracts'
444
+ end
445
+ end
446
+
447
+ it 'does not use the root record endpoints when including nested records' do
448
+ place = Place
449
+ .includes(:contracts)
450
+ .find_by(id: 1)
451
+ expect(place.contracts.first.customer.name).to eq 'Swisscom Directories AG'
452
+ end
453
+ end
454
+
455
+ context 'nested includes all' do
456
+ context 'with optional children' do
457
+ let(:favorites_request_stub) do
458
+ stub_request(:get, %r{http://datastore/favorites})
459
+ .to_return(
460
+ body: {
461
+ items: [{
462
+ href: 'http://datastore/favorites/1',
463
+ place: {
464
+ href: 'http://datastore/places/1'
465
+ }
466
+ }, {
467
+ href: 'http://datastore/favorite/2',
468
+ place: {
469
+ href: 'http://datastore/places/2'
470
+ }
471
+ }, {
472
+ href: 'http://datastore/favorite/3',
473
+ place: {
474
+ href: 'http://datastore/places/3'
475
+ }
476
+ }],
477
+ total: 3,
478
+ offset: 0,
479
+ limit: 100
480
+ }.to_json
481
+ )
482
+ end
483
+
484
+ let(:place1_request_stub) do
485
+ stub_request(:get, %r{http://datastore/places/1})
486
+ .to_return(
487
+ body: {
488
+ href: 'http://datastore/places/1',
489
+ name: 'Place 1',
490
+ contracts: {
491
+ href: 'http://datastore/places/1/contracts'
492
+ }
493
+ }.to_json
494
+ )
495
+ end
496
+
497
+ let(:place2_request_stub) do
498
+ stub_request(:get, %r{http://datastore/places/2})
499
+ .to_return(
500
+ body: {
501
+ href: 'http://datastore/places/2',
502
+ name: 'Place 2'
503
+ }.to_json
504
+ )
505
+ end
506
+
507
+ let(:place3_request_stub) do
508
+ stub_request(:get, %r{http://datastore/places/3})
509
+ .to_return(
510
+ body: {
511
+ href: 'http://datastore/places/3',
512
+ name: 'Place 3',
513
+ contracts: {
514
+ href: 'http://datastore/places/3/contracts'
515
+ }
516
+ }.to_json
517
+ )
518
+ end
519
+
520
+ let(:contracts_request_for_place1_stub) do
521
+ stub_request(:get, %r{http://datastore/places/1/contracts})
522
+ .to_return(
523
+ body: {
524
+ items: [{
525
+ href: 'http://datastore/places/1/contracts/1',
526
+ name: 'Contract 1'
527
+ }],
528
+ total: 1,
529
+ offset: 0,
530
+ limit: 10
531
+ }.to_json
532
+ )
533
+ end
534
+
535
+ let(:contracts_request_for_place3_stub) do
536
+ stub_request(:get, %r{http://datastore/places/3/contracts})
537
+ .to_return(
538
+ body: {
539
+ items: [{
540
+ href: 'http://datastore/places/3/contracts/1',
541
+ name: 'Contract 3'
542
+ }],
543
+ total: 1,
544
+ offset: 0,
545
+ limit: 10
546
+ }.to_json
547
+ )
548
+ end
549
+
550
+ before do
551
+ class Favorite < DHS::Record
552
+ endpoint 'http://datastore/favorites'
553
+ end
554
+
555
+ class Place < DHS::Record
556
+ endpoint 'http://datastore/places/{id}'
557
+ end
558
+
559
+ class Contract < DHS::Record
560
+ endpoint 'http://datastore/places/{place_id}/contracts'
561
+ end
562
+
563
+ favorites_request_stub
564
+ place1_request_stub
565
+ place2_request_stub
566
+ place3_request_stub
567
+ contracts_request_for_place1_stub
568
+ contracts_request_for_place3_stub
569
+ end
570
+
571
+ it 'includes nested objects when they exist' do
572
+ favorites = Favorite.includes(:place).includes(place: :contracts).all
573
+
574
+ expect(favorites.first.place.name).to eq('Place 1')
575
+ expect(favorites.first.place.contracts.first.name).to eq('Contract 1')
576
+ end
577
+
578
+ it 'does not include nested objects when they are not there' do
579
+ favorites = Favorite.includes(:place).includes(place: :contracts).all
580
+
581
+ expect(favorites[1].place.name).to eq('Place 2')
582
+ expect(favorites[1].place.contracts).to be(nil)
583
+ end
584
+
585
+ it 'does include and merges objects after nil objects in collections' do
586
+ favorites = Favorite.includes(:place).includes(place: :contracts).all
587
+
588
+ expect(favorites.last.place.name).to eq('Place 3')
589
+ expect(favorites.last.place.contracts.first.name).to eq('Contract 3')
590
+ end
591
+ end
592
+ end
593
+
594
+ context 'includes collection trough single item' do
595
+ before do
596
+ class Place < DHS::Record
597
+ endpoint 'https://places/{id}'
598
+ end
599
+
600
+ stub_request(:get, 'https://places/1')
601
+ .to_return(
602
+ body: {
603
+ customer: { href: 'https://customers/1' }
604
+ }.to_json
605
+ )
606
+
607
+ stub_request(:get, 'https://customers/1?limit=100')
608
+ .to_return(
609
+ body: {
610
+ addresses: { 'href': 'https://customer/1/addresses' }
611
+ }.to_json
612
+ )
613
+
614
+ stub_request(:get, 'https://customer/1/addresses?limit=100')
615
+ .to_return(
616
+ body: {
617
+ items: [
618
+ { city: 'Zurich', no: 1 },
619
+ { city: 'Zurich', no: 2 }
620
+ ],
621
+ total: 2
622
+ }.to_json
623
+ )
624
+ end
625
+
626
+ it 'includes a collection trough a single item without exceptions' do
627
+ place = Place
628
+ .includes(customer: :addresses)
629
+ .find(1)
630
+ expect(place.customer.addresses.map(&:no)).to eq [1, 2]
631
+ end
632
+ end
633
+
634
+ context 'does not fail including all linked resources' do
635
+ before do
636
+ class CustomerOnboardingToken < DHS::Record
637
+ endpoint 'https://token/{id}'
638
+ end
639
+
640
+ class Place < DHS::Record
641
+ endpoint 'https://places/{id}'
642
+ end
643
+
644
+ class AvailableAsset < DHS::Record
645
+ endpoint 'https://assets'
646
+ end
647
+
648
+ stub_request(:get, 'https://token/1')
649
+ .to_return(
650
+ body: {
651
+ places: [{ href: 'https://places/1' }]
652
+ }.to_json
653
+ )
654
+
655
+ stub_request(:get, 'https://places/1?limit=100')
656
+ .to_return(
657
+ body: {
658
+ available_assets: { 'href': 'https://assets?limit=10&offset=0' }
659
+ }.to_json
660
+ )
661
+
662
+ stub_request(:get, 'https://assets?limit=10&offset=0')
663
+ .to_return(
664
+ body: {
665
+ items: 10.times.map { { asset_code: 'CATEGORIES' } },
666
+ total: 17,
667
+ offset: 0,
668
+ limit: 10
669
+ }.to_json
670
+ )
671
+
672
+ stub_request(:get, 'https://assets?limit=10&offset=10')
673
+ .to_return(
674
+ body: {
675
+ items: 7.times.map { { asset_code: 'CATEGORIES' } },
676
+ total: 17,
677
+ offset: 0,
678
+ limit: 10
679
+ }.to_json
680
+ )
681
+ end
682
+
683
+ it 'includes a collection trough a single item without exceptions' do
684
+ token = CustomerOnboardingToken
685
+ .includes(places: :available_assets)
686
+ .find(1)
687
+ expect(token.places.first.available_assets.length).to eq 17
688
+ end
689
+ end
690
+ end