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