lhs 24.0.0

Sign up to get free protection for your applications and to get access to all the features.
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