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,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A collection is a special type of data
4
+ # that contains multiple items
5
+ class LHS::Collection < LHS::Proxy
6
+ autoload :HandleNested,
7
+ 'lhs/concerns/collection/handle_nested'
8
+ autoload :InternalCollection,
9
+ 'lhs/concerns/collection/internal_collection'
10
+
11
+ include HandleNested
12
+ include InternalCollection
13
+ include Create
14
+
15
+ METHOD_NAMES_EXLCUDED_FROM_WRAPPING = %w(to_a to_ary map).freeze
16
+
17
+ delegate :select, :length, :size, to: :_collection
18
+ delegate :_record, :_raw, to: :_data
19
+ delegate :limit, :count, :total, :offset, :current_page, :start,
20
+ :next?, :previous?, to: :_pagination
21
+
22
+ def _pagination
23
+ _record.pagination(_data)
24
+ end
25
+
26
+ def href
27
+ return _data._raw[:href] if _data._raw.is_a? Hash
28
+ nil
29
+ end
30
+
31
+ def _collection
32
+ @_collection ||= begin
33
+ raw = _data._raw if _data._raw.is_a?(Array)
34
+ raw ||= _data.access(input: _data._raw, record: _record)
35
+ Collection.new(raw, _data, _record)
36
+ end
37
+ end
38
+
39
+ def collection?
40
+ true
41
+ end
42
+
43
+ def item?
44
+ false
45
+ end
46
+
47
+ def raw_items
48
+ if _raw.is_a?(Array)
49
+ _raw
50
+ else
51
+ access(input: _raw, record: _record)
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def method_missing(name, *args, &block)
58
+ if _collection.respond_to?(name)
59
+ value = _collection.send(name, *args, &block)
60
+ record = LHS::Record.for_url(value[:href]) if value.is_a?(Hash) && value[:href]
61
+ record ||= _record
62
+ value = enclose_item_in_data(value) if value.is_a?(Hash)
63
+ return value if METHOD_NAMES_EXLCUDED_FROM_WRAPPING.include?(name.to_s)
64
+ wrap_return(value, record, name, args)
65
+ elsif _data._raw.is_a?(Hash)
66
+ get(name, *args)
67
+ end
68
+ end
69
+
70
+ def respond_to_missing?(name, _include_all = false)
71
+ # We accept every message that does not belong to set of keywords and is not a setter
72
+ !BLACKLISTED_KEYWORDS.include?(name.to_s) && !name.to_s[/=$/]
73
+ end
74
+
75
+ private
76
+
77
+ # Encloses accessed collection item
78
+ # by wrapping it in an LHS::Item
79
+ def enclose_item_in_data(value)
80
+ data = LHS::Data.new(value, _data, _record)
81
+ item = LHS::Item.new(data)
82
+ LHS::Data.new(item, _data)
83
+ end
84
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Complex is the main data structure for includes:
4
+ # Can be for example :place, [:place, :customer],
5
+ # [{ places: [:customer] }, { places: { customer: :contract }}]
6
+ # and so on
7
+ class LHS::Complex
8
+ attr_reader :data
9
+
10
+ def reduce!(data)
11
+ if data.is_a?(LHS::Complex)
12
+ @data = data.data
13
+ elsif data.is_a?(Array) && !data.empty?
14
+ @data = data.inject(LHS::Complex.new.reduce!([])) { |acc, datum| acc.merge!(LHS::Complex.new.reduce!(datum)) }.data
15
+ elsif data.is_a?(Hash) && !data.empty?
16
+ @data = data.map { |k, v| [k, LHS::Complex.new.reduce!(v)] }.to_h
17
+ else
18
+ @data = data
19
+ end
20
+
21
+ self
22
+ end
23
+
24
+ def self.reduce(data)
25
+ new.reduce!(data).unwrap
26
+ end
27
+
28
+ def merge!(other)
29
+ if data.is_a?(Symbol)
30
+ merge_into_symbol!(other)
31
+ elsif data.is_a?(Array)
32
+ merge_into_array!(other)
33
+ elsif data.is_a?(Hash)
34
+ merge_into_hash!(other)
35
+ elsif unwrap != other.unwrap
36
+ raise ArgumentError, "Cannot merge #{unwrap} with #{other.unwrap}"
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ def unwrap
43
+ if data.is_a?(Array)
44
+ result = data.map(&:unwrap)
45
+ result.empty? ? nil : result
46
+ elsif data.is_a?(Hash)
47
+ result = data.map { |k, v| [k, v.unwrap] }.to_h
48
+ result.empty? ? nil : result
49
+ else
50
+ data
51
+ end
52
+ end
53
+
54
+ def ==(other)
55
+ unwrap == other.unwrap
56
+ end
57
+
58
+ private
59
+
60
+ def merge_into_array!(other)
61
+ if data.empty?
62
+ @data = other.data
63
+ elsif other.data.is_a?(Symbol)
64
+ merge_symbol_into_array!(other)
65
+ elsif other.data.is_a?(Array)
66
+ merge_array_into_array!(other)
67
+ elsif other.data.is_a?(Hash)
68
+ merge_hash_into_array!(other)
69
+ end
70
+ end
71
+
72
+ def merge_symbol_into_array!(other)
73
+ return if data.include?(other)
74
+ data.push(other)
75
+ end
76
+
77
+ def merge_array_into_array!(other)
78
+ @data = other.data.inject(self) { |acc, datum| acc.merge!(datum) }.data
79
+ end
80
+
81
+ def merge_hash_into_array!(other)
82
+ @data = [].tap do |new_data|
83
+ data.each do |element|
84
+ if element.data.is_a?(Symbol)
85
+ # remove keys that were in the hash
86
+ new_data << element if !other.data.key?(element.data)
87
+ elsif element.data.is_a?(Array)
88
+ new_data << element
89
+ elsif element.data.is_a?(Hash)
90
+ new_data << element.merge!(other)
91
+ end
92
+ end
93
+ end
94
+
95
+ # add it to the array if there was no hash to merge it
96
+ data.push(other) if data.none? { |element| element.data.is_a?(Hash) }
97
+ end
98
+
99
+ def merge_into_hash!(other)
100
+ if data.empty?
101
+ @data = other.data
102
+ elsif other.data.is_a?(Symbol)
103
+ merge_symbol_into_hash!(other)
104
+ elsif other.data.is_a?(Array)
105
+ merge_array_into_hash!(other)
106
+ elsif other.data.is_a?(Hash)
107
+ merge_hash_into_hash!(other)
108
+ end
109
+ end
110
+
111
+ def merge_symbol_into_hash!(other)
112
+ return if data.key?(other.data)
113
+
114
+ @data = [LHS::Complex.new.reduce!(data), other]
115
+ end
116
+
117
+ def merge_array_into_hash!(other)
118
+ @data = other.data.inject(self) { |acc, datum| acc.merge!(datum) }.data
119
+ end
120
+
121
+ def merge_hash_into_hash!(other)
122
+ other.data.each do |k, v|
123
+ if data.key?(k)
124
+ data[k] = data[k].merge!(v)
125
+ else
126
+ data[k] = v
127
+ end
128
+ end
129
+ end
130
+
131
+ def merge_into_symbol!(other)
132
+ if other.data.is_a?(Symbol)
133
+ merge_symbol_into_symbol!(other)
134
+ elsif other.data.is_a?(Array)
135
+ merge_array_into_symbol!(other)
136
+ elsif other.data.is_a?(Hash)
137
+ merge_hash_into_symbol!(other)
138
+ end
139
+ end
140
+
141
+ def merge_symbol_into_symbol!(other)
142
+ return if other.data == data
143
+
144
+ @data = [LHS::Complex.new.reduce!(data), other]
145
+ end
146
+
147
+ def merge_array_into_symbol!(other)
148
+ @data = other.data.unshift(LHS::Complex.new.reduce!(data)).uniq { |a| a.unwrap }
149
+ end
150
+
151
+ def merge_hash_into_symbol!(other)
152
+ if other.data.key?(data)
153
+ @data = other.data
154
+ else
155
+ @data = [LHS::Complex.new.reduce!(data), other]
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ # Preload all the LHS::Records that are defined in app/models/*
6
+ # in order to collect record endpoints and to be able to identify records from hrefs
7
+ # and not only from model constant names (which is different to ActiveRecord)
8
+ module AutoloadRecords
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ class Engine < Rails::Engine
13
+ initializer 'Load all LHS::Records from app/models/**' do |app|
14
+ Middleware.require_records
15
+ next if app.config.cache_classes
16
+
17
+ app.config.middleware.use Middleware
18
+ end
19
+
20
+ class Middleware
21
+ def initialize(app)
22
+ @app = app
23
+ end
24
+
25
+ def call(env)
26
+ self.class.require_records
27
+ @app.call(env)
28
+ end
29
+
30
+ def self.model_files
31
+ Dir.glob(Rails.root.join('app', 'models', '**', '*.rb'))
32
+ end
33
+
34
+ def self.require_direct_inheritance
35
+ model_files.map do |file|
36
+ next unless File.read(file).match('LHS::Record')
37
+ require_dependency file
38
+ file.split('models/').last.gsub('.rb', '').classify
39
+ end.compact
40
+ end
41
+
42
+ def self.require_inheriting_records(parents)
43
+ model_files.each do |file|
44
+ next if parents.none? { |parent| File.read(file).match(parent) }
45
+ require_dependency file
46
+ end
47
+ end
48
+
49
+ def self.require_records
50
+ require_inheriting_records(require_direct_inheritance)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Collection < LHS::Proxy
6
+
7
+ # Handles pontentially (deep-)nested collections
8
+ # Examples:
9
+ # [ { name: 'Steve '} ]
10
+ # { items: [ { name: 'Steve' } ] }
11
+ # { response: { business: [ { name: 'Steve' } ] } }
12
+ module HandleNested
13
+ extend ActiveSupport::Concern
14
+
15
+ delegate :access, :nest, :concat, to: :class
16
+
17
+ module ClassMethods
18
+ # Access potentially nested collection of items
19
+ def access(input:, record: nil)
20
+ input.dig(*items_key(record))
21
+ end
22
+
23
+ # Initializes nested collection
24
+ def nest(input:, value: nil, record: nil)
25
+ input[items_key(record)] = value
26
+ end
27
+
28
+ # Concats existing nested collection of items
29
+ # with given items
30
+ def concat(input:, items:, record: nil)
31
+ input.dig(*items_key(record)).concat(items)
32
+ end
33
+
34
+ private
35
+
36
+ # Takes configured items key to access collection of items
37
+ # of falls back to the default key
38
+ def items_key(record)
39
+ record&.items_key || LHS::Record.items_key
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Collection < LHS::Proxy
6
+
7
+ module InternalCollection
8
+ extend ActiveSupport::Concern
9
+
10
+ # The internal collection class that includes enumerable
11
+ # and insures to return LHS::Items in case of iterating items
12
+ class Collection
13
+ include Enumerable
14
+
15
+ attr_accessor :raw
16
+ delegate :length, :size, :first, :last, :sample, :[], :present?, :blank?, :empty?,
17
+ :<<, :push, :compact, to: :raw
18
+
19
+ def initialize(raw, parent, record)
20
+ self.raw = raw
21
+ @parent = parent
22
+ @record = record
23
+ end
24
+
25
+ def to_ary
26
+ map { |item| item }
27
+ end
28
+
29
+ def each(&_block)
30
+ raw.each do |item|
31
+ if item.is_a? Hash
32
+ yield cast_item(item)
33
+ else
34
+ yield item
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def cast_item(item)
42
+ record_by_href = LHS::Record.for_url(item[:href]) if item[:href]
43
+ data = LHS::Data.new(item, @parent, @record)
44
+ return (record_by_href || @record).new(data) if record_by_href || @record
45
+ data
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module LHS
6
+ module Configuration
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def config
11
+ LHS::Config.instance
12
+ end
13
+
14
+ def configure
15
+ LHS::Config.instance.reset
16
+ yield config
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Data
6
+
7
+ module Becomes
8
+ extend ActiveSupport::Concern
9
+
10
+ def becomes(klass, options = {})
11
+ return self if self.class == klass && !is_a?(LHS::Data)
12
+ data = LHS::Data.new(_raw, _parent, klass)
13
+ data.errors = options[:errors] if options[:errors]
14
+ data.warnings = options[:warnings] if options[:warnings]
15
+ klass.new(data)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Data
6
+
7
+ module Equality
8
+ extend ActiveSupport::Concern
9
+
10
+ def ==(other)
11
+ _raw == other.try(:_raw)
12
+ end
13
+ end
14
+ end