deep_unrest 0.1.35 → 0.1.36
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/app/controllers/deep_unrest/application_controller.rb +44 -7
- data/config/routes.rb +1 -0
- data/lib/deep_unrest/authorization/pundit_strategy.rb +6 -1
- data/lib/deep_unrest/concerns/map_temp_ids.rb +31 -2
- data/lib/deep_unrest/read.rb +5 -60
- data/lib/deep_unrest/version.rb +1 -1
- data/lib/deep_unrest/write.rb +210 -0
- data/lib/deep_unrest.rb +109 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49fe70a86a480793222a007af3de245afc7d2bba795d3e42ba0f2e74a5791c3b
|
4
|
+
data.tar.gz: 64ac3b1ffcb724b5bd5b8c15c37143da18241685cc36f76b3a7e63d9c568b461
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b824d05e3a002abd22057c337c6b5b948c9c54c92c148873bf4239aa47a5319bcf68dd212d9dbb3dc9093addda891a3e2bc17329677e8ae05fee5c9a245b451
|
7
|
+
data.tar.gz: eb20d4d68c51fb7bb383f1e34996841195f1c44a880e2348f839c39968048032a1b2bccabfe3d1738af5e31a92f3e11a5ac84f39371182ac8d1116e1dc02ad62
|
@@ -4,8 +4,11 @@ module DeepUnrest
|
|
4
4
|
class ApplicationController < ActionController::API
|
5
5
|
include DeepUnrest.authentication_concern
|
6
6
|
|
7
|
+
around_action :allow_nested_arrays, only: :update
|
8
|
+
|
7
9
|
@@temp_ids = {}
|
8
10
|
@@destroyed_entities = []
|
11
|
+
@@changed_entities = []
|
9
12
|
|
10
13
|
def context
|
11
14
|
{ current_user: current_user }
|
@@ -38,7 +41,32 @@ module DeepUnrest
|
|
38
41
|
instance_eval &DeepUnrest.before_read if DeepUnrest.before_read
|
39
42
|
|
40
43
|
results = DeepUnrest.perform_read(context, data, current_user)
|
41
|
-
render json: results, status:
|
44
|
+
render json: results, status: :ok
|
45
|
+
rescue DeepUnrest::Unauthorized => err
|
46
|
+
render json: err.message, status: :forbidden
|
47
|
+
rescue DeepUnrest::UnpermittedParams => err
|
48
|
+
render json: err.message, status: :method_not_allowed
|
49
|
+
end
|
50
|
+
|
51
|
+
def write
|
52
|
+
repaired_params = params[:data]
|
53
|
+
data = repaired_params[:data]
|
54
|
+
context = repaired_params[:context] || {}
|
55
|
+
context[:uuid] = request.uuid
|
56
|
+
context[:current_user] = current_user
|
57
|
+
|
58
|
+
instance_eval &DeepUnrest.before_update if DeepUnrest.before_update
|
59
|
+
|
60
|
+
results = DeepUnrest.perform_write(context, data, current_user)
|
61
|
+
render json: results, status: :ok
|
62
|
+
rescue DeepUnrest::Unauthorized => err
|
63
|
+
render json: err.message, status: :forbidden
|
64
|
+
rescue DeepUnrest::UnpermittedParams => err
|
65
|
+
render json: err.message, status: :method_not_allowed
|
66
|
+
rescue DeepUnrest::Conflict => err
|
67
|
+
render json: err.message, status: :conflict
|
68
|
+
ensure
|
69
|
+
@@temp_ids.delete(request.uuid)
|
42
70
|
end
|
43
71
|
|
44
72
|
def update
|
@@ -46,23 +74,26 @@ module DeepUnrest
|
|
46
74
|
context = allowed_write_params[:data][:context] || {}
|
47
75
|
context[:uuid] = request.uuid
|
48
76
|
context[:current_user] = current_user
|
49
|
-
data = repair_nested_params(
|
77
|
+
data = repair_nested_params(params)[:data][:data]
|
50
78
|
|
51
79
|
instance_eval &DeepUnrest.before_update if DeepUnrest.before_update
|
52
80
|
|
53
|
-
results = DeepUnrest.perform_update(context, data)
|
81
|
+
results = DeepUnrest.perform_update(context, data, current_user)
|
54
82
|
resp = { destroyed: results[:destroyed],
|
83
|
+
changed: results[:changed],
|
55
84
|
tempIds: results[:temp_ids] }
|
56
85
|
resp[:redirect] = results[:redirect_regex].call(redirect) if redirect
|
57
|
-
render json: resp, status:
|
86
|
+
render json: resp, status: :ok
|
58
87
|
rescue DeepUnrest::Unauthorized => err
|
59
|
-
render json: err.message, status:
|
88
|
+
render json: err.message, status: :forbidden
|
60
89
|
rescue DeepUnrest::UnpermittedParams => err
|
61
|
-
render json: err.message, status:
|
90
|
+
render json: err.message, status: :method_not_allowed
|
62
91
|
rescue DeepUnrest::Conflict => err
|
63
|
-
render json: err.message, status:
|
92
|
+
render json: err.message, status: :conflict
|
64
93
|
ensure
|
65
94
|
@@temp_ids.delete(request.uuid)
|
95
|
+
@@destroyed_entities.clear
|
96
|
+
@@changed_entities.clear
|
66
97
|
end
|
67
98
|
|
68
99
|
def current_user
|
@@ -76,5 +107,11 @@ module DeepUnrest
|
|
76
107
|
:errorPath,
|
77
108
|
{ attributes: {} }]])
|
78
109
|
end
|
110
|
+
|
111
|
+
def allow_nested_arrays
|
112
|
+
::ActionController::Parameters::PERMITTED_SCALAR_TYPES << Array
|
113
|
+
yield
|
114
|
+
::ActionController::Parameters::PERMITTED_SCALAR_TYPES - [Array]
|
115
|
+
end
|
79
116
|
end
|
80
117
|
end
|
data/config/routes.rb
CHANGED
@@ -31,12 +31,17 @@ module DeepUnrest
|
|
31
31
|
def self.get_entity_authorization(scope, user)
|
32
32
|
if %i[create update_all index destroy_all].include?(scope[:scope_type])
|
33
33
|
target = scope[:klass]
|
34
|
-
|
34
|
+
elsif scope[:scope]
|
35
|
+
# TODO: deprecate this part of the clause following write endpoint refactor
|
35
36
|
target = scope[:scope][:base].send(scope[:scope][:method],
|
36
37
|
*scope[:scope][:arguments])
|
38
|
+
else
|
39
|
+
target = scope[:klass].find(scope[:query][:id])
|
37
40
|
end
|
38
41
|
|
39
42
|
Pundit.policy!(user, target).send(get_policy_name(scope[:scope_type]))
|
43
|
+
rescue Pundit::NotDefinedError
|
44
|
+
false
|
40
45
|
end
|
41
46
|
|
42
47
|
def self.authorize(scopes, user)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepUnrest
|
2
4
|
module Concerns
|
3
5
|
module MapTempIds
|
@@ -5,9 +7,15 @@ module DeepUnrest
|
|
5
7
|
|
6
8
|
included do
|
7
9
|
attr_accessor :deep_unrest_context
|
10
|
+
attr_accessor :deep_unrest_query_uuid
|
8
11
|
attr_accessor :deep_unrest_temp_id
|
9
12
|
after_create :map_temp_id
|
10
13
|
after_destroy :track_destruction
|
14
|
+
after_save :track_changes
|
15
|
+
end
|
16
|
+
|
17
|
+
def pk
|
18
|
+
send self.class.primary_key
|
11
19
|
end
|
12
20
|
|
13
21
|
def map_temp_id
|
@@ -15,7 +23,7 @@ module DeepUnrest
|
|
15
23
|
'@@temp_ids'
|
16
24
|
)
|
17
25
|
return unless temp_id_map && @deep_unrest_temp_id
|
18
|
-
temp_id_map[@deep_unrest_context][@deep_unrest_temp_id] =
|
26
|
+
temp_id_map[@deep_unrest_context][@deep_unrest_temp_id] = pk
|
19
27
|
end
|
20
28
|
|
21
29
|
# the client needs to know which items were destroyed so it can clean up
|
@@ -27,10 +35,31 @@ module DeepUnrest
|
|
27
35
|
return unless destroyed
|
28
36
|
destroyed << {
|
29
37
|
type: self.class.to_s.pluralize.camelize(:lower),
|
30
|
-
id:
|
38
|
+
id: pk,
|
31
39
|
destroyed: true
|
32
40
|
}
|
33
41
|
end
|
42
|
+
|
43
|
+
# the client needs to know which items were charged so it can keep its
|
44
|
+
# local sync in store with the db
|
45
|
+
def track_changes
|
46
|
+
changed = DeepUnrest::ApplicationController.class_variable_get(
|
47
|
+
'@@changed_entities'
|
48
|
+
)
|
49
|
+
return unless changed && saved_changes?
|
50
|
+
changed << {
|
51
|
+
klass: self.class,
|
52
|
+
id: pk,
|
53
|
+
attributes: attribute_diff,
|
54
|
+
query_uuid: @deep_unrest_query_uuid
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def attribute_diff
|
59
|
+
saved_changes.each_with_object({}) do |(attr_name, (_old, val)), diff|
|
60
|
+
diff[attr_name] = val
|
61
|
+
end
|
62
|
+
end
|
34
63
|
end
|
35
64
|
end
|
36
65
|
end
|
data/lib/deep_unrest/read.rb
CHANGED
@@ -2,10 +2,6 @@
|
|
2
2
|
|
3
3
|
module DeepUnrest
|
4
4
|
module Read
|
5
|
-
# def self.map_included(params, addr)
|
6
|
-
# params[:include].map { |k, v| create_read_mappings({ "#{k}": v }, [*addr, :include]) }
|
7
|
-
# end
|
8
|
-
|
9
5
|
def self.create_read_mappings(params, addr = [])
|
10
6
|
return unless params
|
11
7
|
params.map do |k, v|
|
@@ -19,7 +15,7 @@ module DeepUnrest
|
|
19
15
|
addr: resource_addr,
|
20
16
|
key: k.camelize(:lower),
|
21
17
|
uuid: uuid,
|
22
|
-
query: deep_underscore_keys(v) },
|
18
|
+
query: DeepUnrest.deep_underscore_keys(v) },
|
23
19
|
*create_read_mappings(v[:include], [*resource_addr, :include])]
|
24
20
|
end.flatten.compact
|
25
21
|
end
|
@@ -28,31 +24,9 @@ module DeepUnrest
|
|
28
24
|
str.pluralize == str && str.singularize != str
|
29
25
|
end
|
30
26
|
|
31
|
-
def self.serialize_result(ctx, item)
|
32
|
-
JSONAPI::ResourceSerializer.new(item[:resource],
|
33
|
-
fields: {
|
34
|
-
"#{item[:key].pluralize}": item[:query][:fields].map(&:underscore).map(&:to_sym)
|
35
|
-
}).serialize_to_hash(item[:resource].new(item[:record], ctx))[:data]
|
36
|
-
end
|
37
|
-
|
38
27
|
def self.serialize_results(ctx, data)
|
39
28
|
data.each do |item|
|
40
|
-
item[:serialized_result] = serialize_result(ctx, item)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.deep_underscore_keys(query)
|
45
|
-
query.deep_transform_keys! do |key|
|
46
|
-
k = begin
|
47
|
-
key.to_s.underscore
|
48
|
-
rescue StandardError
|
49
|
-
key
|
50
|
-
end
|
51
|
-
begin
|
52
|
-
k.to_sym
|
53
|
-
rescue StandardError
|
54
|
-
key
|
55
|
-
end
|
29
|
+
item[:serialized_result] = DeepUnrest.serialize_result(ctx, item)
|
56
30
|
end
|
57
31
|
end
|
58
32
|
|
@@ -184,53 +158,24 @@ module DeepUnrest
|
|
184
158
|
[included, meta]
|
185
159
|
end
|
186
160
|
|
187
|
-
def self.set_attr(hash, path, val, cursor = nil)
|
188
|
-
cursor ||= hash
|
189
|
-
key = path.shift
|
190
|
-
|
191
|
-
if path.empty?
|
192
|
-
case cursor
|
193
|
-
when Array
|
194
|
-
cursor << val
|
195
|
-
when Hash
|
196
|
-
cursor[key] = val
|
197
|
-
end
|
198
|
-
return hash
|
199
|
-
end
|
200
|
-
|
201
|
-
next_cursor = case key
|
202
|
-
when /\[\]$/
|
203
|
-
cursor[key.gsub('[]', '')] ||= []
|
204
|
-
else
|
205
|
-
cursor[key] ||= {}
|
206
|
-
end
|
207
|
-
|
208
|
-
set_attr(hash, path, val, next_cursor)
|
209
|
-
end
|
210
161
|
|
211
162
|
def self.format_response(mappings)
|
212
163
|
response = {}
|
213
164
|
mappings.each do |mapping|
|
214
|
-
set_attr(response, mapping[:addr], mapping[:serialized_result])
|
165
|
+
DeepUnrest.set_attr(response, mapping[:addr], mapping[:serialized_result])
|
215
166
|
end
|
216
167
|
response
|
217
168
|
end
|
218
169
|
|
219
|
-
def self.collect_authorized_scopes(mappings, user)
|
220
|
-
mappings.each do |mapping|
|
221
|
-
mapping[:scope] = DeepUnrest.authorization_strategy.get_authorized_scope(user, mapping[:klass])
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
170
|
def self.read(ctx, params, user)
|
226
171
|
# create mappings for assembly / disassembly
|
227
172
|
mappings = create_read_mappings(params.to_unsafe_h)
|
228
173
|
|
229
174
|
# authorize user for requested scope(s)
|
230
|
-
DeepUnrest.authorization_strategy.authorize(mappings, user)
|
175
|
+
DeepUnrest.authorization_strategy.authorize(mappings, user)
|
231
176
|
|
232
177
|
# collect authorized scopes
|
233
|
-
collect_authorized_scopes(mappings, user)
|
178
|
+
DeepUnrest.collect_authorized_scopes(mappings, user)
|
234
179
|
|
235
180
|
# read data
|
236
181
|
data, meta = execute_queries(mappings)
|
data/lib/deep_unrest/version.rb
CHANGED
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepUnrest
|
4
|
+
module Write
|
5
|
+
def self.get_scope_type(item)
|
6
|
+
return :destroy if item[:destroy]
|
7
|
+
return :show if item[:readOnly] || item[:attributes].blank?
|
8
|
+
return :create if item[:id] && DeepUnrest.temp_id?(item[:id].to_s)
|
9
|
+
:update
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.create_write_mappings(params, addr = [])
|
13
|
+
return unless params
|
14
|
+
params.map do |k, v|
|
15
|
+
resource_addr = [*addr, k]
|
16
|
+
uuid = SecureRandom.uuid
|
17
|
+
v[:uuid] = uuid
|
18
|
+
[{ klass: k.singularize.classify.constantize,
|
19
|
+
policy: "#{k.singularize.classify}Policy".constantize,
|
20
|
+
resource: "#{k.singularize.classify}Resource".constantize,
|
21
|
+
scope_type: get_scope_type(v),
|
22
|
+
addr: resource_addr,
|
23
|
+
key: k.camelize(:lower),
|
24
|
+
uuid: uuid,
|
25
|
+
query: DeepUnrest.deep_underscore_keys(v) },
|
26
|
+
*create_write_mappings(v[:included], [*resource_addr, :include])]
|
27
|
+
end.flatten.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.append_ar_paths(mappings)
|
31
|
+
mappings.each do |item|
|
32
|
+
item[:ar_addr] = []
|
33
|
+
item[:addr].each_with_index do |segment, i|
|
34
|
+
next if segment == :include
|
35
|
+
item[:ar_addr] << if item[:addr][i - 1] == :include
|
36
|
+
"#{segment}_attributes".to_sym
|
37
|
+
else
|
38
|
+
segment
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.authorize_attributes(mappings, ctx)
|
45
|
+
unauthorized_items = []
|
46
|
+
|
47
|
+
mappings.reject { |m| m[:scope_type] == :show }
|
48
|
+
.reject { |m| m[:destroy] }
|
49
|
+
.each do |item|
|
50
|
+
attributes = item.dig(:query, :attributes) || {}
|
51
|
+
resource = item[:resource]
|
52
|
+
p = JSONAPI::RequestParser.new
|
53
|
+
p.resource_klass = resource
|
54
|
+
opts = if item[:scope_type] == :create
|
55
|
+
resource.creatable_fields(ctx)
|
56
|
+
else
|
57
|
+
resource.updatable_fields(ctx)
|
58
|
+
end
|
59
|
+
|
60
|
+
p.parse_params({ attributes: attributes }, opts)[:attributes]
|
61
|
+
rescue JSONAPI::Exceptions::ParameterNotAllowed
|
62
|
+
unpermitted_keys = attributes.keys.map(&:to_sym) - opts
|
63
|
+
item[:errors] = unpermitted_keys.each_with_object({}) do |attr_key, memo|
|
64
|
+
memo[attr_key] = 'Unpermitted parameter'
|
65
|
+
end
|
66
|
+
unauthorized_items << item
|
67
|
+
end
|
68
|
+
|
69
|
+
return if unauthorized_items.blank?
|
70
|
+
|
71
|
+
msg = serialize_errors(unauthorized_items)
|
72
|
+
raise DeepUnrest::UnpermittedParams, msg
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.build_mutation_bodies(mappings)
|
76
|
+
mappings.reject { |m| m[:scope_type] == :show }
|
77
|
+
.each_with_object({}) do |item, memo|
|
78
|
+
# TODO: use pkey instead of "id"
|
79
|
+
next_attrs = item.dig(:query, :attributes || {})
|
80
|
+
.deep_symbolize_keys
|
81
|
+
update_body = { id: item.dig(:query, :id),
|
82
|
+
deep_unrest_query_uuid: item.dig(:query, :uuid),
|
83
|
+
**next_attrs }
|
84
|
+
update_body[:_destroy] = true if item[:scope_type] == :destroy
|
85
|
+
DeepUnrest.set_attr(memo, item[:ar_addr].clone, update_body)
|
86
|
+
|
87
|
+
item[:mutate] = memo.fetch(*item[:ar_addr]) if item[:ar_addr].size == 1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.execute_queries(mappings, context)
|
92
|
+
ActiveRecord::Base.transaction do
|
93
|
+
mappings.select { |m| m[:mutate] }.map do |item|
|
94
|
+
record = case item[:scope_type]
|
95
|
+
when :update
|
96
|
+
model = item[:klass].find(item.dig(:query, :id))
|
97
|
+
model.assign_attributes(item[:mutate])
|
98
|
+
resource = item[:resource].new(model, context)
|
99
|
+
resource.run_callbacks :save do
|
100
|
+
resource.run_callbacks :update do
|
101
|
+
model.save
|
102
|
+
model
|
103
|
+
end
|
104
|
+
end
|
105
|
+
when :create
|
106
|
+
model = item[:klass].new(item[:mutate])
|
107
|
+
resource = item[:resource].new(model, context)
|
108
|
+
resource.run_callbacks :save do
|
109
|
+
resource.run_callbacks :create do
|
110
|
+
resource._model.save
|
111
|
+
resource._model
|
112
|
+
end
|
113
|
+
end
|
114
|
+
when :destroy
|
115
|
+
model = item[:klass].find(id)
|
116
|
+
resource = item[:resource].new(model, context)
|
117
|
+
resource.run_callbacks :remove do
|
118
|
+
item[:klass].destroy(id)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
result = { record: record }
|
123
|
+
if item[:temp_id]
|
124
|
+
result[:temp_ids] = {}
|
125
|
+
result[:temp_ids][item[:temp_id]] = record.id
|
126
|
+
end
|
127
|
+
result
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.serialize_changes(ctx, mappings, changed)
|
133
|
+
changed.select { |c| c[:query_uuid] }
|
134
|
+
.each_with_object({}) do |c, memo|
|
135
|
+
mapping = mappings.find { |m| m.dig(:query, :uuid) == c[:query_uuid] }
|
136
|
+
mapping[:query][:fields] = c[:attributes].keys
|
137
|
+
mapping[:record] = c[:klass].new(id: c[:id])
|
138
|
+
mapping[:record].assign_attributes(c[:attributes])
|
139
|
+
result = DeepUnrest.serialize_result(ctx, mapping)
|
140
|
+
DeepUnrest.set_attr(memo, mapping[:addr], result)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.serialize_errors(mappings)
|
145
|
+
{ errors: mappings.each_with_object({}) do |item, memo|
|
146
|
+
err = {
|
147
|
+
id: item.dig(:query, :id),
|
148
|
+
type: item.dig(:query, :type),
|
149
|
+
attributes: item[:errors,]
|
150
|
+
}
|
151
|
+
DeepUnrest.set_attr(memo, [*item[:addr]], err)
|
152
|
+
end }.to_json
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.write(ctx, params, user)
|
156
|
+
temp_id_map = DeepUnrest::ApplicationController.class_variable_get(
|
157
|
+
'@@temp_ids'
|
158
|
+
)
|
159
|
+
|
160
|
+
# create mappings for assembly / disassembly
|
161
|
+
mappings = create_write_mappings(params.to_unsafe_h)
|
162
|
+
|
163
|
+
# authorize user for requested scope(s)
|
164
|
+
DeepUnrest.authorization_strategy.authorize(mappings, user)
|
165
|
+
|
166
|
+
authorize_attributes(mappings, ctx)
|
167
|
+
|
168
|
+
# collect authorized scopes
|
169
|
+
# DeepUnrest.collect_authorized_scopes(mappings, user)
|
170
|
+
append_ar_paths(mappings)
|
171
|
+
|
172
|
+
# bulid update arguments
|
173
|
+
build_mutation_bodies(mappings)
|
174
|
+
|
175
|
+
# convert temp_ids from ids to non-activerecord attributes
|
176
|
+
DeepUnrest.convert_temp_ids!(ctx[:uuid], mappings)
|
177
|
+
|
178
|
+
# save data, run callbaks
|
179
|
+
results = execute_queries(mappings, ctx)
|
180
|
+
|
181
|
+
# check results for errors
|
182
|
+
errors = results.map { |res| DeepUnrest.format_error_keys(res) }
|
183
|
+
.compact
|
184
|
+
.reject(&:empty?)
|
185
|
+
.compact
|
186
|
+
|
187
|
+
if errors.empty?
|
188
|
+
destroyed = DeepUnrest::ApplicationController.class_variable_get(
|
189
|
+
'@@destroyed_entities'
|
190
|
+
)
|
191
|
+
|
192
|
+
changed = DeepUnrest::ApplicationController.class_variable_get(
|
193
|
+
'@@changed_entities'
|
194
|
+
)
|
195
|
+
|
196
|
+
return {
|
197
|
+
temp_ids: temp_id_map[ctx[:uuid]],
|
198
|
+
destroyed: destroyed,
|
199
|
+
changed: serialize_changes(ctx, mappings, changed)
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
# map errors to their sources
|
204
|
+
formatted_errors = { errors: map_errors_to_param_keys(scopes, errors) }
|
205
|
+
|
206
|
+
# raise error if there are any errors
|
207
|
+
raise DeepUnrest::Conflict, formatted_errors.to_json
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
data/lib/deep_unrest.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'deep_unrest/engine'
|
4
4
|
require 'deep_unrest/read'
|
5
|
+
require 'deep_unrest/write'
|
5
6
|
|
6
7
|
# workaronud for rails bug with association indices.
|
7
8
|
# see https://github.com/rails/rails/pull/24728
|
@@ -15,7 +16,7 @@ module ActiveRecord
|
|
15
16
|
new_record,
|
16
17
|
autosave)
|
17
18
|
if new_record || autosave
|
18
|
-
association
|
19
|
+
association&.target
|
19
20
|
else
|
20
21
|
association.target.find_all(&:new_record?)
|
21
22
|
end
|
@@ -167,7 +168,7 @@ module DeepUnrest
|
|
167
168
|
end
|
168
169
|
|
169
170
|
p.parse_params({ attributes: attributes }, opts)[:attributes]
|
170
|
-
rescue JSONAPI::Exceptions::
|
171
|
+
rescue JSONAPI::Exceptions::ParameterNotAllowed
|
171
172
|
unpermitted_keys = attributes.keys.map(&:to_sym) - opts
|
172
173
|
msg = "Attributes #{unpermitted_keys} of #{type.classify} not allowed"
|
173
174
|
msg += " to #{user.class} with id '#{user.id}'" if user
|
@@ -213,6 +214,7 @@ module DeepUnrest
|
|
213
214
|
|
214
215
|
def self.parse_id(id_str)
|
215
216
|
return false if id_str.nil?
|
217
|
+
return id_str if id_str.is_a? Integer
|
216
218
|
id_match = id_str.match(/^\.?(?<id>[\w\-]+)$/)
|
217
219
|
id_match && id_match[:id]
|
218
220
|
end
|
@@ -368,10 +370,34 @@ module DeepUnrest
|
|
368
370
|
(non_dupes + merged).flatten
|
369
371
|
end
|
370
372
|
|
373
|
+
def self.set_attr(hash, path, val, cursor = nil)
|
374
|
+
cursor ||= hash
|
375
|
+
key = path.shift
|
376
|
+
|
377
|
+
if path.empty?
|
378
|
+
case cursor
|
379
|
+
when Array
|
380
|
+
cursor << val
|
381
|
+
when Hash
|
382
|
+
cursor[key] = val
|
383
|
+
end
|
384
|
+
return hash
|
385
|
+
end
|
386
|
+
|
387
|
+
next_cursor = case key
|
388
|
+
when /\[\]$/
|
389
|
+
cursor[key.gsub('[]', '')] ||= []
|
390
|
+
else
|
391
|
+
cursor[key] ||= {}
|
392
|
+
end
|
393
|
+
|
394
|
+
set_attr(hash, path, val, next_cursor)
|
395
|
+
end
|
396
|
+
|
371
397
|
def self.build_mutation_body(ops, scopes, user)
|
372
398
|
err_path_memo = {}
|
373
399
|
ops.each_with_object(HashWithIndifferentAccess.new({})) do |op, memo|
|
374
|
-
memo.deep_merge!(build_mutation_fragment(op, scopes, user, err_path_memo)) do |
|
400
|
+
memo.deep_merge!(build_mutation_fragment(op, scopes, user, err_path_memo)) do |_key, a, b|
|
375
401
|
if a.is_a? Array
|
376
402
|
combine_arrays(a, b)
|
377
403
|
else
|
@@ -516,7 +542,43 @@ module DeepUnrest
|
|
516
542
|
DeepUnrest::Read.read(ctx, params, user)
|
517
543
|
end
|
518
544
|
|
519
|
-
def self.
|
545
|
+
def self.perform_write(ctx, params, user)
|
546
|
+
DeepUnrest::Write.write(ctx, params, user)
|
547
|
+
end
|
548
|
+
|
549
|
+
def self.serialize_changes(diffs, user)
|
550
|
+
ctx = { current_user: user }
|
551
|
+
diffs.each do |diff|
|
552
|
+
diff[:resource] = diff[:attributes]
|
553
|
+
pk = diff[:klass].primary_key
|
554
|
+
diff[:resource][pk] = diff[:id]
|
555
|
+
diff[:model] = diff[:klass].new(diff[:resource])
|
556
|
+
end
|
557
|
+
|
558
|
+
allowed_models = diffs.select do |diff|
|
559
|
+
scope = DeepUnrest.authorization_strategy
|
560
|
+
.get_authorized_scope(user,
|
561
|
+
diff[:klass])
|
562
|
+
scope.exists?(diff[:id])
|
563
|
+
rescue NameError
|
564
|
+
false
|
565
|
+
end
|
566
|
+
|
567
|
+
resources = allowed_models.map do |diff|
|
568
|
+
resource_klass = get_resource(diff[:klass].to_s)
|
569
|
+
fields = {}
|
570
|
+
keys = diff[:resource].keys.map(&:to_sym)
|
571
|
+
fields[to_assoc(diff[:klass].to_s.pluralize)] = keys
|
572
|
+
|
573
|
+
JSONAPI::ResourceSerializer.new(
|
574
|
+
resource_klass,
|
575
|
+
fields: fields
|
576
|
+
).serialize_to_hash(resource_klass.new(diff[:model], ctx))[:data]
|
577
|
+
end
|
578
|
+
resources.select { |item| item.dig('attributes') }.compact
|
579
|
+
end
|
580
|
+
|
581
|
+
def self.perform_update(ctx, params, user)
|
520
582
|
temp_id_map = DeepUnrest::ApplicationController.class_variable_get(
|
521
583
|
'@@temp_ids'
|
522
584
|
)
|
@@ -556,10 +618,18 @@ module DeepUnrest
|
|
556
618
|
destroyed = DeepUnrest::ApplicationController.class_variable_get(
|
557
619
|
'@@destroyed_entities'
|
558
620
|
)
|
621
|
+
|
622
|
+
changed = DeepUnrest::ApplicationController.class_variable_get(
|
623
|
+
'@@changed_entities'
|
624
|
+
)
|
625
|
+
|
626
|
+
diff = serialize_changes(changed, user)
|
627
|
+
|
559
628
|
return {
|
560
629
|
redirect_regex: build_redirect_regex(temp_id_map[uuid]),
|
561
630
|
temp_ids: temp_id_map[uuid],
|
562
|
-
destroyed: destroyed
|
631
|
+
destroyed: destroyed,
|
632
|
+
changed: diff
|
563
633
|
}
|
564
634
|
end
|
565
635
|
|
@@ -569,4 +639,38 @@ module DeepUnrest
|
|
569
639
|
# raise error if there are any errors
|
570
640
|
raise Conflict, formatted_errors.to_json unless formatted_errors.empty?
|
571
641
|
end
|
642
|
+
|
643
|
+
### SHARED ###
|
644
|
+
def self.deep_underscore_keys(query)
|
645
|
+
query.deep_transform_keys! do |key|
|
646
|
+
k = begin
|
647
|
+
key.to_s.underscore
|
648
|
+
rescue StandardError
|
649
|
+
key
|
650
|
+
end
|
651
|
+
begin
|
652
|
+
k.to_sym
|
653
|
+
rescue StandardError
|
654
|
+
key
|
655
|
+
end
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
def self.collect_authorized_scopes(mappings, user)
|
660
|
+
mappings.each do |mapping|
|
661
|
+
mapping[:scope] = DeepUnrest.authorization_strategy.get_authorized_scope(user, mapping[:klass])
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
def self.serialize_result(ctx, item)
|
666
|
+
# item[:resource].exclude_links :none
|
667
|
+
resource_instance = item[:resource].new(item[:record], ctx)
|
668
|
+
JSONAPI::ResourceSerializer.new(
|
669
|
+
item[:resource],
|
670
|
+
fields: {
|
671
|
+
"#{item[:key].pluralize}": item[:query][:fields].map(&:underscore)
|
672
|
+
.map(&:to_sym)
|
673
|
+
}
|
674
|
+
).serialize_to_hash(resource_instance)[:data]
|
675
|
+
end
|
572
676
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deep_unrest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.36
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lynn Hurley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -151,6 +151,7 @@ files:
|
|
151
151
|
- lib/deep_unrest/paginators/basic.rb
|
152
152
|
- lib/deep_unrest/read.rb
|
153
153
|
- lib/deep_unrest/version.rb
|
154
|
+
- lib/deep_unrest/write.rb
|
154
155
|
- lib/tasks/deep_unrest_tasks.rake
|
155
156
|
homepage: https://github.com/graveflex/deep_unrest
|
156
157
|
licenses:
|