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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -1
- data/.travis-gemfile +2 -2
- data/.travis.yml +5 -3
- data/.yardopts +1 -1
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -1
- data/LICENSE.md +21 -0
- data/README.md +22 -17
- data/Rakefile +1 -1
- data/ballast.gemspec +1 -1
- data/{doc → docs}/Ballast.html +19 -21
- data/docs/Ballast/AjaxResponse.html +1470 -0
- data/{doc → docs}/Ballast/Concerns.html +3 -3
- data/{doc → docs}/Ballast/Concerns/AjaxHandling.html +33 -38
- data/{doc → docs}/Ballast/Concerns/Common.html +45 -52
- data/{doc → docs}/Ballast/Concerns/ErrorsHandling.html +13 -14
- data/docs/Ballast/Concerns/JSONApi.html +127 -0
- data/docs/Ballast/Concerns/JSONApi/PaginationHandling.html +617 -0
- data/docs/Ballast/Concerns/JSONApi/RequestHandling.html +775 -0
- data/docs/Ballast/Concerns/JSONApi/ResponseHandling.html +917 -0
- data/{doc → docs}/Ballast/Concerns/View.html +35 -42
- data/{doc → docs}/Ballast/Configuration.html +19 -22
- data/{doc → docs}/Ballast/Emoji.html +1 -1
- data/{doc → docs}/Ballast/Emoji/Character.html +15 -18
- data/docs/Ballast/Emoji/Utils.html +794 -0
- data/{doc → docs}/Ballast/Errors.html +1 -1
- data/{doc → docs}/Ballast/Errors/Base.html +16 -18
- data/{doc → docs}/Ballast/Errors/Failure.html +1 -1
- data/{doc → docs}/Ballast/Errors/InvalidDomain.html +1 -1
- data/{doc → docs}/Ballast/Errors/ValidationFailure.html +1 -1
- data/{doc → docs}/Ballast/Middlewares.html +1 -1
- data/{doc → docs}/Ballast/Middlewares/DefaultHost.html +16 -18
- data/docs/Ballast/RequestDomainMatcher.html +917 -0
- data/docs/Ballast/Service.html +1513 -0
- data/docs/Ballast/Service/Response.html +1270 -0
- data/{doc → docs}/Ballast/Version.html +5 -9
- data/{doc → docs}/_index.html +45 -1
- data/docs/class_list.html +58 -0
- data/{doc → docs}/css/common.css +0 -0
- data/{doc → docs}/css/full_list.css +0 -0
- data/{doc → docs}/css/style.css +0 -0
- data/{doc → docs}/file.README.html +7 -5
- data/{doc → docs}/file_list.html +0 -0
- data/{doc → docs}/frames.html +0 -0
- data/{doc → docs}/index.html +7 -5
- data/{doc → docs}/js/app.js +0 -0
- data/{doc → docs}/js/full_list.js +0 -0
- data/{doc → docs}/js/jquery.js +0 -0
- data/{doc → docs}/method_list.html +150 -36
- data/docs/top-level-namespace.html +149 -0
- data/lib/ballast.rb +1 -1
- data/lib/ballast/ajax_response.rb +1 -1
- data/lib/ballast/concerns/ajax_handling.rb +1 -1
- data/lib/ballast/concerns/common.rb +1 -1
- data/lib/ballast/concerns/errors_handling.rb +1 -1
- data/lib/ballast/concerns/json_api/pagination_handling.rb +83 -0
- data/lib/ballast/concerns/json_api/request_handling.rb +235 -0
- data/lib/ballast/concerns/json_api/response_handling.rb +70 -0
- data/lib/ballast/concerns/view.rb +1 -1
- data/lib/ballast/configuration.rb +1 -1
- data/lib/ballast/emoji.rb +1 -1
- data/lib/ballast/errors.rb +1 -1
- data/lib/ballast/middlewares/default_host.rb +1 -1
- data/lib/ballast/request_domain_matcher.rb +1 -1
- data/lib/ballast/service.rb +1 -1
- data/lib/ballast/version.rb +2 -2
- data/spec/ballast/ajax_response_spec.rb +1 -1
- data/spec/ballast/concerns/ajax_handling_spec.rb +1 -1
- data/spec/ballast/concerns/common_spec.rb +1 -1
- data/spec/ballast/concerns/errors_handling_spec.rb +1 -1
- data/spec/ballast/concerns/view_spec.rb +1 -1
- data/spec/ballast/configuration_spec.rb +1 -1
- data/spec/ballast/errors_spec.rb +1 -1
- data/spec/ballast/middlewares/default_host_spec.rb +1 -1
- data/spec/ballast/request_domain_matcher_spec.rb +1 -1
- data/spec/ballast_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +47 -39
- data/doc/Ballast/AjaxResponse.html +0 -1478
- data/doc/Ballast/Emoji/Utils.html +0 -799
- data/doc/Ballast/RequestDomainMatcher.html +0 -923
- data/doc/Ballast/Service.html +0 -1522
- data/doc/Ballast/Service/Response.html +0 -1278
- data/doc/class_list.html +0 -58
- 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
|
+
— 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> »
|
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>
|
data/lib/ballast.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
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
|