safrano 0.4.1 → 0.4.6
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/lib/core_ext/Dir/iter.rb +18 -0
- data/lib/core_ext/Hash/transform.rb +21 -0
- data/lib/core_ext/Integer/edm.rb +13 -0
- data/lib/core_ext/REXML/Document/output.rb +16 -0
- data/lib/core_ext/String/convert.rb +25 -0
- data/lib/core_ext/String/edm.rb +13 -0
- data/lib/core_ext/dir.rb +3 -0
- data/lib/core_ext/hash.rb +3 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/rexml.rb +3 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/odata/attribute.rb +15 -10
- data/lib/odata/batch.rb +15 -13
- data/lib/odata/collection.rb +144 -535
- data/lib/odata/collection_filter.rb +47 -40
- data/lib/odata/collection_media.rb +155 -99
- data/lib/odata/collection_order.rb +50 -37
- data/lib/odata/common_logger.rb +36 -34
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +183 -216
- data/lib/odata/error.rb +195 -31
- data/lib/odata/expand.rb +126 -0
- data/lib/odata/filter/base.rb +74 -0
- data/lib/odata/filter/error.rb +49 -6
- data/lib/odata/filter/parse.rb +44 -36
- data/lib/odata/filter/sequel.rb +136 -67
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +26 -19
- data/lib/odata/filter/tree.rb +113 -63
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +639 -0
- data/lib/odata/navigation_attribute.rb +44 -61
- data/lib/odata/relations.rb +5 -5
- data/lib/odata/select.rb +54 -0
- data/lib/odata/transition.rb +71 -0
- data/lib/odata/url_parameters.rb +128 -37
- data/lib/odata/walker.rb +20 -10
- data/lib/safrano.rb +17 -37
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +29 -104
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +39 -43
- data/lib/safrano/rack_app.rb +68 -67
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
- data/lib/safrano/request.rb +102 -51
- data/lib/safrano/response.rb +5 -3
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +274 -219
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +17 -29
- metadata +34 -11
data/lib/odata/entity.rb
CHANGED
@@ -1,44 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'rexml/document'
|
3
5
|
require 'safrano.rb'
|
4
|
-
require 'odata/
|
6
|
+
require 'odata/model_ext.rb' # required for self.class.entity_type_name ??
|
5
7
|
require_relative 'navigation_attribute'
|
6
8
|
|
7
|
-
module
|
9
|
+
module Safrano
|
8
10
|
# this will be mixed in the Model classes (subclasses of Sequel Model)
|
9
11
|
module EntityBase
|
10
12
|
attr_reader :params
|
11
|
-
attr_reader :uribase
|
12
13
|
|
13
|
-
include
|
14
|
-
|
14
|
+
include Safrano::NavigationInfo
|
15
|
+
|
15
16
|
# methods related to transitions to next state (cf. walker)
|
16
17
|
module Transitions
|
17
18
|
def allowed_transitions
|
18
|
-
|
19
|
-
Safrano::TransitionEnd,
|
20
|
-
Safrano::TransitionCount,
|
21
|
-
Safrano::TransitionLinks,
|
22
|
-
Safrano::TransitionValue,
|
23
|
-
Safrano::Transition.new(self.class.transition_attribute_regexp,
|
24
|
-
trans: 'transition_attribute')
|
25
|
-
]
|
26
|
-
if (ncurgx = self.class.nav_collection_url_regexp)
|
27
|
-
alltr <<
|
28
|
-
Safrano::Transition.new(%r{\A/(#{ncurgx})(.*)\z},
|
29
|
-
trans: 'transition_nav_collection')
|
30
|
-
|
31
|
-
end
|
32
|
-
if (neurgx = self.class.nav_entity_url_regexp)
|
33
|
-
alltr <<
|
34
|
-
Safrano::Transition.new(%r{\A/(#{neurgx})(.*)\z},
|
35
|
-
trans: 'transition_nav_entity')
|
36
|
-
end
|
37
|
-
alltr
|
19
|
+
self.class.entity_allowed_transitions
|
38
20
|
end
|
39
21
|
|
40
22
|
def transition_end(_match_result)
|
41
|
-
|
23
|
+
Safrano::Transition::RESULT_END
|
42
24
|
end
|
43
25
|
|
44
26
|
def transition_count(_match_result)
|
@@ -56,8 +38,7 @@ module OData
|
|
56
38
|
|
57
39
|
def transition_attribute(match_result)
|
58
40
|
attrib = match_result[1]
|
59
|
-
|
60
|
-
[OData::Attribute.new(self, attrib), :run]
|
41
|
+
[Safrano::Attribute.new(self, attrib), :run]
|
61
42
|
end
|
62
43
|
|
63
44
|
def transition_nav_collection(match_result)
|
@@ -69,17 +50,26 @@ module OData
|
|
69
50
|
attrib = match_result[1]
|
70
51
|
[get_related_entity(attrib), :run]
|
71
52
|
end
|
53
|
+
|
54
|
+
def transition_invalid_attribute(match_result)
|
55
|
+
invalid_attrib = match_result[1]
|
56
|
+
[nil, :error, Safrano::ErrorNotFoundSegment.new(invalid_attrib)]
|
57
|
+
end
|
72
58
|
end
|
73
59
|
|
74
60
|
include Transitions
|
75
61
|
|
62
|
+
# for testing only?
|
63
|
+
def ==(other)
|
64
|
+
((self.class.type_name == other.class.type_name) and (@values == other.values))
|
65
|
+
end
|
66
|
+
|
76
67
|
def nav_values
|
77
68
|
@nav_values = {}
|
78
69
|
|
79
70
|
self.class.nav_entity_attribs&.each_key do |na_str|
|
80
71
|
@nav_values[na_str.to_sym] = send(na_str)
|
81
72
|
end
|
82
|
-
|
83
73
|
@nav_values
|
84
74
|
end
|
85
75
|
|
@@ -91,50 +81,43 @@ module OData
|
|
91
81
|
@nav_coll
|
92
82
|
end
|
93
83
|
|
94
|
-
def uri
|
95
|
-
"
|
84
|
+
def uri
|
85
|
+
@odata_pk ||= "(#{pk_uri})"
|
86
|
+
"#{self.class.uri}#{@odata_pk}"
|
96
87
|
end
|
88
|
+
|
97
89
|
D = 'd'.freeze
|
98
|
-
|
99
|
-
|
90
|
+
DJ_OPEN = '{"d":'.freeze
|
91
|
+
DJ_CLOSE = '}'.freeze
|
92
|
+
|
100
93
|
# Json formatter for a single entity (probably OData V1/V2 like)
|
101
|
-
def to_odata_json(
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
94
|
+
def to_odata_json(request:)
|
95
|
+
template = self.class.output_template(expand_list: @uparms.expand.template,
|
96
|
+
select: @uparms.select)
|
97
|
+
innerj = request.service.get_entity_odata_h(entity: self,
|
98
|
+
template: template).to_json
|
99
|
+
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
106
100
|
end
|
107
101
|
|
108
102
|
# Json formatter for a single entity reached by navigation $links
|
109
103
|
def to_odata_onelink_json(service:)
|
110
|
-
innerj = service.get_entity_odata_link_h(entity: self
|
111
|
-
|
112
|
-
"#{DJopen}#{innerj}#{DJclose}"
|
104
|
+
innerj = service.get_entity_odata_link_h(entity: self).to_json
|
105
|
+
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
113
106
|
end
|
114
107
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
# (and the inverted transformation is in test/client.rb)
|
121
|
-
# will require a more systematic solution some day
|
122
|
-
values_for_odata.transform_values! { |v|
|
123
|
-
case v
|
124
|
-
when Time
|
125
|
-
# try to get back the database time zone and value
|
126
|
-
(v + v.gmt_offset).utc.to_datetime
|
127
|
-
else
|
128
|
-
v
|
129
|
-
end
|
130
|
-
}
|
108
|
+
def selected_values_for_odata(cols)
|
109
|
+
allvals = values_for_odata
|
110
|
+
selvals = {}
|
111
|
+
cols.map(&:to_sym).each { |k| selvals[k] = allvals[k] if allvals.key?(k) }
|
112
|
+
selvals
|
131
113
|
end
|
132
114
|
|
133
|
-
# post
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
115
|
+
# some clients wrongly expect post payload with the new entity in an array
|
116
|
+
# TODO quirks array mode !
|
117
|
+
def to_odata_array_json(request:)
|
118
|
+
innerj = request.service.get_coll_odata_h(array: [self],
|
119
|
+
template: self.class.default_template).to_json
|
120
|
+
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
138
121
|
end
|
139
122
|
|
140
123
|
def type_name
|
@@ -143,32 +126,37 @@ module OData
|
|
143
126
|
|
144
127
|
def copy_request_infos(req)
|
145
128
|
@params = req.params
|
146
|
-
@uribase = req.uribase
|
147
129
|
@do_links = req.walker.do_links
|
130
|
+
@uparms = UrlParameters4Single.new(self, @params)
|
148
131
|
end
|
149
132
|
|
150
|
-
|
151
|
-
def odata_get(req)
|
152
|
-
copy_request_infos(req)
|
153
|
-
|
133
|
+
def odata_get_output(req)
|
154
134
|
if req.walker.media_value
|
155
135
|
odata_media_value_get(req)
|
156
136
|
elsif req.accept?(APPJSON)
|
137
|
+
# json is default content type so we dont need to specify it here again
|
157
138
|
if req.walker.do_links
|
158
|
-
[200,
|
139
|
+
[200, EMPTY_HASH, [to_odata_onelink_json(service: req.service)]]
|
159
140
|
else
|
160
|
-
[200,
|
141
|
+
[200, EMPTY_HASH, [to_odata_json(request: req)]]
|
161
142
|
end
|
162
143
|
else # TODO: other formats
|
163
144
|
415
|
164
145
|
end
|
165
146
|
end
|
166
|
-
|
147
|
+
|
148
|
+
# Finally Process REST verbs...
|
149
|
+
def odata_get(req)
|
150
|
+
copy_request_infos(req)
|
151
|
+
@uparms.check_all.tap_valid { return odata_get_output(req) }
|
152
|
+
.tap_error { |e| return e.odata_get(req) }
|
153
|
+
end
|
154
|
+
|
167
155
|
DELETE_REL_AND_ENTY = lambda do |entity, assoc, parent|
|
168
|
-
|
156
|
+
Safrano.remove_nav_relation(assoc, parent)
|
169
157
|
entity.destroy(transaction: false)
|
170
158
|
end
|
171
|
-
|
159
|
+
|
172
160
|
def odata_delete_relation_and_entity(req, assoc, parent)
|
173
161
|
if parent
|
174
162
|
if req.in_changeset
|
@@ -182,52 +170,47 @@ module OData
|
|
182
170
|
else
|
183
171
|
destroy(transaction: false)
|
184
172
|
end
|
185
|
-
|
186
|
-
|
173
|
+
rescue StandardError => e
|
174
|
+
raise SequelAdapterError.new(e)
|
187
175
|
end
|
188
|
-
|
176
|
+
|
189
177
|
# TODO: differentiate between POST/PUT/PATCH/MERGE
|
190
178
|
def odata_post(req)
|
191
179
|
if req.walker.media_value
|
192
180
|
odata_media_value_put(req)
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
update_fields(data, self.class.data_fields, missing: :skip)
|
202
|
-
end
|
203
|
-
|
204
|
-
[202, {}, to_odata_post_json(service: req.service)]
|
205
|
-
else # TODO: other formats
|
206
|
-
415
|
181
|
+
elsif req.accept?(APPJSON)
|
182
|
+
data.delete('__metadata')
|
183
|
+
|
184
|
+
if req.in_changeset
|
185
|
+
set_fields(data, self.class.data_fields, missing: :skip)
|
186
|
+
save(transaction: false)
|
187
|
+
else
|
188
|
+
update_fields(data, self.class.data_fields, missing: :skip)
|
207
189
|
end
|
190
|
+
|
191
|
+
[202, EMPTY_HASH, to_odata_post_json(service: req.service)]
|
192
|
+
else # TODO: other formats
|
193
|
+
415
|
208
194
|
end
|
209
195
|
end
|
210
196
|
|
211
197
|
def odata_put(req)
|
212
198
|
if req.walker.media_value
|
213
199
|
odata_media_value_put(req)
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
@uribase = req.uribase
|
218
|
-
data.delete('__metadata')
|
219
|
-
|
220
|
-
if req.in_changeset
|
221
|
-
set_fields(data, self.class.data_fields, missing: :skip)
|
222
|
-
save(transaction: false)
|
223
|
-
else
|
224
|
-
update_fields(data, self.class.data_fields, missing: :skip)
|
225
|
-
end
|
200
|
+
elsif req.accept?(APPJSON)
|
201
|
+
data = JSON.parse(req.body.read)
|
202
|
+
data.delete('__metadata')
|
226
203
|
|
227
|
-
|
228
|
-
|
229
|
-
|
204
|
+
if req.in_changeset
|
205
|
+
set_fields(data, self.class.data_fields, missing: :skip)
|
206
|
+
save(transaction: false)
|
207
|
+
else
|
208
|
+
update_fields(data, self.class.data_fields, missing: :skip)
|
230
209
|
end
|
210
|
+
|
211
|
+
ARY_204_EMPTY_HASH_ARY
|
212
|
+
else # TODO: other formats
|
213
|
+
415
|
231
214
|
end
|
232
215
|
end
|
233
216
|
|
@@ -237,14 +220,12 @@ module OData
|
|
237
220
|
|
238
221
|
# validate payload column names
|
239
222
|
if (invalid = self.class.invalid_hash_data?(data))
|
240
|
-
::
|
241
|
-
return [422,
|
223
|
+
::Safrano::Request::ON_CGST_ERROR.call(req)
|
224
|
+
return [422, EMPTY_HASH, ['Invalid attribute name: ', invalid.to_s]]
|
242
225
|
end
|
243
226
|
# TODO: check values/types
|
244
227
|
|
245
228
|
my_data_fields = self.class.data_fields
|
246
|
-
@uribase = req.uribase
|
247
|
-
# if req.accept?('application/json')
|
248
229
|
|
249
230
|
if req.in_changeset
|
250
231
|
set_fields(data, my_data_fields, missing: :skip)
|
@@ -253,75 +234,17 @@ module OData
|
|
253
234
|
update_fields(data, my_data_fields, missing: :skip)
|
254
235
|
end
|
255
236
|
# patch should return 204 + no content
|
256
|
-
|
237
|
+
ARY_204_EMPTY_HASH_ARY
|
257
238
|
end
|
258
239
|
end
|
259
240
|
|
260
|
-
#
|
261
|
-
#
|
262
|
-
module NavigationRedefinitions
|
263
|
-
def all
|
264
|
-
@child_method.call
|
265
|
-
end
|
266
|
-
|
267
|
-
def count
|
268
|
-
@child_method.call.count
|
269
|
-
end
|
270
|
-
|
271
|
-
def dataset
|
272
|
-
@child_dataset_method.call
|
273
|
-
end
|
274
|
-
|
275
|
-
def navigated_dataset
|
276
|
-
@child_dataset_method.call
|
277
|
-
end
|
278
|
-
|
279
|
-
def each
|
280
|
-
y = @child_method.call
|
281
|
-
y.each { |enty| yield enty }
|
282
|
-
end
|
283
|
-
|
284
|
-
# TODO design... this is not DRY
|
285
|
-
def slug_field
|
286
|
-
superclass.slug_field
|
287
|
-
end
|
288
|
-
|
289
|
-
def type_name
|
290
|
-
superclass.type_name
|
291
|
-
end
|
292
|
-
|
293
|
-
def media_handler
|
294
|
-
superclass.media_handler
|
295
|
-
end
|
296
|
-
|
297
|
-
def to_a
|
298
|
-
y = @child_method.call
|
299
|
-
y.to_a
|
300
|
-
end
|
301
|
-
end
|
302
|
-
# GetRelated that returns a anonymous Class (ie. representing a collection)
|
303
|
-
# subtype of the related object Class ( childklass )
|
241
|
+
# GetRelated that returns a collection object representing
|
242
|
+
# wrapping the related object Class ( childklass )
|
304
243
|
# (...to_many relationship )
|
305
244
|
def get_related(childattrib)
|
306
|
-
|
307
|
-
childklass = self.class.nav_collection_attribs[childattrib]
|
308
|
-
Class.new(childklass) do
|
309
|
-
# this makes use of Sequel's Model relationships; eg this is
|
310
|
-
# 'Race[12].Edition'
|
311
|
-
# where Race[12] would be our self and 'Edition' is the
|
312
|
-
# childattrib(collection)
|
313
|
-
@child_method = parent.method(childattrib.to_sym)
|
314
|
-
@child_dataset_method = parent.method("#{childattrib}_dataset".to_sym)
|
315
|
-
@nav_parent = parent
|
316
|
-
@navattr_reflection = parent.class.association_reflections[childattrib.to_sym]
|
317
|
-
prepare_pk
|
318
|
-
prepare_fields
|
319
|
-
# Now in this anonymous Class we can refine the "all, count and []
|
320
|
-
# methods, to take into account the relationship
|
321
|
-
extend NavigationRedefinitions
|
322
|
-
end
|
245
|
+
Safrano::OData::NavigatedCollection.new(childattrib, self)
|
323
246
|
end
|
324
|
-
|
247
|
+
|
325
248
|
# GetRelatedEntity that returns an single related Entity
|
326
249
|
# (...to_one relationship )
|
327
250
|
def get_related_entity(childattrib)
|
@@ -334,35 +257,36 @@ module OData
|
|
334
257
|
# then we return a Nil... wrapper object. This object then
|
335
258
|
# allows to receive a POST operation that would actually create the nav attribute entity
|
336
259
|
|
337
|
-
ret = method(childattrib.to_sym).call ||
|
338
|
-
|
260
|
+
ret = method(childattrib.to_sym).call || Safrano::NilNavigationAttribute.new
|
261
|
+
|
339
262
|
ret.set_relation_info(self, childattrib)
|
340
|
-
|
263
|
+
|
341
264
|
ret
|
342
265
|
end
|
343
266
|
end
|
344
|
-
|
267
|
+
|
268
|
+
# end of module SafranoEntity
|
345
269
|
module Entity
|
346
270
|
include EntityBase
|
347
271
|
end
|
348
272
|
|
349
273
|
module NonMediaEntity
|
350
274
|
# non media entity metadata for json h
|
351
|
-
def metadata_h
|
352
|
-
{ uri: uri
|
275
|
+
def metadata_h
|
276
|
+
{ uri: uri,
|
353
277
|
type: type_name }
|
354
278
|
end
|
355
279
|
|
356
280
|
def values_for_odata
|
357
|
-
values
|
281
|
+
values
|
358
282
|
end
|
359
283
|
|
360
284
|
def odata_delete(req)
|
361
285
|
if req.accept?(APPJSON)
|
362
|
-
# delete
|
286
|
+
# delete
|
363
287
|
begin
|
364
288
|
odata_delete_relation_and_entity(req, @navattr_reflection, @nav_parent)
|
365
|
-
[200,
|
289
|
+
[200, EMPTY_HASH, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
|
366
290
|
rescue SequelAdapterError => e
|
367
291
|
BadRequestSequelAdapterError.new(e).odata_get(req)
|
368
292
|
end
|
@@ -372,44 +296,64 @@ module OData
|
|
372
296
|
end
|
373
297
|
|
374
298
|
# in case of a non media entity, we have to return an error on $value request
|
375
|
-
def odata_media_value_get(
|
376
|
-
|
299
|
+
def odata_media_value_get(_req)
|
300
|
+
BadRequestNonMediaValue.odata_get
|
377
301
|
end
|
378
302
|
|
379
303
|
# in case of a non media entity, we have to return an error on $value PUT
|
380
|
-
def odata_media_value_put(
|
381
|
-
|
304
|
+
def odata_media_value_put(_req)
|
305
|
+
BadRequestNonMediaValue.odata_get
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
module MappingBeforeOutput
|
310
|
+
# needed for proper datetime output
|
311
|
+
def casted_values(cols = nil)
|
312
|
+
vals = case cols
|
313
|
+
when nil
|
314
|
+
# we need to dup the model values as we need to change it before passing to_json,
|
315
|
+
# but we dont want to interfere with Sequel's owned data
|
316
|
+
# (eg because then in worst case it could happen that we write back changed values to DB)
|
317
|
+
values_for_odata.dup
|
318
|
+
else
|
319
|
+
selected_values_for_odata(cols)
|
320
|
+
end
|
321
|
+
self.class.time_cols.each { |tc| vals[tc] = vals[tc]&.iso8601 if vals.key?(tc) }
|
322
|
+
vals
|
323
|
+
end
|
324
|
+
end
|
325
|
+
module NoMappingBeforeOutput
|
326
|
+
# current model does not have eg. Time fields--> no special mapping, just to_json is fine
|
327
|
+
# --> we can use directly the model.values (values_for_odata) withoud dup'ing it as we dont
|
328
|
+
# need to change it, just output as is
|
329
|
+
def casted_values(cols = nil)
|
330
|
+
case cols
|
331
|
+
when nil
|
332
|
+
values_for_odata
|
333
|
+
else
|
334
|
+
selected_values_for_odata(cols)
|
335
|
+
end
|
382
336
|
end
|
383
337
|
end
|
384
338
|
|
385
339
|
module MediaEntity
|
386
340
|
# media entity metadata for json h
|
387
|
-
def metadata_h
|
388
|
-
{ uri: uri
|
341
|
+
def metadata_h
|
342
|
+
{ uri: uri,
|
389
343
|
type: type_name,
|
390
|
-
media_src: media_src
|
391
|
-
edit_media: edit_media
|
344
|
+
media_src: media_src,
|
345
|
+
edit_media: edit_media,
|
392
346
|
content_type: @values[:content_type] }
|
393
347
|
end
|
394
348
|
|
395
|
-
def media_src
|
349
|
+
def media_src
|
396
350
|
version = self.class.media_handler.ressource_version(self)
|
397
|
-
"#{uri
|
398
|
-
end
|
399
|
-
|
400
|
-
def edit_media(uribase)
|
401
|
-
"#{uri(uribase)}/$value"
|
402
|
-
end
|
403
|
-
|
404
|
-
# directory where to put/find the media files for this entity-type
|
405
|
-
def klass_dir
|
406
|
-
type_name
|
351
|
+
"#{uri}/$value?version=#{version}"
|
407
352
|
end
|
408
353
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
# end
|
354
|
+
def edit_media
|
355
|
+
"#{uri}/$value"
|
356
|
+
end
|
413
357
|
|
414
358
|
def values_for_odata
|
415
359
|
ret = values.dup
|
@@ -422,13 +366,12 @@ module OData
|
|
422
366
|
# delete the MR
|
423
367
|
# delegate to the media handler on collection(ie class) level
|
424
368
|
# TODO error handling
|
425
|
-
|
426
|
-
|
427
|
-
self.class.media_handler.odata_delete(request: req, entity: self)
|
369
|
+
|
370
|
+
self.class.media_handler.odata_delete(entity: self)
|
428
371
|
# delete the relation(s) to parent(s) (if any) and then entity
|
429
372
|
odata_delete_relation_and_entity(req, @navattr_reflection, @nav_parent)
|
430
373
|
# result
|
431
|
-
[200,
|
374
|
+
[200, EMPTY_HASH, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
|
432
375
|
else # TODO: other formats
|
433
376
|
415
|
434
377
|
end
|
@@ -444,17 +387,20 @@ module OData
|
|
444
387
|
def odata_media_value_put(req)
|
445
388
|
model = self.class
|
446
389
|
req.with_media_data do |data, mimetype, filename|
|
447
|
-
emdata = { :
|
390
|
+
emdata = { content_type: mimetype }
|
448
391
|
if req.in_changeset
|
449
392
|
set_fields(emdata, model.data_fields, missing: :skip)
|
450
393
|
save(transaction: false)
|
451
394
|
else
|
395
|
+
|
452
396
|
update_fields(emdata, model.data_fields, missing: :skip)
|
397
|
+
|
453
398
|
end
|
454
399
|
model.media_handler.replace_file(data: data,
|
455
400
|
entity: self,
|
456
401
|
filename: filename)
|
457
|
-
|
402
|
+
|
403
|
+
ARY_204_EMPTY_HASH_ARY
|
458
404
|
end
|
459
405
|
end
|
460
406
|
end
|
@@ -469,6 +415,7 @@ module OData
|
|
469
415
|
def media_path_id
|
470
416
|
pk.to_s
|
471
417
|
end
|
418
|
+
|
472
419
|
def media_path_ids
|
473
420
|
[pk]
|
474
421
|
end
|
@@ -478,17 +425,37 @@ module OData
|
|
478
425
|
module EntityMultiPK
|
479
426
|
include Entity
|
480
427
|
def pk_uri
|
481
|
-
|
482
|
-
self.
|
428
|
+
pku = +''
|
429
|
+
self.class.odata_upk_parts.each_with_index { |upart, i|
|
430
|
+
pku = "#{pku}#{upart}#{pk[i]}"
|
431
|
+
}
|
432
|
+
pku
|
483
433
|
end
|
484
434
|
|
485
435
|
def media_path_id
|
486
|
-
|
436
|
+
pk_hash.values.join(SPACE)
|
487
437
|
end
|
488
|
-
|
438
|
+
|
489
439
|
def media_path_ids
|
490
|
-
|
440
|
+
pk_hash.values
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
module EntityCreateStandardOutput
|
445
|
+
# Json formatter for a create entity POST call / Standard version; return as json object
|
446
|
+
def to_odata_create_json(request:)
|
447
|
+
# TODO Perf: reduce method call overhead
|
448
|
+
# we added this redirection for readability and flexibility
|
449
|
+
to_odata_json(request: request)
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
module EntityCreateArrayOutput
|
454
|
+
# Json formatter for a create entity POST call Array version
|
455
|
+
def to_odata_create_json(request:)
|
456
|
+
# TODO Perf: reduce method call overhead
|
457
|
+
# we added this redirection for readability and flexibility
|
458
|
+
to_odata_array_json(request: request)
|
491
459
|
end
|
492
460
|
end
|
493
|
-
end
|
494
|
-
# end of Module OData
|
461
|
+
end # end of Module OData
|