ballast 2.2.4 → 2.2.5

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