ballast 2.2.4 → 2.2.5

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -1
  3. data/.travis-gemfile +2 -2
  4. data/.travis.yml +5 -3
  5. data/.yardopts +1 -1
  6. data/CHANGELOG.md +9 -0
  7. data/Gemfile +1 -1
  8. data/LICENSE.md +21 -0
  9. data/README.md +22 -17
  10. data/Rakefile +1 -1
  11. data/ballast.gemspec +1 -1
  12. data/{doc → docs}/Ballast.html +19 -21
  13. data/docs/Ballast/AjaxResponse.html +1470 -0
  14. data/{doc → docs}/Ballast/Concerns.html +3 -3
  15. data/{doc → docs}/Ballast/Concerns/AjaxHandling.html +33 -38
  16. data/{doc → docs}/Ballast/Concerns/Common.html +45 -52
  17. data/{doc → docs}/Ballast/Concerns/ErrorsHandling.html +13 -14
  18. data/docs/Ballast/Concerns/JSONApi.html +127 -0
  19. data/docs/Ballast/Concerns/JSONApi/PaginationHandling.html +617 -0
  20. data/docs/Ballast/Concerns/JSONApi/RequestHandling.html +775 -0
  21. data/docs/Ballast/Concerns/JSONApi/ResponseHandling.html +917 -0
  22. data/{doc → docs}/Ballast/Concerns/View.html +35 -42
  23. data/{doc → docs}/Ballast/Configuration.html +19 -22
  24. data/{doc → docs}/Ballast/Emoji.html +1 -1
  25. data/{doc → docs}/Ballast/Emoji/Character.html +15 -18
  26. data/docs/Ballast/Emoji/Utils.html +794 -0
  27. data/{doc → docs}/Ballast/Errors.html +1 -1
  28. data/{doc → docs}/Ballast/Errors/Base.html +16 -18
  29. data/{doc → docs}/Ballast/Errors/Failure.html +1 -1
  30. data/{doc → docs}/Ballast/Errors/InvalidDomain.html +1 -1
  31. data/{doc → docs}/Ballast/Errors/ValidationFailure.html +1 -1
  32. data/{doc → docs}/Ballast/Middlewares.html +1 -1
  33. data/{doc → docs}/Ballast/Middlewares/DefaultHost.html +16 -18
  34. data/docs/Ballast/RequestDomainMatcher.html +917 -0
  35. data/docs/Ballast/Service.html +1513 -0
  36. data/docs/Ballast/Service/Response.html +1270 -0
  37. data/{doc → docs}/Ballast/Version.html +5 -9
  38. data/{doc → docs}/_index.html +45 -1
  39. data/docs/class_list.html +58 -0
  40. data/{doc → docs}/css/common.css +0 -0
  41. data/{doc → docs}/css/full_list.css +0 -0
  42. data/{doc → docs}/css/style.css +0 -0
  43. data/{doc → docs}/file.README.html +7 -5
  44. data/{doc → docs}/file_list.html +0 -0
  45. data/{doc → docs}/frames.html +0 -0
  46. data/{doc → docs}/index.html +7 -5
  47. data/{doc → docs}/js/app.js +0 -0
  48. data/{doc → docs}/js/full_list.js +0 -0
  49. data/{doc → docs}/js/jquery.js +0 -0
  50. data/{doc → docs}/method_list.html +150 -36
  51. data/docs/top-level-namespace.html +149 -0
  52. data/lib/ballast.rb +1 -1
  53. data/lib/ballast/ajax_response.rb +1 -1
  54. data/lib/ballast/concerns/ajax_handling.rb +1 -1
  55. data/lib/ballast/concerns/common.rb +1 -1
  56. data/lib/ballast/concerns/errors_handling.rb +1 -1
  57. data/lib/ballast/concerns/json_api/pagination_handling.rb +83 -0
  58. data/lib/ballast/concerns/json_api/request_handling.rb +235 -0
  59. data/lib/ballast/concerns/json_api/response_handling.rb +70 -0
  60. data/lib/ballast/concerns/view.rb +1 -1
  61. data/lib/ballast/configuration.rb +1 -1
  62. data/lib/ballast/emoji.rb +1 -1
  63. data/lib/ballast/errors.rb +1 -1
  64. data/lib/ballast/middlewares/default_host.rb +1 -1
  65. data/lib/ballast/request_domain_matcher.rb +1 -1
  66. data/lib/ballast/service.rb +1 -1
  67. data/lib/ballast/version.rb +2 -2
  68. data/spec/ballast/ajax_response_spec.rb +1 -1
  69. data/spec/ballast/concerns/ajax_handling_spec.rb +1 -1
  70. data/spec/ballast/concerns/common_spec.rb +1 -1
  71. data/spec/ballast/concerns/errors_handling_spec.rb +1 -1
  72. data/spec/ballast/concerns/view_spec.rb +1 -1
  73. data/spec/ballast/configuration_spec.rb +1 -1
  74. data/spec/ballast/errors_spec.rb +1 -1
  75. data/spec/ballast/middlewares/default_host_spec.rb +1 -1
  76. data/spec/ballast/request_domain_matcher_spec.rb +1 -1
  77. data/spec/ballast_spec.rb +1 -1
  78. data/spec/spec_helper.rb +1 -1
  79. metadata +47 -39
  80. data/doc/Ballast/AjaxResponse.html +0 -1478
  81. data/doc/Ballast/Emoji/Utils.html +0 -799
  82. data/doc/Ballast/RequestDomainMatcher.html +0 -923
  83. data/doc/Ballast/Service.html +0 -1522
  84. data/doc/Ballast/Service/Response.html +0 -1278
  85. data/doc/class_list.html +0 -58
  86. data/doc/top-level-namespace.html +0 -112
@@ -0,0 +1,149 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.8.7.6
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ hasFrames = window.top.frames.main ? true : false;
19
+ relpath = '';
20
+ framesUrl = "frames.html#!top-level-namespace.html";
21
+ </script>
22
+
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
25
+
26
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
27
+
28
+
29
+ </head>
30
+ <body>
31
+ <div id="header">
32
+ <div id="menu">
33
+
34
+ <a href="_index.html">Index</a> &raquo;
35
+
36
+
37
+ <span class="title">Top Level Namespace</span>
38
+
39
+
40
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
41
+ </div>
42
+
43
+ <div id="search">
44
+
45
+ <a class="full_list_link" id="class_list_link"
46
+ href="class_list.html">
47
+ Class List
48
+ </a>
49
+
50
+ <a class="full_list_link" id="method_list_link"
51
+ href="method_list.html">
52
+ Method List
53
+ </a>
54
+
55
+ <a class="full_list_link" id="file_list_link"
56
+ href="file_list.html">
57
+ File List
58
+ </a>
59
+
60
+ </div>
61
+ <div class="clear"></div>
62
+ </div>
63
+
64
+ <iframe id="search_frame"></iframe>
65
+
66
+ <div id="content"><h1>Top Level Namespace
67
+
68
+
69
+
70
+ </h1>
71
+
72
+ <dl class="box">
73
+
74
+
75
+
76
+ <dt class="r1">Extended by:</dt>
77
+ <dd class="r1"><span class='object_link'><a href="Ballast/Emoji/Utils.html" title="Ballast::Emoji::Utils (module)">Ballast::Emoji::Utils</a></span></dd>
78
+
79
+
80
+
81
+
82
+ <dt class="r2">Includes:</dt>
83
+ <dd class="r2"><span class='object_link'><a href="Ballast/Emoji/Character.html" title="Ballast::Emoji::Character (module)">Ballast::Emoji::Character</a></span></dd>
84
+
85
+
86
+
87
+
88
+
89
+ </dl>
90
+ <div class="clear"></div>
91
+
92
+ <h2>Defined Under Namespace</h2>
93
+ <p class="children">
94
+
95
+
96
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Ballast.html" title="Ballast (module)">Ballast</a></span>
97
+
98
+
99
+
100
+
101
+ </p>
102
+
103
+
104
+
105
+
106
+
107
+
108
+ <h2>Instance Attribute Summary</h2>
109
+
110
+ <h3 class="inherited">Attributes included from <span class='object_link'><a href="Ballast/Emoji/Utils.html" title="Ballast::Emoji::Utils (module)">Ballast::Emoji::Utils</a></span></h3>
111
+ <p class="inherited"><span class='object_link'><a href="Ballast/Emoji/Utils.html#url_mapper-instance_method" title="Ballast::Emoji::Utils#url_mapper (method)">#url_mapper</a></span></p>
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+ <h2>Method Summary</h2>
122
+
123
+ <h3 class="inherited">Methods included from <span class='object_link'><a href="Ballast/Emoji/Utils.html" title="Ballast::Emoji::Utils (module)">Ballast::Emoji::Utils</a></span></h3>
124
+ <p class="inherited"><span class='object_link'><a href="Ballast/Emoji/Utils.html#enumerate-instance_method" title="Ballast::Emoji::Utils#enumerate (method)">enumerate</a></span>, <span class='object_link'><a href="Ballast/Emoji/Utils.html#replace-instance_method" title="Ballast::Emoji::Utils#replace (method)">replace</a></span>, <span class='object_link'><a href="Ballast/Emoji/Utils.html#replace_regex-instance_method" title="Ballast::Emoji::Utils#replace_regex (method)">replace_regex</a></span>, <span class='object_link'><a href="Ballast/Emoji/Utils.html#url_for-instance_method" title="Ballast::Emoji::Utils#url_for (method)">url_for</a></span></p>
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+ <h3 class="inherited">Methods included from <span class='object_link'><a href="Ballast/Emoji/Character.html" title="Ballast::Emoji::Character (module)">Ballast::Emoji::Character</a></span></h3>
135
+ <p class="inherited"><span class='object_link'><a href="Ballast/Emoji/Character.html#image_tag-instance_method" title="Ballast::Emoji::Character#image_tag (method)">#image_tag</a></span>, <span class='object_link'><a href="Ballast/Emoji/Character.html#markup-instance_method" title="Ballast::Emoji::Character#markup (method)">#markup</a></span>, <span class='object_link'><a href="Ballast/Emoji/Character.html#url-instance_method" title="Ballast::Emoji::Character#url (method)">#url</a></span></p>
136
+
137
+
138
+
139
+
140
+ </div>
141
+
142
+ <div id="footer">
143
+ Generated on Thu Aug 18 15:49:48 2016 by
144
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
145
+ 0.8.7.6 (ruby-2.3.0).
146
+ </div>
147
+
148
+ </body>
149
+ </html>
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
3
- # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
3
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
4
4
  #
5
5
 
6
6
  # PI: Ignore flog on this file.
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
3
- # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
3
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
4
4
  #
5
5
 
6
6
  module Ballast
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
3
- # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
3
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
4
4
  #
5
5
 
6
6
  module Ballast
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
3
- # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
3
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
4
4
  #
5
5
 
6
6
  module Ballast
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
3
- # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
3
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
4
4
  #
5
5
 
6
6
  module Ballast
@@ -0,0 +1,83 @@
1
+ #
2
+ # This file is part of the ballast gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
3
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
4
+ #
5
+
6
+ module Ballast
7
+ module Concerns
8
+ module JSONApi
9
+ # A concern to handle errors. It requires the Ajax concern.
10
+ module PaginationHandling
11
+ # Paginates a collection.
12
+ #
13
+ # @param collection [ActiveRecord::Relation] The collection to handle.
14
+ # @param sort_field [Symbol] The field to sort on.
15
+ # @param sort_order [Symbol] The sorting order.
16
+ # return [ActiveRecord::Relation] A paginated and sorted collection.
17
+ def paginate(collection, sort_field: :id, sort_order: :desc)
18
+ direction = @cursor.direction
19
+ value = @cursor.value
20
+
21
+ # Apply the query
22
+ collection = apply_value(collection, value, sort_field, sort_order)
23
+ collection = collection.limit(@cursor.size).order(sprintf("%s %s", sort_field, sort_order.upcase))
24
+
25
+ # If we're fetching previous we reverse the order to make sure we fetch the results adiacents to the previous request,
26
+ # then we reverse results to ensure the order requested
27
+ if direction != "next"
28
+ collection = collection.reverse_order
29
+ collection = collection.reverse
30
+ end
31
+
32
+ collection
33
+ end
34
+
35
+ # Returns the field used for pagination.
36
+ #
37
+ # @return [Symbol] The field used for pagination.
38
+ def pagination_field
39
+ @pagination_field ||= :id
40
+ end
41
+
42
+ # Returns whether pagination should be skipped in templates rendering for JSON API.
43
+ #
44
+ # @return [Boolean] Whether pagination should be skipped.
45
+ def pagination_skip?
46
+ @skip_pagination
47
+ end
48
+
49
+ # Returns whether pagination is supported for the current set of objects for JSON API.
50
+ #
51
+ # @return [Boolean] Whether pagination is supported.
52
+ def pagination_supported?
53
+ @objects.respond_to?(:first) && @objects.respond_to?(:last)
54
+ end
55
+
56
+ # Returns a URL to get a specific page of the current set of objects.
57
+ #
58
+ # @param key [String] The page to get. Supported values are `next`, `prev`, `previous` and `first`.
59
+ # @return [String] A URL.
60
+ def pagination_url(key = nil)
61
+ exist = @cursor.might_exist?(key, @objects)
62
+ exist ? url_for(request.params.merge(page: @cursor.save(@objects, key, field: pagination_field)).merge(only_path: false)) : nil
63
+ end
64
+
65
+ private
66
+
67
+ # :nodoc:
68
+ def apply_value(collection, value, sort_field, sort_order)
69
+ if value
70
+ if cursor.use_offset
71
+ collection = collection.offset(value)
72
+ else
73
+ value = DateTime.parse(value, PaginationCursor::TIMESTAMP_FORMAT) if collection.columns_hash[sort_field.to_s].type == :datetime
74
+ collection = collection.where(sprintf("%s %s ?", sort_field, @cursor.operator(sort_order)), value)
75
+ end
76
+ end
77
+
78
+ collection
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,235 @@
1
+ module Ballast
2
+ module Concerns
3
+ # A concern to handle JSON API backends.
4
+ module JSONApi
5
+ # A concern to handle JSON API requests.
6
+ module RequestHandling
7
+ # The default JSON API Content-Type.
8
+ CONTENT_TYPE = "application/vnd.api+json".freeze
9
+
10
+ # Adds Cross Origin Request (CORS) headers.
11
+ def request_handle_cors
12
+ headers["Access-Control-Allow-Origin"] = Rails.env.development? ? "http://#{request_source_host}:4200" : Rails.application.secrets.cors_source
13
+ headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS"
14
+ headers["Access-Control-Allow-Headers"] = "Content-Type, X-User-Email, X-User-Token"
15
+ headers["Access-Control-Max-Age"] = 1.year.to_i.to_s
16
+ end
17
+
18
+ # Validates a request.
19
+ #
20
+ # @return [Object] The request data
21
+ def request_validate
22
+ content_type = request_valid_content_type
23
+ request.format = :json
24
+ response.content_type = content_type unless Rails.env.development? && params["json"]
25
+
26
+ @cursor = PaginationCursor.new(params, :page)
27
+
28
+ params[:data] ||= HashWithIndifferentAccess.new
29
+
30
+ validate_data(content_type)
31
+ end
32
+
33
+ # Returns the current source host.
34
+ #
35
+ # @return [String] The current source host.
36
+ def request_source_host
37
+ @api_source ||= URI.parse(request.url).host
38
+ end
39
+
40
+ # Returns the valid Content-Type for JSON API requests.
41
+ #
42
+ # @return [String] The valid Content-Type for JSON API requests.
43
+ def request_valid_content_type
44
+ Ballast::Concerns::JSONApi::RequestHandling::CONTENT_TYPE
45
+ end
46
+
47
+ # Extract a model attributes from request data
48
+ #
49
+ # @param target [Object] The target object.
50
+ # @param type_field [Symbol] The field of the request data which contains the data type.
51
+ # @param attributes_field [Object] The field of the request data which contains the data attributes.
52
+ # @param relationships_field [Object] The field of the request data which contains the data relationships.
53
+ # @return [Hash] The model attributes.
54
+ def request_extract_model(target, type_field: :type, attributes_field: :attributes, relationships_field: :relationships)
55
+ data = params[:data]
56
+
57
+ request_validate_model_type(target, data, type_field)
58
+
59
+ data = data[attributes_field]
60
+ fail_request!(:bad_request, "Missing attributes in the \"attributes\" field.") if data.blank?
61
+
62
+ # Extract attributes using strong parameters
63
+ data = unembed_relationships(validate_attributes(data, target), target, relationships_field)
64
+
65
+ # Extract relationships
66
+ data.merge!(validate_relationships(params[:data], target, relationships_field))
67
+
68
+ data
69
+ end
70
+
71
+ # Casts attributes according to the target object definitions.
72
+ #
73
+ # @param target [Object] The target object.
74
+ # @param attributes [Hash] The attributes to cast.
75
+ # @return [Hash] The casted attributes.
76
+ def request_cast_attributes(target, attributes)
77
+ types = target.class.column_types
78
+
79
+ attributes.each do |k, v|
80
+ request_cast_attribute(target, attributes, types, k, v)
81
+ end
82
+
83
+ attributes
84
+ end
85
+
86
+ private
87
+
88
+ # :nodoc:
89
+ def validate_data(content_type)
90
+ if request.post? || request.patch?
91
+ raise(Errors::BadRequestError) unless request.content_type == content_type
92
+
93
+ request_load_data
94
+ raise(Errors::MissingDataError) unless params[:data].present?
95
+ end
96
+ end
97
+
98
+ # :nodoc:
99
+ def request_load_data
100
+ data_source =
101
+ begin
102
+ request.body.read
103
+ rescue
104
+ nil
105
+ end
106
+
107
+ return if data_source.blank?
108
+
109
+ data = ActiveSupport::JSON.decode(data_source)
110
+ params[:data] = data.fetch("data", {}).with_indifferent_access
111
+ rescue JSON::ParserError
112
+ raise(Errors::InvalidDataError)
113
+ end
114
+
115
+ # :nodoc:
116
+ def request_validate_model_type(target, data, type_field)
117
+ provided_type = data[type_field]
118
+ expected_type = sanitize_model_name(target.class.name)
119
+
120
+ return if sanitize_model_name(data[type_field]) == expected_type
121
+
122
+ fail_request!(
123
+ :bad_request, "#{provided_type.present? ? "Invalid type \"#{provided_type}\"" : "No type"} provided when type \"#{expected_type}\" was expected."
124
+ )
125
+ end
126
+
127
+ # :nodoc:
128
+ def request_cast_attribute(target, attributes, types, key, value)
129
+ case types[key].type
130
+ when :boolean then
131
+ Validators::BooleanValidator.parse(value, raise_errors: true)
132
+ attributes[key] = value.to_boolean
133
+ when :datetime
134
+ value = Validators::TimestampValidator.parse(value, raise_errors: true)
135
+ attributes[key] = value
136
+ end
137
+ rescue => e
138
+ target.additional_errors.add(key, e.message)
139
+ end
140
+
141
+ # :nodoc:
142
+ def sanitize_model_name(name)
143
+ name.ensure_string.underscore.singularize
144
+ end
145
+
146
+ # :nodoc:
147
+ def validate_attributes(data, target)
148
+ # Before performing the validation, copy all embedded data to a temporary hash and replace with boolean in order to pass validation
149
+ copied = {}
150
+
151
+ data.each do |k, v|
152
+ if v.is_a?(Hash)
153
+ copied[k] = v
154
+ data[k] = true
155
+ end
156
+ end
157
+
158
+ ActionController::Parameters.new(data).permit(target.class::ATTRIBUTES).merge(copied) # Now return by restoring copied attributes
159
+ rescue ActionController::UnpermittedParameters => e
160
+ e.params.map! { |s| sprintf("attributes.%s", s) }
161
+ raise e
162
+ end
163
+
164
+ # :nodoc:
165
+ def unembed_relationships(data, target, field)
166
+ return data unless defined?(target.class::RELATIONSHIPS)
167
+ relationships = target.class::RELATIONSHIPS
168
+
169
+ data.each do |k, v|
170
+ k = k.to_sym
171
+ next unless relationships.include?(k)
172
+
173
+ params[:data][field] ||= {}
174
+ params[:data][field][k] = {data: {type: sanitize_model_name(relationships[k] || k), id: v}}
175
+ data.delete(k)
176
+ end
177
+
178
+ data
179
+ end
180
+
181
+ # :nodoc:
182
+ def validate_relationships(data, target, field)
183
+ return {} unless defined?(target.class::RELATIONSHIPS)
184
+ relationships = target.class::RELATIONSHIPS
185
+
186
+ allowed = relationships.keys.reduce({}) do |accu, k|
187
+ accu[k] = {data: [:type, :id]}
188
+ accu
189
+ end
190
+
191
+ resolve_references(target, relationships, ActionController::Parameters.new(data[field]).permit(allowed))
192
+ rescue ActionController::UnpermittedParameters => e
193
+ e.params.map! { |s| sprintf("%s.%s", field, s) }
194
+ raise e
195
+ end
196
+
197
+ # :nodoc:
198
+ def resolve_references(target, relationships, references)
199
+ references.reduce({}) do |accu, (field, data)|
200
+ begin
201
+ expected, id, sanitized, type = prepare_resolution(data, field, relationships)
202
+ accu[field] = validate_reference(expected, id, sanitized, type)
203
+ rescue => e
204
+ raise e if e.is_a?(Lazier::Exceptions::Debug)
205
+ target.additional_errors.add(field, e.message)
206
+ end
207
+
208
+ accu
209
+ end
210
+ end
211
+
212
+ # :nodoc:
213
+ def validate_reference(expected, id, sanitized, type)
214
+ raise("Relationship does not contain the \"data.type\" attribute") if type.blank?
215
+ raise("Relationship does not contain the \"data.id\" attribute") if id.blank?
216
+ raise("Invalid relationship type \"#{type}\" provided for when type \"#{expected}\" was expected.") unless sanitized == sanitize_model_name(expected)
217
+
218
+ reference = expected.classify.constantize.find_with_any(id)
219
+ raise("Refers to a non existing \"#{sanitized}\" resource.") unless reference
220
+ reference
221
+ end
222
+
223
+ # :nodoc:
224
+ def prepare_resolution(data, field, relationships)
225
+ type = data.dig(:data, :type)
226
+ id = data.dig(:data, :id)
227
+ expected = sanitize_model_name(relationships[field.to_sym] || field.classify)
228
+ sanitized = sanitize_model_name(type)
229
+
230
+ [expected, id, sanitized, type]
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end