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,465 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class DHS::Record
6
+
7
+ module Chainable
8
+ extend ActiveSupport::Concern
9
+
10
+ # You can start new option chains for already fetched records (needed for save, update, valid etc.)
11
+ def options(hash = nil)
12
+ return method_missing(:options) if hash.nil?
13
+ Chain.new(self.class, Option.new(hash), self)
14
+ end
15
+
16
+ module ClassMethods
17
+ def where(args = nil)
18
+ if href?(args)
19
+ Chain.new(self, Option.new(url: args))
20
+ else
21
+ Chain.new(self, Parameter.new(args))
22
+ end
23
+ end
24
+
25
+ def fetch
26
+ Chain.new(self, nil).fetch
27
+ end
28
+
29
+ def all(hash = nil)
30
+ chain = Chain.new(self, Parameter.new(hash))
31
+ chain._links.push(Option.new(all: true))
32
+ chain
33
+ end
34
+
35
+ def expanded(options = nil)
36
+ Chain.new(self, Option.new(expanded: options || true))
37
+ end
38
+
39
+ def options(hash = nil)
40
+ Chain.new(self, Option.new(hash))
41
+ end
42
+
43
+ def page(page)
44
+ Chain.new(self, Pagination.new(page: page))
45
+ end
46
+
47
+ def per(limit)
48
+ Chain.new(self, Pagination.new(per: limit))
49
+ end
50
+
51
+ def limit(argument = nil)
52
+ Chain.new(self, Pagination.new(per: argument))
53
+ end
54
+
55
+ def rescue(error_class, handler)
56
+ Chain.new(self, ErrorHandling.new(error_class => handler))
57
+ end
58
+
59
+ def ignore(*error_classes)
60
+ chain = Chain.new(self, IgnoredError.new(error_classes.shift))
61
+ error_classes.each do |error_class|
62
+ chain._links.push(IgnoredError.new(error_class))
63
+ end
64
+ chain
65
+ end
66
+
67
+ def includes_first_page(*args)
68
+ Chain.new(self, Include.new(Chain.unfold(args)))
69
+ end
70
+
71
+ def includes(*args)
72
+ chain = Chain.new(self, Include.new(Chain.unfold(args)))
73
+ chain.include_all!(args)
74
+ chain
75
+ end
76
+
77
+ def references(*args)
78
+ Chain.new(self, Reference.new(Chain.unfold(args)))
79
+ end
80
+ end
81
+
82
+ # Link: A part of a chain
83
+ class Link
84
+
85
+ attr_reader :data
86
+
87
+ def initialize(data = nil)
88
+ @data = data
89
+ end
90
+
91
+ def [](parameter)
92
+ @data[parameter]
93
+ end
94
+ end
95
+
96
+ # Parameter: Part of the chain that will be forwarded to the endpoint as parameter
97
+ class Parameter < Link
98
+ end
99
+
100
+ # Option: Part of the chain that will be used to configure the communication with the endpoint
101
+ class Option < Link
102
+ end
103
+
104
+ # Pagination: Part of the chain that will be used to controll pagination
105
+ class Pagination < Link
106
+ end
107
+
108
+ # Include: Part of the chain that will be used to include linked resources
109
+ class Include < Link
110
+ end
111
+
112
+ # Reference: Part of the chain that will be used to pass options to requests
113
+ # made to include linked resources
114
+ class Reference < Link
115
+ end
116
+
117
+ # ErrorHandling: Catch and resolve errors when resolving the chain
118
+ class ErrorHandling < Link
119
+ delegate :call, to: :handler
120
+
121
+ def handler
122
+ @data.values.first
123
+ end
124
+
125
+ def class
126
+ @data.keys.first
127
+ end
128
+
129
+ def to_a
130
+ [self.class, handler]
131
+ end
132
+ end
133
+
134
+ # IgnoredError: Ignore certain DHC errors when resolving the chain
135
+ class IgnoredError < Link
136
+ end
137
+
138
+ # A sequence of links
139
+ class Chain
140
+
141
+ excluded_methods = []
142
+ # Instance exec is required for scope chains
143
+ excluded_methods += [:instance_exec]
144
+ # Clone is required to have immutable
145
+ excluded_methods += [:clone]
146
+ # We are asked from ruby to not delegate __send__ and object_id
147
+ excluded_methods += %i[__send__ object_id]
148
+ delegated_methods = Object.instance_methods - excluded_methods
149
+ delegate(*delegated_methods, to: :resolve)
150
+
151
+ attr_accessor :_links
152
+
153
+ def self.unfold(args)
154
+ (args.size == 1) ? args[0] : args
155
+ end
156
+
157
+ def initialize(record_class, link, record = nil)
158
+ @record_class = record_class
159
+ @record = record
160
+ self._links = [link].compact
161
+ end
162
+
163
+ def create(data = {})
164
+ @record_class.create(data, resolved_options)
165
+ end
166
+
167
+ def create!(data = {})
168
+ @record_class.create!(data, resolved_options)
169
+ end
170
+
171
+ def save!(options = nil)
172
+ options ||= {}
173
+ @record.save!(resolved_options.merge(options))
174
+ end
175
+
176
+ def save(options = nil)
177
+ options ||= {}
178
+ @record.save(resolved_options.merge(options))
179
+ end
180
+
181
+ def destroy(options = nil)
182
+ options ||= {}
183
+ options = options.respond_to?(:to_h) ? options : { id: options }
184
+ if @record
185
+ @record.destroy(resolved_options.merge(options))
186
+ else
187
+ @record_class.destroy(options, resolved_options)
188
+ end
189
+ end
190
+
191
+ def update(data = {}, options = nil)
192
+ options ||= {}
193
+ @record.update(data, resolved_options.merge(options))
194
+ end
195
+
196
+ def update!(data = {}, options = nil)
197
+ options ||= {}
198
+ @record.update!(data, resolved_options.merge(options))
199
+ end
200
+
201
+ def partial_update(data = {}, options = nil)
202
+ options ||= {}
203
+ @record.update(data, resolved_options.merge(options), true)
204
+ end
205
+
206
+ def partial_update!(data = {}, options = nil)
207
+ options ||= {}
208
+ @record.update!(data, resolved_options.merge(options), true)
209
+ end
210
+
211
+ def valid?(options = nil)
212
+ options ||= {}
213
+ @record.valid?(resolved_options.merge(options))
214
+ end
215
+ alias validate valid?
216
+
217
+ def where(args = nil)
218
+ if DHS::Record.href?(args)
219
+ push(Option.new(url: args))
220
+ else
221
+ push(Parameter.new(args))
222
+ end
223
+ end
224
+
225
+ def all(hash = nil)
226
+ push([Parameter.new(hash), Option.new(all: true)])
227
+ end
228
+
229
+ def expanded(options = nil)
230
+ push(Option.new(expanded: options || true))
231
+ end
232
+
233
+ def options(hash = nil)
234
+ push(Option.new(hash))
235
+ end
236
+
237
+ def ignore(*error_classes)
238
+ chain = push(IgnoredError.new(error_classes.shift))
239
+ error_classes.each do |error_class|
240
+ chain._links.push(IgnoredError.new(error_class))
241
+ end
242
+ chain
243
+ end
244
+
245
+ def page(page)
246
+ push(Pagination.new(page: page))
247
+ end
248
+
249
+ def per(per)
250
+ push(Pagination.new(per: per))
251
+ end
252
+
253
+ def limit(argument = nil)
254
+ return resolve.limit if argument.blank?
255
+ push(Pagination.new(per: argument))
256
+ end
257
+
258
+ def rescue(error_class, handler)
259
+ push(ErrorHandling.new(error_class => handler))
260
+ end
261
+
262
+ def includes_first_page(*args)
263
+ push(Include.new(Chain.unfold(args)))
264
+ end
265
+
266
+ def includes(*args)
267
+ chain = push(Include.new(Chain.unfold(args)))
268
+ chain.include_all!(args)
269
+ chain
270
+ end
271
+
272
+ def references(*args)
273
+ push(Reference.new(Chain.unfold(args)))
274
+ end
275
+
276
+ def find(*args)
277
+ @record_class.find(*args.push(resolved_options))
278
+ end
279
+
280
+ def find_by(params = {})
281
+ @record_class.find_by(params, resolved_options)
282
+ end
283
+
284
+ def find_by!(params = {})
285
+ @record_class.find_by!(params, resolved_options)
286
+ end
287
+
288
+ def first!
289
+ @record_class.first!(resolved_options)
290
+ end
291
+
292
+ # Returns a hash of where conditions
293
+ def where_values_hash
294
+ chain_parameters
295
+ end
296
+
297
+ # Returns a hash of options
298
+ def option_values_hash
299
+ chain_options
300
+ end
301
+
302
+ # Returns a hash of pagination values
303
+ def pagination_values_hash
304
+ chain_pagination
305
+ end
306
+
307
+ # Returns a hash of include conditions
308
+ def includes_values
309
+ chain_includes
310
+ end
311
+
312
+ # Returns a hash of reference options
313
+ def references_values
314
+ chain_references
315
+ end
316
+
317
+ # Adds additional .references(name_of_linked_resource: { all: true })
318
+ # to all linked resources included with includes_all
319
+ def include_all!(args)
320
+ includes_all_to_references(args).each do |reference|
321
+ _links.push(reference)
322
+ end
323
+ end
324
+
325
+ def fetch
326
+ resolve
327
+ end
328
+
329
+ protected
330
+
331
+ def method_missing(name, *args, **keyword_args, &block)
332
+ scope = @record_class.scopes[name]
333
+ return instance_exec(*args, &scope) if scope
334
+ resolve.send(name, *args, **keyword_args, &block)
335
+ end
336
+
337
+ def respond_to_missing?(name, include_all = false)
338
+ @record_class.scopes[name] ||
339
+ resolve.respond_to?(name, include_all)
340
+ end
341
+
342
+ def resolve
343
+ return @resolved if defined?(@resolved)
344
+ response_data = @record_class.request(resolved_options)
345
+ @resolved = response_data ? @record_class.new(response_data) : nil
346
+ end
347
+
348
+ def resolved_options
349
+ options = chain_options
350
+ options = options.deep_merge(params: chain_parameters.merge(chain_pagination))
351
+ options = options.merge(rescue: chain_error_handler) if chain_error_handler.present?
352
+ options = options.merge(ignore: chain_ignored_errors) if chain_ignored_errors.present?
353
+ options = options.merge(including: chain_includes) if chain_includes.present?
354
+ options = options.merge(referencing: chain_references) if chain_references.present?
355
+ options
356
+ end
357
+
358
+ private
359
+
360
+ # Translates includes_all(resources:) to the internal datastructure
361
+ # references(resource: { all: true })
362
+ def includes_all_to_references(args, parent = nil)
363
+ references = []
364
+ if args.is_a?(Array)
365
+ includes_all_to_references_for_arrays!(references, args, parent)
366
+ elsif args.is_a?(Hash)
367
+ includes_all_to_references_for_hash!(references, args, parent)
368
+ elsif args.is_a?(Symbol)
369
+ includes_all_to_references_for_symbol!(references, args, parent)
370
+ end
371
+ references
372
+ end
373
+
374
+ def includes_all_to_references_for_arrays!(references, args, parent)
375
+ args.each do |part|
376
+ references.concat(includes_all_to_references(part, parent))
377
+ end
378
+ end
379
+
380
+ def includes_all_to_references_for_hash!(references, args, parent)
381
+ args.each do |key, value|
382
+ parent ||= { all: true }
383
+ references.concat([Reference.new(key => parent)])
384
+ references.concat(includes_all_to_references(value, parent))
385
+ end
386
+ end
387
+
388
+ def includes_all_to_references_for_symbol!(references, args, parent)
389
+ if parent.present?
390
+ parent[args] = { all: true }
391
+ else
392
+ references.concat([Reference.new(args => { all: true })])
393
+ end
394
+ end
395
+
396
+ def push(link)
397
+ clone = self.clone
398
+ clone._links = _links + [link].flatten.compact
399
+ clone
400
+ end
401
+
402
+ def chain_parameters
403
+ @chain_parameters ||= merge_links(_links.select { |link| link.is_a? Parameter })
404
+ end
405
+
406
+ def chain_options
407
+ @chain_options ||= merge_links(_links.select { |link| link.is_a? Option })
408
+ end
409
+
410
+ def chain_error_handler
411
+ @chain_error_handler ||= _links.select { |link| link.is_a? ErrorHandling }
412
+ end
413
+
414
+ def chain_ignored_errors
415
+ @chain_ignored_errors ||= _links
416
+ .select { |link| link.is_a? IgnoredError }
417
+ .map { |link| link.data }
418
+ end
419
+
420
+ def chain_pagination
421
+ @chain_pagination ||= resolve_pagination(_links.select { |link| link.is_a? Pagination })
422
+ end
423
+
424
+ def chain_includes
425
+ @chain_includes ||= DHS::Complex.reduce(
426
+ _links
427
+ .select { |link| link.is_a?(Include) && link.data.present? }
428
+ .map { |link| link.data }
429
+ )
430
+ end
431
+
432
+ def chain_references
433
+ @chain_references ||= DHS::Complex.reduce(
434
+ _links
435
+ .select { |link| link.is_a?(Reference) && link.data.present? }
436
+ .map { |link| link.data }
437
+ )
438
+ end
439
+
440
+ def resolve_pagination(links)
441
+ return {} if links.empty?
442
+ page = 1
443
+ per = DHS::Pagination::Base::DEFAULT_LIMIT
444
+ links.each do |link|
445
+ page = link[:page] if link[:page].present?
446
+ per = link[:per] if link[:per].present?
447
+ end
448
+ pagination = @record_class.pagination_class
449
+ {
450
+ @record_class.pagination_key(:parameter) => pagination.page_to_offset(page, per),
451
+ @record_class.limit_key(:parameter) => per
452
+ }
453
+ end
454
+
455
+ def merge_links(links)
456
+ hash = {}
457
+ links.each do |link|
458
+ next if link.data.blank?
459
+ hash.deep_merge!(link.data)
460
+ end
461
+ hash
462
+ end
463
+ end
464
+ end
465
+ end