lhs 24.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 (306) hide show
  1. checksums.yaml +7 -0
  2. data/.bundler-version +1 -0
  3. data/.gitignore +39 -0
  4. data/.rubocop.localch.yml +325 -0
  5. data/.rubocop.yml +52 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.activesupport4 +5 -0
  9. data/Gemfile.activesupport5 +4 -0
  10. data/LICENSE +674 -0
  11. data/README.md +2836 -0
  12. data/Rakefile +25 -0
  13. data/cider-ci.yml +6 -0
  14. data/cider-ci/bin/bundle +51 -0
  15. data/cider-ci/bin/ruby_install +8 -0
  16. data/cider-ci/bin/ruby_version +25 -0
  17. data/cider-ci/jobs/rspec-activesupport-4.yml +27 -0
  18. data/cider-ci/jobs/rspec-activesupport-5.yml +26 -0
  19. data/cider-ci/jobs/rspec-activesupport-latest.yml +24 -0
  20. data/cider-ci/jobs/rubocop.yml +18 -0
  21. data/cider-ci/task_components/bundle.yml +22 -0
  22. data/cider-ci/task_components/rspec.yml +37 -0
  23. data/cider-ci/task_components/rubocop.yml +29 -0
  24. data/cider-ci/task_components/ruby.yml +15 -0
  25. data/friday.yml +2 -0
  26. data/lhs.gemspec +43 -0
  27. data/lib/lhs.rb +100 -0
  28. data/lib/lhs/collection.rb +84 -0
  29. data/lib/lhs/complex.rb +158 -0
  30. data/lib/lhs/concerns/autoload_records.rb +55 -0
  31. data/lib/lhs/concerns/collection/handle_nested.rb +43 -0
  32. data/lib/lhs/concerns/collection/internal_collection.rb +49 -0
  33. data/lib/lhs/concerns/configuration.rb +20 -0
  34. data/lib/lhs/concerns/data/becomes.rb +18 -0
  35. data/lib/lhs/concerns/data/equality.rb +14 -0
  36. data/lib/lhs/concerns/data/json.rb +14 -0
  37. data/lib/lhs/concerns/data/to_hash.rb +14 -0
  38. data/lib/lhs/concerns/inspect.rb +70 -0
  39. data/lib/lhs/concerns/is_href.rb +15 -0
  40. data/lib/lhs/concerns/item/destroy.rb +38 -0
  41. data/lib/lhs/concerns/item/endpoint_lookup.rb +27 -0
  42. data/lib/lhs/concerns/item/save.rb +55 -0
  43. data/lib/lhs/concerns/item/update.rb +50 -0
  44. data/lib/lhs/concerns/item/validation.rb +61 -0
  45. data/lib/lhs/concerns/o_auth.rb +25 -0
  46. data/lib/lhs/concerns/option_blocks.rb +26 -0
  47. data/lib/lhs/concerns/proxy/accessors.rb +132 -0
  48. data/lib/lhs/concerns/proxy/create.rb +45 -0
  49. data/lib/lhs/concerns/proxy/link.rb +25 -0
  50. data/lib/lhs/concerns/proxy/problems.rb +27 -0
  51. data/lib/lhs/concerns/record/attribute_assignment.rb +25 -0
  52. data/lib/lhs/concerns/record/batch.rb +40 -0
  53. data/lib/lhs/concerns/record/chainable.rb +465 -0
  54. data/lib/lhs/concerns/record/configuration.rb +103 -0
  55. data/lib/lhs/concerns/record/create.rb +24 -0
  56. data/lib/lhs/concerns/record/custom_setters.rb +22 -0
  57. data/lib/lhs/concerns/record/destroy.rb +18 -0
  58. data/lib/lhs/concerns/record/endpoints.rb +108 -0
  59. data/lib/lhs/concerns/record/equality.rb +14 -0
  60. data/lib/lhs/concerns/record/find.rb +86 -0
  61. data/lib/lhs/concerns/record/find_by.rb +38 -0
  62. data/lib/lhs/concerns/record/first.rb +20 -0
  63. data/lib/lhs/concerns/record/href_for.rb +19 -0
  64. data/lib/lhs/concerns/record/last.rb +27 -0
  65. data/lib/lhs/concerns/record/mapping.rb +25 -0
  66. data/lib/lhs/concerns/record/merge.rb +26 -0
  67. data/lib/lhs/concerns/record/model.rb +23 -0
  68. data/lib/lhs/concerns/record/pagination.rb +47 -0
  69. data/lib/lhs/concerns/record/provider.rb +23 -0
  70. data/lib/lhs/concerns/record/relations.rb +26 -0
  71. data/lib/lhs/concerns/record/request.rb +620 -0
  72. data/lib/lhs/concerns/record/scope.rb +25 -0
  73. data/lib/lhs/concerns/record/tracing.rb +24 -0
  74. data/lib/lhs/concerns/record/update.rb +17 -0
  75. data/lib/lhs/config.rb +24 -0
  76. data/lib/lhs/data.rb +165 -0
  77. data/lib/lhs/endpoint.rb +12 -0
  78. data/lib/lhs/interceptors/auto_oauth/interceptor.rb +33 -0
  79. data/lib/lhs/interceptors/auto_oauth/thread_registry.rb +18 -0
  80. data/lib/lhs/interceptors/extended_rollbar/handler.rb +40 -0
  81. data/lib/lhs/interceptors/extended_rollbar/interceptor.rb +20 -0
  82. data/lib/lhs/interceptors/extended_rollbar/thread_registry.rb +19 -0
  83. data/lib/lhs/interceptors/request_cycle_cache/interceptor.rb +41 -0
  84. data/lib/lhs/interceptors/request_cycle_cache/thread_registry.rb +18 -0
  85. data/lib/lhs/item.rb +59 -0
  86. data/lib/lhs/pagination/base.rb +90 -0
  87. data/lib/lhs/pagination/link.rb +21 -0
  88. data/lib/lhs/pagination/offset.rb +22 -0
  89. data/lib/lhs/pagination/page.rb +18 -0
  90. data/lib/lhs/pagination/start.rb +22 -0
  91. data/lib/lhs/problems/base.rb +113 -0
  92. data/lib/lhs/problems/errors.rb +69 -0
  93. data/lib/lhs/problems/nested/base.rb +54 -0
  94. data/lib/lhs/problems/nested/errors.rb +16 -0
  95. data/lib/lhs/problems/nested/warnings.rb +15 -0
  96. data/lib/lhs/problems/warnings.rb +24 -0
  97. data/lib/lhs/proxy.rb +68 -0
  98. data/lib/lhs/railtie.rb +34 -0
  99. data/lib/lhs/record.rb +112 -0
  100. data/lib/lhs/rspec.rb +10 -0
  101. data/lib/lhs/test/stubbable_records.rb +34 -0
  102. data/lib/lhs/unprocessable.rb +6 -0
  103. data/lib/lhs/version.rb +5 -0
  104. data/script/ci/build.sh +18 -0
  105. data/spec/auto_oauth_spec.rb +169 -0
  106. data/spec/autoloading_spec.rb +48 -0
  107. data/spec/collection/accessors_spec.rb +31 -0
  108. data/spec/collection/collection_items_spec.rb +44 -0
  109. data/spec/collection/configurable_spec.rb +43 -0
  110. data/spec/collection/delegate_spec.rb +21 -0
  111. data/spec/collection/enumerable_spec.rb +27 -0
  112. data/spec/collection/href_spec.rb +17 -0
  113. data/spec/collection/meta_data_spec.rb +57 -0
  114. data/spec/collection/respond_to_spec.rb +20 -0
  115. data/spec/collection/to_a_spec.rb +34 -0
  116. data/spec/collection/to_ary_spec.rb +40 -0
  117. data/spec/collection/without_object_items_spec.rb +27 -0
  118. data/spec/complex/reduce_spec.rb +202 -0
  119. data/spec/concerns/record/request_spec.rb +78 -0
  120. data/spec/data/collection_spec.rb +56 -0
  121. data/spec/data/equality_spec.rb +23 -0
  122. data/spec/data/inspect_spec.rb +88 -0
  123. data/spec/data/is_item_or_collection_spec.rb +40 -0
  124. data/spec/data/item_spec.rb +106 -0
  125. data/spec/data/merge_spec.rb +27 -0
  126. data/spec/data/parent_spec.rb +39 -0
  127. data/spec/data/raw_spec.rb +48 -0
  128. data/spec/data/respond_to_spec.rb +26 -0
  129. data/spec/data/root_spec.rb +25 -0
  130. data/spec/data/select_spec.rb +27 -0
  131. data/spec/data/to_ary_spec.rb +28 -0
  132. data/spec/data/to_json_spec.rb +68 -0
  133. data/spec/dummy/Rakefile +8 -0
  134. data/spec/dummy/app/assets/images/.keep +0 -0
  135. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  136. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  137. data/spec/dummy/app/controllers/application_controller.rb +26 -0
  138. data/spec/dummy/app/controllers/automatic_authentication_controller.rb +29 -0
  139. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  140. data/spec/dummy/app/controllers/error_handling_with_chains_controller.rb +36 -0
  141. data/spec/dummy/app/controllers/extended_rollbar_controller.rb +10 -0
  142. data/spec/dummy/app/controllers/option_blocks_controller.rb +15 -0
  143. data/spec/dummy/app/controllers/request_cycle_cache_controller.rb +27 -0
  144. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  145. data/spec/dummy/app/mailers/.keep +0 -0
  146. data/spec/dummy/app/models/.keep +0 -0
  147. data/spec/dummy/app/models/concerns/.keep +0 -0
  148. data/spec/dummy/app/models/dummy_customer.rb +6 -0
  149. data/spec/dummy/app/models/dummy_record.rb +6 -0
  150. data/spec/dummy/app/models/dummy_record_with_auto_oauth_provider.rb +6 -0
  151. data/spec/dummy/app/models/dummy_record_with_multiple_oauth_providers1.rb +7 -0
  152. data/spec/dummy/app/models/dummy_record_with_multiple_oauth_providers2.rb +7 -0
  153. data/spec/dummy/app/models/dummy_record_with_multiple_providers_per_endpoint.rb +6 -0
  154. data/spec/dummy/app/models/dummy_record_with_oauth.rb +7 -0
  155. data/spec/dummy/app/models/dummy_user.rb +6 -0
  156. data/spec/dummy/app/models/providers/customer_system.rb +7 -0
  157. data/spec/dummy/app/models/providers/internal_services.rb +7 -0
  158. data/spec/dummy/app/views/error_handling_with_chains/error.html.erb +1 -0
  159. data/spec/dummy/app/views/error_handling_with_chains/show.html.erb +3 -0
  160. data/spec/dummy/app/views/form_for.html.erb +5 -0
  161. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  162. data/spec/dummy/bin/bundle +5 -0
  163. data/spec/dummy/bin/rails +6 -0
  164. data/spec/dummy/bin/rake +6 -0
  165. data/spec/dummy/config.ru +6 -0
  166. data/spec/dummy/config/application.rb +16 -0
  167. data/spec/dummy/config/boot.rb +7 -0
  168. data/spec/dummy/config/environment.rb +7 -0
  169. data/spec/dummy/config/environments/development.rb +36 -0
  170. data/spec/dummy/config/environments/production.rb +77 -0
  171. data/spec/dummy/config/environments/test.rb +40 -0
  172. data/spec/dummy/config/initializers/assets.rb +10 -0
  173. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  174. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  175. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  176. data/spec/dummy/config/initializers/inflections.rb +18 -0
  177. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  178. data/spec/dummy/config/initializers/rollbar.rb +9 -0
  179. data/spec/dummy/config/initializers/session_store.rb +5 -0
  180. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  181. data/spec/dummy/config/locales/en.yml +23 -0
  182. data/spec/dummy/config/routes.rb +27 -0
  183. data/spec/dummy/config/secrets.yml +22 -0
  184. data/spec/dummy/lib/assets/.keep +0 -0
  185. data/spec/dummy/public/404.html +67 -0
  186. data/spec/dummy/public/422.html +67 -0
  187. data/spec/dummy/public/500.html +66 -0
  188. data/spec/dummy/public/favicon.ico +0 -0
  189. data/spec/endpoint/for_url_spec.rb +27 -0
  190. data/spec/extended_rollbar_spec.rb +67 -0
  191. data/spec/item/access_errors_spec.rb +33 -0
  192. data/spec/item/accessors_spec.rb +21 -0
  193. data/spec/item/add_error_spec.rb +21 -0
  194. data/spec/item/becomes_spec.rb +38 -0
  195. data/spec/item/blacklisted_keywords_spec.rb +30 -0
  196. data/spec/item/delegate_spec.rb +32 -0
  197. data/spec/item/destroy_spec.rb +113 -0
  198. data/spec/item/dig_spec.rb +29 -0
  199. data/spec/item/error_codes_spec.rb +55 -0
  200. data/spec/item/errors_spec.rb +324 -0
  201. data/spec/item/fetch_spec.rb +39 -0
  202. data/spec/item/getter_spec.rb +24 -0
  203. data/spec/item/internal_data_structure_spec.rb +37 -0
  204. data/spec/item/map_spec.rb +46 -0
  205. data/spec/item/nested_errors_spec.rb +28 -0
  206. data/spec/item/partial_update_spec.rb +170 -0
  207. data/spec/item/respond_to_spec.rb +31 -0
  208. data/spec/item/save_spec.rb +115 -0
  209. data/spec/item/setter_spec.rb +44 -0
  210. data/spec/item/translate_errors_spec.rb +257 -0
  211. data/spec/item/update_spec.rb +161 -0
  212. data/spec/item/validation_spec.rb +131 -0
  213. data/spec/item/warning_codes_spec.rb +55 -0
  214. data/spec/item/warnings_spec.rb +52 -0
  215. data/spec/option_blocks/ensure_reset_between_requests_spec.rb +23 -0
  216. data/spec/option_blocks/main_spec.rb +55 -0
  217. data/spec/pagination/link/current_page_spec.rb +19 -0
  218. data/spec/pagination/link/pages_left_spec.rb +36 -0
  219. data/spec/pagination/link/parallel_spec.rb +19 -0
  220. data/spec/pagination/link/total_spec.rb +46 -0
  221. data/spec/pagination/offset/pages_left_spec.rb +26 -0
  222. data/spec/pagination/parameters_spec.rb +61 -0
  223. data/spec/proxy/create_sub_resource_spec.rb +182 -0
  224. data/spec/proxy/load_spec.rb +75 -0
  225. data/spec/proxy/record_identification_spec.rb +35 -0
  226. data/spec/rails_helper.rb +13 -0
  227. data/spec/record/all_spec.rb +133 -0
  228. data/spec/record/attribute_assignment_spec.rb +28 -0
  229. data/spec/record/build_spec.rb +26 -0
  230. data/spec/record/cast_nested_data_spec.rb +78 -0
  231. data/spec/record/create_spec.rb +160 -0
  232. data/spec/record/creation_failed_spec.rb +55 -0
  233. data/spec/record/custom_setters_spec.rb +45 -0
  234. data/spec/record/definitions_spec.rb +29 -0
  235. data/spec/record/destroy_spec.rb +38 -0
  236. data/spec/record/dig_configuration_spec.rb +75 -0
  237. data/spec/record/dup_spec.rb +20 -0
  238. data/spec/record/endpoint_inheritance_spec.rb +65 -0
  239. data/spec/record/endpoint_options_spec.rb +52 -0
  240. data/spec/record/endpoint_priorities_spec.rb +26 -0
  241. data/spec/record/endpoints_spec.rb +96 -0
  242. data/spec/record/equality_spec.rb +27 -0
  243. data/spec/record/error_handling_integration_spec.rb +25 -0
  244. data/spec/record/error_handling_spec.rb +40 -0
  245. data/spec/record/expanded_spec.rb +69 -0
  246. data/spec/record/fetch_spec.rb +41 -0
  247. data/spec/record/find_by_chains_spec.rb +21 -0
  248. data/spec/record/find_by_spec.rb +76 -0
  249. data/spec/record/find_each_spec.rb +57 -0
  250. data/spec/record/find_in_batches_spec.rb +122 -0
  251. data/spec/record/find_in_parallel_spec.rb +67 -0
  252. data/spec/record/find_spec.rb +104 -0
  253. data/spec/record/first_spec.rb +39 -0
  254. data/spec/record/force_merge_spec.rb +56 -0
  255. data/spec/record/handle_includes_errors_spec.rb +32 -0
  256. data/spec/record/has_many_spec.rb +120 -0
  257. data/spec/record/has_one_spec.rb +116 -0
  258. data/spec/record/href_for_spec.rb +25 -0
  259. data/spec/record/ignore_errors_spec.rb +139 -0
  260. data/spec/record/immutable_chains_spec.rb +22 -0
  261. data/spec/record/includes_first_page_spec.rb +737 -0
  262. data/spec/record/includes_spec.rb +693 -0
  263. data/spec/record/includes_warning_spec.rb +46 -0
  264. data/spec/record/item_key_spec.rb +81 -0
  265. data/spec/record/items_created_key_configuration_spec.rb +37 -0
  266. data/spec/record/last_spec.rb +68 -0
  267. data/spec/record/loading_twice_spec.rb +19 -0
  268. data/spec/record/mapping_spec.rb +102 -0
  269. data/spec/record/model_name_spec.rb +17 -0
  270. data/spec/record/new_spec.rb +106 -0
  271. data/spec/record/options_getter_spec.rb +26 -0
  272. data/spec/record/options_spec.rb +164 -0
  273. data/spec/record/paginatable_collection_spec.rb +360 -0
  274. data/spec/record/pagination_chain_spec.rb +101 -0
  275. data/spec/record/pagination_links_spec.rb +72 -0
  276. data/spec/record/pagination_spec.rb +71 -0
  277. data/spec/record/persisted_spec.rb +52 -0
  278. data/spec/record/provider_spec.rb +41 -0
  279. data/spec/record/references_spec.rb +53 -0
  280. data/spec/record/relation_caching_spec.rb +121 -0
  281. data/spec/record/reload_by_id_spec.rb +43 -0
  282. data/spec/record/reload_spec.rb +65 -0
  283. data/spec/record/request_spec.rb +90 -0
  284. data/spec/record/save_spec.rb +40 -0
  285. data/spec/record/scope_chains_spec.rb +39 -0
  286. data/spec/record/select_spec.rb +17 -0
  287. data/spec/record/to_ary_spec.rb +65 -0
  288. data/spec/record/to_hash_spec.rb +22 -0
  289. data/spec/record/to_json_spec.rb +22 -0
  290. data/spec/record/tracing_spec.rb +154 -0
  291. data/spec/record/update_spec.rb +62 -0
  292. data/spec/record/where_chains_spec.rb +57 -0
  293. data/spec/record/where_spec.rb +62 -0
  294. data/spec/record/where_values_hash_spec.rb +32 -0
  295. data/spec/request_cycle_cache_spec.rb +106 -0
  296. data/spec/require_lhs_spec.rb +9 -0
  297. data/spec/spec_helper.rb +6 -0
  298. data/spec/stubs/all_spec.rb +72 -0
  299. data/spec/support/fixtures/json/feedback.json +11 -0
  300. data/spec/support/fixtures/json/feedbacks.json +174 -0
  301. data/spec/support/fixtures/json/localina_content_ad.json +23 -0
  302. data/spec/support/load_json.rb +5 -0
  303. data/spec/support/request_cycle_cache.rb +10 -0
  304. data/spec/support/reset.rb +67 -0
  305. data/spec/views/form_for_spec.rb +20 -0
  306. metadata +776 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Record
6
+
7
+ module Last
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ def last(options = nil)
12
+ options = trace!(options)
13
+ first_batch = find_by({}, options).parent
14
+ if first_batch.paginated?
15
+ pagination = first_batch._pagination
16
+ find_by({ pagination_key => pagination.class.page_to_offset(pagination.last_page, pagination.limit) }, options)
17
+ else
18
+ first_batch.last
19
+ end
20
+ end
21
+
22
+ def last!(options = nil)
23
+ find_by!({}, trace!(options))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Record
6
+
7
+ # Mapping allows to configure some accessors that access data using a provided proc
8
+ module Mapping
9
+ extend ActiveSupport::Concern
10
+
11
+ module ClassMethods
12
+ def mapping
13
+ @mapping ||= {}
14
+ end
15
+
16
+ def mapping=(mapping)
17
+ @mapping = mapping
18
+ end
19
+
20
+ def map(name, block)
21
+ mapping[name] = block
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Record
6
+
7
+ module Merge
8
+ extend ActiveSupport::Concern
9
+
10
+ def merge(other)
11
+ _record.new(_data.to_h.merge(other.to_h))
12
+ end
13
+
14
+ def merge!(other)
15
+ _data._raw.merge!(other.to_h)
16
+ end
17
+
18
+ def deep_merge(other)
19
+ _record.new(_data.to_h.deep_merge(other.to_h))
20
+ end
21
+
22
+ def deep_merge!(other)
23
+ _data._raw.deep_merge!(other.to_h)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_model'
5
+
6
+ class LHS::Record
7
+
8
+ module Model
9
+ extend ActiveSupport::Concern
10
+
11
+ def to_model
12
+ self
13
+ end
14
+
15
+ def persisted?
16
+ href.present?
17
+ end
18
+
19
+ included do
20
+ extend ActiveModel::Naming
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Record
6
+
7
+ module Pagination
8
+ extend ActiveSupport::Concern
9
+ # Kaminari-Interface
10
+ delegate :current_page, :first_page, :last_page, :prev_page, :next_page, :limit_value, :total_pages, to: :_pagination
11
+
12
+ def paginated?(raw = nil)
13
+ self.class.paginated?(raw || _raw)
14
+ end
15
+
16
+ def _pagination
17
+ self.class.pagination(_data)
18
+ end
19
+
20
+ module ClassMethods
21
+ def pagination_class
22
+ case pagination_strategy.to_sym
23
+ when :page
24
+ LHS::Pagination::Page
25
+ when :start
26
+ LHS::Pagination::Start
27
+ when :link
28
+ LHS::Pagination::Link
29
+ else
30
+ LHS::Pagination::Offset
31
+ end
32
+ end
33
+
34
+ def pagination(data)
35
+ pagination_class.new(data)
36
+ end
37
+
38
+ # Checks if given raw is paginated or not
39
+ def paginated?(raw)
40
+ raw.is_a?(Hash) && (
41
+ raw.dig(*total_key).present? ||
42
+ raw.dig(*limit_key(:body)).present?
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+
6
+ class LHS::Record
7
+
8
+ # A provider can define options used for that specific provider
9
+ module Provider
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ class_attribute :provider_options unless defined? provider_options
14
+ self.provider_options = nil
15
+ end
16
+
17
+ module ClassMethods
18
+ def provider(options = nil)
19
+ self.provider_options = options
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/class/attribute'
5
+
6
+ class LHS::Record
7
+
8
+ module Relations
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ class_attribute :_relations
13
+ self._relations = {}
14
+ end
15
+
16
+ module ClassMethods
17
+ def has_many(*options)
18
+ name = options[0]
19
+ options = options[1] || {}
20
+ _relations[name] = { record_class_name: options.fetch(:class_name, name.to_s.singularize.classify) }
21
+ end
22
+
23
+ alias has_one has_many
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,620 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/object'
5
+
6
+ class LHS::Record
7
+
8
+ module Request
9
+ extend ActiveSupport::Concern
10
+
11
+ module ClassMethods
12
+ def request(options)
13
+ options ||= {}
14
+ options = deep_merge_with_option_blocks(options)
15
+ options = options.freeze
16
+ if options.is_a?(Array)
17
+ multiple_requests(
18
+ filter_empty_request_options(options)
19
+ )
20
+ else
21
+ single_request(options)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def deep_merge_with_option_blocks(options)
28
+ return options if LHS::OptionBlocks::CurrentOptionBlock.options.blank?
29
+ if options.is_a?(Hash)
30
+ options.deep_merge(LHS::OptionBlocks::CurrentOptionBlock.options)
31
+ elsif options.is_a?(Array)
32
+ options.map { |option| option.deep_merge(LHS::OptionBlocks::CurrentOptionBlock.options) }
33
+ end
34
+ end
35
+
36
+ def single_request_load_and_merge_remaining_objects!(data, options, endpoint)
37
+ return if options[:all].blank? || !paginated
38
+ load_and_merge_remaining_objects!(
39
+ data: data,
40
+ options: process_options(options, endpoint),
41
+ load_not_paginated_collection: true
42
+ )
43
+ end
44
+
45
+ def filter_empty_request_options(options)
46
+ options.map do |option|
47
+ option if !option || !option.key?(:url) || !option[:url].nil?
48
+ end
49
+ end
50
+
51
+ # Applies limit to the first request of an all request chain
52
+ # Tries to apply an high value for limit and reacts on the limit
53
+ # returned by the endpoint to make further requests
54
+ def apply_limit!(options)
55
+ return if !paginated || options[:all].blank?
56
+ options[:params] ||= {}
57
+ options[:params] = options[:params].merge(limit_key(:parameter) => options[:params][limit_key(:parameter)] || LHS::Pagination::Base::DEFAULT_LIMIT)
58
+ end
59
+
60
+ # Convert URLs in options to endpoint templates
61
+ def convert_options_to_endpoints(options)
62
+ if options.is_a?(Array)
63
+ options.map { |request_options| convert_options_to_endpoint(request_options) }
64
+ else
65
+ convert_options_to_endpoint(options)
66
+ end
67
+ end
68
+
69
+ def convert_options_to_endpoint(options)
70
+ return if options.blank?
71
+ url = options[:url]
72
+ endpoint = LHS::Endpoint.for_url(url)
73
+ return unless endpoint
74
+ template = endpoint.url
75
+ new_options = options.deep_merge(
76
+ params: LHC::Endpoint.values_as_params(template, url).merge(values_from_get_params(url, options))
77
+ )
78
+ new_options[:url] = template
79
+ new_options
80
+ end
81
+
82
+ # Extracts values from url's get parameters
83
+ # and return them as a ruby hash
84
+ def values_from_get_params(url, options)
85
+ uri = parse_uri(url, options)
86
+ return {} if uri.query.blank?
87
+ params = Rack::Utils.parse_nested_query(uri.query).deep_symbolize_keys
88
+ params
89
+ end
90
+
91
+ def parse_uri(url, options)
92
+ URI.parse(
93
+ if url.match(Addressable::Template::EXPRESSION)
94
+ compute_url(options[:params], url)
95
+ else
96
+ url
97
+ end
98
+ )
99
+ end
100
+
101
+ # Extends existing raw data with additionaly fetched data
102
+ def extend_raw_data!(data, addition, key)
103
+ if data.collection?
104
+ extend_base_collection!(data, addition, key)
105
+ elsif data[key]._raw.is_a? Array
106
+ extend_base_array!(data, addition, key)
107
+ elsif data.item?
108
+ extend_base_item!(data, addition, key)
109
+ end
110
+ end
111
+
112
+ def extend_base_collection!(data, addition, key)
113
+ data.map do |item|
114
+ item_raw = item._raw[key]
115
+ item_raw.blank? ? [nil] : item_raw
116
+ end
117
+ .flatten
118
+ .each_with_index do |item, index|
119
+ item_addition = addition[index]
120
+ next if item_addition.nil? || item.nil?
121
+ if item_addition._raw.is_a?(Array)
122
+ extend_base_collection_with_array!(item, item_addition._raw)
123
+ else
124
+ item.merge! item_addition._raw
125
+ end
126
+ end
127
+ end
128
+
129
+ def extend_base_collection_with_array!(item, addition)
130
+ item[items_key] ||= []
131
+ item[items_key].concat(addition)
132
+ end
133
+
134
+ def extend_base_array!(data, addition, key)
135
+ data[key].zip(addition) do |item, additional_item|
136
+ item._raw.merge!(additional_item._raw) if additional_item.present?
137
+ end
138
+ end
139
+
140
+ def extend_base_item!(data, addition, key)
141
+ return if addition.nil?
142
+ if addition.collection?
143
+ extend_base_item_with_collection!(data, addition, key)
144
+ else # simple case merges hash into hash
145
+ data._raw[key.to_sym].merge!(addition._raw)
146
+ end
147
+ end
148
+
149
+ def extend_base_item_with_collection!(data, addition, key)
150
+ target = data[key]
151
+ if target._raw.is_a? Array
152
+ data[key] = addition.map(&:_raw)
153
+ else # hash with items
154
+ extend_base_item_with_hash_of_items!(target, addition)
155
+ end
156
+ end
157
+
158
+ def extend_base_item_with_hash_of_items!(target, addition)
159
+ LHS::Collection.nest(input: target._raw, value: [], record: self)
160
+ if LHS::Collection.access(input: target._raw, record: self).empty?
161
+ LHS::Collection.nest(input: target._raw, value: addition.compact.map(&:_raw), record: self)
162
+ else
163
+ LHS::Collection.access(input: target._raw, record: self).each_with_index do |item, index|
164
+ item.merge!(addition[index])
165
+ end
166
+ end
167
+ end
168
+
169
+ def handle_includes(includes, data, references = {})
170
+ references ||= {}
171
+ if includes.is_a? Hash
172
+ includes.each { |included, sub_includes| handle_include(included, data, sub_includes, references[included]) }
173
+ elsif includes.is_a? Array
174
+ includes.each do |included|
175
+ handle_includes(included, data, references)
176
+ end
177
+ else
178
+ handle_include(includes, data, nil, references[includes])
179
+ end
180
+ data.clear_cache! if data.present? # as we just included new nested resources
181
+ end
182
+
183
+ def handle_include(included, data, sub_includes = nil, reference = nil)
184
+ if data.blank? || skip_loading_includes?(data, included)
185
+ handle_skip_include(included, data, sub_includes, reference)
186
+ else
187
+ options = options_for_data(data, included)
188
+ options = extend_with_reference(options, reference)
189
+ addition = load_include(options, data, sub_includes, reference)
190
+ extend_raw_data!(data, addition, included)
191
+ expand_addition!(data, included, options) unless expanded_data?(addition)
192
+ end
193
+ end
194
+
195
+ def handle_skip_include(included, data, sub_includes = nil, reference = nil)
196
+ return if sub_includes.blank?
197
+ handle_includes(sub_includes, data[included], reference)
198
+ end
199
+
200
+ def options_for_data(data, included = nil)
201
+ return options_for_multiple(data, included) if data.collection?
202
+ return options_for_nested_items(data, included) if included && data[included].collection?
203
+ url_option_for(data, included)
204
+ end
205
+
206
+ def expand_addition!(data, included, reference)
207
+ addition = data[included]
208
+ options = options_for_data(addition)
209
+ options = extend_with_reference(options, reference)
210
+ record = record_for_options(options) || self
211
+ options = convert_options_to_endpoints(options) if record_for_options(options)
212
+ expanded_data = record.request(options)
213
+ extend_raw_data!(data, expanded_data, included)
214
+ end
215
+
216
+ def expanded_data?(addition)
217
+ return false if addition.blank?
218
+ if addition.item?
219
+ (addition._raw.keys - [:href]).any?
220
+ elsif addition.collection?
221
+ addition.any? do |item|
222
+ next if item.blank?
223
+ if item._raw.is_a?(Hash)
224
+ (item._raw.keys - [:href]).any?
225
+ elsif item._raw.is_a?(Array)
226
+ item.any? { |item| (item._raw.keys - [:href]).any? }
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ # Extends request options with options provided for this reference
233
+ def extend_with_reference(options, reference)
234
+ return options if reference.blank?
235
+ reference = reference.except(:url)
236
+ options ||= {}
237
+ if options.is_a?(Array)
238
+ options.map { |request_options| request_options.merge(reference) if request_options.present? }
239
+ elsif options.present?
240
+ options.merge(reference)
241
+ end
242
+ end
243
+
244
+ def skip_loading_includes?(data, included)
245
+ if data.collection?
246
+ data.to_a.none? { |item| item[included].present? }
247
+ elsif data.dig(included).blank?
248
+ true
249
+ elsif data[included].item? && data[included][:href].blank?
250
+ true
251
+ else
252
+ !data._raw.key?(included)
253
+ end
254
+ end
255
+
256
+ # After fetching the first page,
257
+ # we can evaluate if there are further remote objects remaining
258
+ # and after preparing all the requests that have to be made in order to fetch all
259
+ # remote items during this batch, they are fetched in parallel
260
+ def load_and_merge_remaining_objects!(data:, options:, load_not_paginated_collection: false)
261
+ if paginated?(data._raw)
262
+ load_and_merge_paginated_collection!(data, options)
263
+ elsif data.collection? && paginated?(data.first.try(:_raw))
264
+ load_and_merge_set_of_paginated_collections!(data, options)
265
+ elsif load_not_paginated_collection && data.collection?
266
+ warn('[Warning] "all" has been requested, but endpoint does not provide pagination meta data. If you just want to fetch the first response, use "where" or "fetch".')
267
+ load_and_merge_not_paginated_collection!(data, options)
268
+ end
269
+ end
270
+
271
+ def load_and_merge_not_paginated_collection!(data, options)
272
+ return if data.length.zero?
273
+ options = options.is_a?(Hash) ? options : {}
274
+ limit = options.dig(:params, limit_key(:parameter)) || pagination_class::DEFAULT_LIMIT
275
+ offset = options.dig(:params, pagination_key(:parameter)) || pagination_class::DEFAULT_OFFSET
276
+ options[:params] = options.fetch(:params, {}).merge(
277
+ limit_key(:parameter) => limit,
278
+ pagination_key(:parameter) => pagination_class.next_offset(
279
+ offset,
280
+ limit
281
+ )
282
+ )
283
+ additional_data = data._record.request(options)
284
+ additional_data.each do |item_data|
285
+ data.concat(input: data._raw, items: [item_data], record: self)
286
+ end
287
+ end
288
+
289
+ # sets nested data for a source object that needs to be accessed with a given path e.g. [:response, :total]
290
+ def set_nested_data(source, path, value)
291
+ return source[path] = value unless path.is_a?(Array)
292
+ path = path.dup
293
+ last = path.pop
294
+ path.inject(source, :fetch)[last] = value
295
+ end
296
+
297
+ def load_and_merge_paginated_collection!(data, options)
298
+ set_nested_data(data._raw, limit_key(:body), data.length) if data._raw.dig(*limit_key(:body)).blank? && !data.length.zero?
299
+ pagination = data._record.pagination(data)
300
+ return data unless pagination.pages_left?
301
+ record = data._record
302
+ if pagination.parallel?
303
+ load_and_merge_parallel_requests!(record, data, pagination, options)
304
+ else
305
+ load_and_merge_sequential_requests!(record, data, options, data._raw.dig(:next, :href), pagination)
306
+ end
307
+ end
308
+
309
+ def load_and_merge_parallel_requests!(record, data, pagination, options)
310
+ record.request(
311
+ options_for_next_batch(record, pagination, options)
312
+ ).each do |batch_data|
313
+ merge_batch_data_with_parent!(batch_data, data)
314
+ end
315
+ end
316
+
317
+ def load_and_merge_sequential_requests!(record, data, options, next_link, pagination)
318
+ warn "[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/local-ch/lhs#pagination-strategy-link)."
319
+ while next_link.present?
320
+ page_data = record.request(
321
+ options.except(:all).merge(url: next_link)
322
+ )
323
+ next_link = page_data._raw.dig(:next, :href)
324
+ merge_batch_data_with_parent!(page_data, data, pagination)
325
+ end
326
+ end
327
+
328
+ def load_and_merge_set_of_paginated_collections!(data, options)
329
+ options_for_next_batch = []
330
+ options.each_with_index do |element, index|
331
+ next if element.nil?
332
+ record = data[index]._record
333
+ pagination = record.pagination(data[index])
334
+ next unless pagination.pages_left?
335
+ options_for_next_batch.push(
336
+ options_for_next_batch(record, pagination, options[index]).tap do |options|
337
+ options.each do |option|
338
+ option[:merge_with_index] = index
339
+ end
340
+ end
341
+ )
342
+ end
343
+ data._record.request(options_for_next_batch.flatten).each do |batch_data|
344
+ merge_batch_data_with_parent!(batch_data, data[batch_data._request.options[:merge_with_index]])
345
+ end
346
+ end
347
+
348
+ # Load additional resources that are requested with include
349
+ def load_include(options, _data, sub_includes, references)
350
+ record = record_for_options(options) || self
351
+ options = convert_options_to_endpoints(options) if record_for_options(options)
352
+ prepare_options_for_include_request!(options, sub_includes, references)
353
+ if references && references[:all] # include all linked resources
354
+ load_include_all!(options, record, sub_includes, references)
355
+ else # simply request first page/batch
356
+ load_include_simple!(options, record)
357
+ end
358
+ end
359
+
360
+ def load_include_all!(options, record, sub_includes, references)
361
+ prepare_options_for_include_all_request!(options)
362
+ data = load_all_included!(record, options)
363
+ references.delete(:all) # for this reference all remote objects have been fetched
364
+ continue_including(data, sub_includes, references)
365
+ end
366
+
367
+ def load_include_simple!(options, record)
368
+ data = record.request(options)
369
+ warn "[WARNING] You included `#{options[:url]}`, but this endpoint is paginated. You might want to use `includes_all` instead of `includes` (https://github.com/local-ch/lhs#includes_all-for-paginated-endpoints)." if data && paginated?(data._raw)
370
+ data
371
+ end
372
+
373
+ # Continues loading included resources after one complete batch/level has been fetched
374
+ def continue_including(data, including, referencing)
375
+ handle_includes(including, data, referencing) if including.present? && data.present?
376
+ data
377
+ end
378
+
379
+ # Loads all included/linked resources,
380
+ # paginates itself to ensure all records are fetched
381
+ def load_all_included!(record, options)
382
+ data = record.request(options)
383
+ pagination = data._record.pagination(data)
384
+ load_and_merge_remaining_objects!(data: data, options: options) if pagination.parallel?
385
+ data
386
+ end
387
+
388
+ def prepare_options_for_include_all_request!(options)
389
+ if options.is_a?(Array)
390
+ options.each do |option|
391
+ prepare_option_for_include_all_request!(option)
392
+ end
393
+ else
394
+ prepare_option_for_include_all_request!(options)
395
+ end
396
+ options
397
+ end
398
+
399
+ # When including all resources on one level, don't forward :includes & :references
400
+ # as we have to fetch all resources on this level first, before we continue_including
401
+ def prepare_option_for_include_all_request!(option)
402
+ return option if option.blank? || option[:url].nil?
403
+ uri = parse_uri(option[:url], option)
404
+ get_params = Rack::Utils.parse_nested_query(uri.query)
405
+ .symbolize_keys
406
+ .except(limit_key(:parameter), pagination_key(:parameter))
407
+ option[:params] ||= {}
408
+ option[:params].reverse_merge!(get_params)
409
+ option[:params][limit_key(:parameter)] ||= LHS::Pagination::Base::DEFAULT_LIMIT
410
+ option[:url] = option[:url].gsub("?#{uri.query}", '')
411
+ option.delete(:including)
412
+ option.delete(:referencing)
413
+ option
414
+ end
415
+
416
+ def prepare_options_for_include_request!(options, sub_includes, references)
417
+ if options.is_a?(Array)
418
+ options.each { |option| option.merge!(including: sub_includes, referencing: references) if sub_includes.present? }
419
+ elsif sub_includes.present?
420
+ options.merge!(including: sub_includes, referencing: references)
421
+ end
422
+ options || {}
423
+ end
424
+
425
+ def merge_batch_data_with_parent!(batch_data, parent_data, pagination = nil)
426
+ parent_data.concat(input: parent_data._raw, items: batch_data.raw_items, record: self)
427
+ return if pagination.present? && pagination.is_a?(LHS::Pagination::Link)
428
+ [limit_key(:body), total_key, pagination_key(:body)].each do |pagination_attribute|
429
+ set_nested_data(
430
+ parent_data._raw,
431
+ pagination_attribute,
432
+ batch_data._raw.dig(*pagination_attribute)
433
+ )
434
+ end
435
+ end
436
+
437
+ # Merge explicit params nested in 'params' namespace with original hash.
438
+ def merge_explicit_params!(params)
439
+ return true unless params
440
+ explicit_params = params[:params]
441
+ params.delete(:params)
442
+ params.merge!(explicit_params) if explicit_params
443
+ end
444
+
445
+ def multiple_requests(options)
446
+ options = options.map do |option|
447
+ next if option.blank?
448
+ process_options(option, find_endpoint(option[:params], option.fetch(:url, nil)))
449
+ end
450
+ data = LHC.request(options.compact).map do |response|
451
+ LHS::Data.new(response.body, nil, self, response.request)
452
+ end
453
+ including = LHS::Complex.reduce(options.compact.map { |options| options.delete(:including) }.compact)
454
+ referencing = LHS::Complex.reduce(options.compact.map { |options| options.delete(:referencing) }.compact)
455
+ data = restore_with_nils(data, locate_nils(options)) # nil objects in data provide location information for mapping
456
+ data = LHS::Data.new(data, nil, self)
457
+ handle_includes(including, data, referencing) if including.present? && data.present?
458
+ data
459
+ end
460
+
461
+ def locate_nils(array)
462
+ nils = []
463
+ array.each_with_index { |value, index| nils << index if value.nil? }
464
+ nils
465
+ end
466
+
467
+ def restore_with_nils(array, nils)
468
+ array = array.dup
469
+ nils.sort.each { |index| array.insert(index, nil) }
470
+ array
471
+ end
472
+
473
+ def options_for_multiple(data, key = nil)
474
+ data.map do |item|
475
+ url_option_for(item, key)
476
+ end.flatten
477
+ end
478
+
479
+ def options_for_nested_items(data, key = nil)
480
+ data[key].map do |item|
481
+ url_option_for(item)
482
+ end.flatten
483
+ end
484
+
485
+ def options_for_next_batch(record, pagination, options)
486
+ batch_options = []
487
+ pagination.pages_left.times do |index|
488
+ page_options = {
489
+ params: {
490
+ record.limit_key(:parameter) => pagination.limit,
491
+ record.pagination_key(:parameter) => pagination.next_offset(index + 1)
492
+ }
493
+ }
494
+ batch_options.push(
495
+ options.deep_dup.deep_merge(page_options)
496
+ )
497
+ end
498
+ batch_options
499
+ end
500
+
501
+ # Merge explicit params and take configured endpoints options as base
502
+ def process_options(options, endpoint)
503
+ ignored_errors = options[:ignored_errors]
504
+ options = options.deep_dup
505
+ options[:ignored_errors] = ignored_errors if ignored_errors.present?
506
+ options[:params]&.deep_symbolize_keys!
507
+ options[:rescue] = merge_error_handlers(options[:rescue]) if options[:rescue]
508
+ options = (provider_options || {})
509
+ .deep_merge(endpoint.options || {})
510
+ .deep_merge(options)
511
+ options[:url] = compute_url!(options[:params]) unless options.key?(:url)
512
+ merge_explicit_params!(options[:params])
513
+ options.delete(:params) if options[:params]&.empty?
514
+ inject_interceptors!(options)
515
+ options
516
+ end
517
+
518
+ def inject_interceptors!(options)
519
+ if LHS.config.request_cycle_cache_enabled
520
+ inject_interceptor!(
521
+ options,
522
+ LHS::Interceptors::RequestCycleCache::Interceptor,
523
+ LHC::Caching,
524
+ "[WARNING] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured (see https://github.com/local-ch/lhc/blob/master/README.md#caching-interceptor)!"
525
+ )
526
+ end
527
+
528
+ endpoint = find_endpoint(options[:params], options.fetch(:url, nil))
529
+ if auto_oauth? || (endpoint.options&.dig(:oauth) && LHS.config.auto_oauth) || options[:oauth]
530
+ inject_interceptor!(
531
+ options.merge!(record: self),
532
+ LHS::Interceptors::AutoOauth::Interceptor,
533
+ LHC::Auth,
534
+ "[WARNING] Can't enable auto oauth as LHC::Auth interceptor is not enabled/configured (see https://github.com/local-ch/lhc/blob/master/README.md#authentication-interceptor)!"
535
+ )
536
+ end
537
+ end
538
+
539
+ def inject_interceptor!(options, interceptor, dependecy, warning)
540
+ interceptors = options[:interceptors] || LHC.config.interceptors
541
+ if interceptors.include?(dependecy)
542
+ # Ensure interceptor is prepend
543
+ interceptors = interceptors.unshift(interceptor)
544
+ options[:interceptors] = interceptors
545
+ else
546
+ warn(warning)
547
+ end
548
+ end
549
+
550
+ # LHC supports only one error handler, merge all error handlers to one
551
+ # and reraise
552
+ def merge_error_handlers(handlers)
553
+ lambda do |response|
554
+ return_data = nil
555
+ error_class = LHC::Error.find(response)
556
+ error = error_class.new(error_class, response)
557
+ handlers = handlers.map(&:to_a).to_a.select { |handler_error_class, _| error.is_a? handler_error_class }
558
+ raise(error) unless handlers.any?
559
+ handlers.each do |_, handler|
560
+ handlers_return = handler.call(response)
561
+ return_data = handlers_return if handlers_return.present?
562
+ end
563
+ return return_data
564
+ end
565
+ end
566
+
567
+ def record_for_options(options)
568
+ records = []
569
+ if options.is_a?(Array)
570
+ options.compact.each do |option|
571
+ record = LHS::Record.for_url(option[:url])
572
+ next unless record
573
+ records.push(record)
574
+ end
575
+ raise 'Found more than one record that could be used to do the request' if records.uniq.count > 1
576
+ records.uniq.first
577
+ else # Hash
578
+ LHS::Record.for_url(options[:url])
579
+ end
580
+ end
581
+
582
+ def single_request(options)
583
+ options ||= {}
584
+ options = options.dup
585
+ including = options.delete(:including)
586
+ referencing = options.delete(:referencing)
587
+ endpoint = find_endpoint(options[:params], options.fetch(:url, nil))
588
+ apply_limit!(options)
589
+ response = LHC.request(process_options(options, endpoint))
590
+ return nil if !response.success? && response.error_ignored?
591
+ data = LHS::Data.new(response.body, nil, self, response.request, endpoint)
592
+ single_request_load_and_merge_remaining_objects!(data, options, endpoint)
593
+ expand_items(data, options[:expanded]) if data.collection? && options[:expanded]
594
+ handle_includes(including, data, referencing) if including.present? && data.present?
595
+ data
596
+ end
597
+
598
+ def expand_items(data, expand_options)
599
+ expand_options = {} unless expand_options.is_a?(Hash)
600
+ options = data.map do |item|
601
+ expand_options.merge(url: item.href)
602
+ end
603
+ expanded_data = request(options)
604
+ data.each_with_index do |item, index|
605
+ item.merge_raw!(expanded_data[index])
606
+ end
607
+ end
608
+
609
+ def url_option_for(item, key = nil)
610
+ link = key ? item[key] : item
611
+ return if link.blank?
612
+ return { url: link.href } if !link.collection?
613
+
614
+ link.map do |item|
615
+ { url: item.href } if item.present? && item.href.present?
616
+ end.compact
617
+ end
618
+ end
619
+ end
620
+ end