safrano 0.4.2 → 0.5.0
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 +9 -7
- data/lib/odata/collection.rb +140 -591
- data/lib/odata/collection_filter.rb +18 -42
- data/lib/odata/collection_media.rb +111 -54
- data/lib/odata/collection_order.rb +5 -2
- data/lib/odata/common_logger.rb +2 -0
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +123 -172
- data/lib/odata/error.rb +183 -32
- data/lib/odata/expand.rb +20 -17
- data/lib/odata/filter/base.rb +74 -0
- data/lib/odata/filter/error.rb +49 -6
- data/lib/odata/filter/parse.rb +41 -25
- data/lib/odata/filter/sequel.rb +133 -62
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +26 -19
- data/lib/odata/filter/tree.rb +106 -52
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +639 -0
- data/lib/odata/navigation_attribute.rb +13 -26
- data/lib/odata/relations.rb +5 -5
- data/lib/odata/select.rb +17 -5
- data/lib/odata/transition.rb +71 -0
- data/lib/odata/url_parameters.rb +100 -24
- data/lib/odata/walker.rb +20 -10
- data/lib/safrano.rb +18 -38
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +23 -107
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +29 -33
- data/lib/safrano/rack_app.rb +66 -65
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
- data/lib/safrano/request.rb +96 -45
- data/lib/safrano/response.rb +4 -2
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +240 -130
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +6 -19
- metadata +32 -11
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Safrano
|
6
|
+
# Type mapping DB --> Edm
|
7
|
+
# TypeMap = {"INTEGER" => "Edm.Int32" , "TEXT" => "Edm.String",
|
8
|
+
# "STRING" => "Edm.String"}
|
9
|
+
# Todo: complete mapping... this is just for the most common ones
|
10
|
+
|
11
|
+
# TODO: use Sequel GENERIC_TYPES: -->
|
12
|
+
# Constants
|
13
|
+
# GENERIC_TYPES = %w'String Integer Float Numeric BigDecimal Date DateTime
|
14
|
+
# Time File TrueClass FalseClass'.freeze
|
15
|
+
# Classes specifying generic types that Sequel will convert to
|
16
|
+
# database-specific types.
|
17
|
+
DB_TYPE_STRING_RGX = /\ACHAR\s*\(\d+\)\z/.freeze
|
18
|
+
|
19
|
+
# used in $metadata
|
20
|
+
# cf. Sequel Database column_schema_default_to_ruby_value
|
21
|
+
# schema_column_type
|
22
|
+
# https://www.odata.org/documentation/odata-version-2-0/overview/
|
23
|
+
def self.default_edm_type(ruby_type:)
|
24
|
+
case ruby_type
|
25
|
+
when :integer
|
26
|
+
'Edm.Int32'
|
27
|
+
when :string
|
28
|
+
'Edm.String'
|
29
|
+
when :date, :datetime,
|
30
|
+
'Edm.DateTime'
|
31
|
+
when :time
|
32
|
+
'Edm.Time'
|
33
|
+
when :boolean
|
34
|
+
'Edm.Boolean'
|
35
|
+
when :float
|
36
|
+
'Edm.Double'
|
37
|
+
when :decimal
|
38
|
+
'Edm.Decimal'
|
39
|
+
when :blob
|
40
|
+
'Edm.Binary'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# use Edm twice so that we can do include Safrano::Edm and then
|
45
|
+
# have Edm::Int32 etc... availabe
|
46
|
+
# and we can have Edm::String different from ::String
|
47
|
+
module Edm
|
48
|
+
module Edm
|
49
|
+
module OutputClassMethods
|
50
|
+
def type_name
|
51
|
+
"Edm.#{name.split('::').last}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def odata_collection(array)
|
55
|
+
array
|
56
|
+
end
|
57
|
+
|
58
|
+
def odata_value(instance)
|
59
|
+
instance
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Null < NilClass
|
64
|
+
extend OutputClassMethods
|
65
|
+
# nil --> null convertion is done by to_json
|
66
|
+
def self.odata_value(instance)
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.convert_from_urlparam(v)
|
71
|
+
return Contract::NOK unless (v == 'null')
|
72
|
+
|
73
|
+
Contract.valid(nil)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Binary is a String with the BINARY encoding
|
78
|
+
class Binary < String
|
79
|
+
extend OutputClassMethods
|
80
|
+
|
81
|
+
def self.convert_from_urlparam(v)
|
82
|
+
Contract.valid(v.dup.force_encoding('BINARY'))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# an object alwys evaluates to
|
87
|
+
# true ([true, anything not false & not nil objs])
|
88
|
+
# or false([nil, false])
|
89
|
+
class Boolean < Object
|
90
|
+
extend OutputClassMethods
|
91
|
+
def Boolean.odata_value(instance)
|
92
|
+
instance ? true : false
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.odata_collection(array)
|
96
|
+
array.map { |v| odata_value(v) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.convert_from_urlparam(v)
|
100
|
+
return Contract::NOK unless ['true', 'false'].include?(v)
|
101
|
+
|
102
|
+
Contract.valid(v == 'true')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Bytes are usualy represented as Intger in ruby,
|
107
|
+
# eg.String.bytes --> Array of ints
|
108
|
+
class Byte < Integer
|
109
|
+
extend OutputClassMethods
|
110
|
+
|
111
|
+
def self.convert_from_urlparam(v)
|
112
|
+
return Contract::NOK unless ((bytev = v.to_i) < 256)
|
113
|
+
|
114
|
+
Contract.valid(bytev)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class DateTime < ::DateTime
|
119
|
+
extend OutputClassMethods
|
120
|
+
def DateTime.odata_value(instance)
|
121
|
+
instance.to_datetime
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.odata_collection(array)
|
125
|
+
array.map { |v| odata_value(v) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.convert_from_urlparam(v)
|
129
|
+
begin
|
130
|
+
Contract.valid(DateTime.parse(v))
|
131
|
+
rescue
|
132
|
+
return convertion_error(v)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class String < ::String
|
138
|
+
extend OutputClassMethods
|
139
|
+
|
140
|
+
def self.convert_from_urlparam(v)
|
141
|
+
Contract.valid(v)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class Int32 < Integer
|
146
|
+
extend OutputClassMethods
|
147
|
+
|
148
|
+
def self.convert_from_urlparam(v)
|
149
|
+
return Contract::NOK unless (ret = number_or_nil(v))
|
150
|
+
|
151
|
+
Contract.valid(ret)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class Int64 < Integer
|
156
|
+
extend OutputClassMethods
|
157
|
+
|
158
|
+
def self.convert_from_urlparam(v)
|
159
|
+
return Contract::NOK unless (ret = number_or_nil(v))
|
160
|
+
|
161
|
+
Contract.valid(ret)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class Double < Float
|
166
|
+
extend OutputClassMethods
|
167
|
+
|
168
|
+
def self.convert_from_urlparam(v)
|
169
|
+
begin
|
170
|
+
Contract.valid(v.to_f)
|
171
|
+
rescue
|
172
|
+
return Contract::NOK
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# include Safrano
|
181
|
+
|
182
|
+
# x = Edm::String.new('xxx')
|
183
|
+
|
184
|
+
# pp x
|
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
|
+
include Safrano::NavigationInfo
|
14
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,10 +50,20 @@ 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
|
|
@@ -90,26 +81,27 @@ module OData
|
|
90
81
|
@nav_coll
|
91
82
|
end
|
92
83
|
|
93
|
-
def uri
|
94
|
-
"
|
84
|
+
def uri
|
85
|
+
@odata_pk ||= "(#{pk_uri})"
|
86
|
+
"#{self.class.uri}#{@odata_pk}"
|
95
87
|
end
|
88
|
+
|
96
89
|
D = 'd'.freeze
|
97
90
|
DJ_OPEN = '{"d":'.freeze
|
98
91
|
DJ_CLOSE = '}'.freeze
|
99
92
|
|
100
93
|
# Json formatter for a single entity (probably OData V1/V2 like)
|
101
|
-
def to_odata_json(
|
102
|
-
template = self.class.output_template(@uparms
|
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
|
106
99
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
107
100
|
end
|
108
101
|
|
109
102
|
# Json formatter for a single entity reached by navigation $links
|
110
103
|
def to_odata_onelink_json(service:)
|
111
|
-
innerj = service.get_entity_odata_link_h(entity: self
|
112
|
-
uribase: @uribase).to_json
|
104
|
+
innerj = service.get_entity_odata_link_h(entity: self).to_json
|
113
105
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
114
106
|
end
|
115
107
|
|
@@ -120,36 +112,11 @@ module OData
|
|
120
112
|
selvals
|
121
113
|
end
|
122
114
|
|
123
|
-
#
|
124
|
-
# TODO
|
125
|
-
def
|
126
|
-
|
127
|
-
|
128
|
-
values_for_odata
|
129
|
-
else
|
130
|
-
selected_values_for_odata(cols)
|
131
|
-
end
|
132
|
-
|
133
|
-
# WARNING; this code is duplicated in attribute.rb
|
134
|
-
# (and the inverted transformation is in test/client.rb)
|
135
|
-
# will require a more systematic solution some day
|
136
|
-
|
137
|
-
vals.transform_values! do |v|
|
138
|
-
case v
|
139
|
-
when Time
|
140
|
-
# try to get back the database time zone and value
|
141
|
-
(v + v.gmt_offset).utc.to_datetime
|
142
|
-
else
|
143
|
-
v
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# post paylod expects the new entity in an array
|
149
|
-
def to_odata_post_json(service:)
|
150
|
-
innerj = service.get_coll_odata_h(array: [self],
|
151
|
-
template: self.class.default_template,
|
152
|
-
uribase: @uribase).to_json
|
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
|
153
120
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
154
121
|
end
|
155
122
|
|
@@ -159,29 +126,34 @@ module OData
|
|
159
126
|
|
160
127
|
def copy_request_infos(req)
|
161
128
|
@params = req.params
|
162
|
-
@uribase = req.uribase
|
163
129
|
@do_links = req.walker.do_links
|
164
|
-
@uparms = UrlParameters4Single.new(@params)
|
130
|
+
@uparms = UrlParameters4Single.new(self, @params)
|
165
131
|
end
|
166
132
|
|
167
|
-
|
168
|
-
def odata_get(req)
|
169
|
-
copy_request_infos(req)
|
133
|
+
def odata_get_output(req)
|
170
134
|
if req.walker.media_value
|
171
135
|
odata_media_value_get(req)
|
172
136
|
elsif req.accept?(APPJSON)
|
137
|
+
# json is default content type so we dont need to specify it here again
|
173
138
|
if req.walker.do_links
|
174
|
-
[200,
|
139
|
+
[200, EMPTY_HASH, [to_odata_onelink_json(service: req.service)]]
|
175
140
|
else
|
176
|
-
[200,
|
141
|
+
[200, EMPTY_HASH, [to_odata_json(request: req)]]
|
177
142
|
end
|
178
143
|
else # TODO: other formats
|
179
144
|
415
|
180
145
|
end
|
181
146
|
end
|
182
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
|
+
|
183
155
|
DELETE_REL_AND_ENTY = lambda do |entity, assoc, parent|
|
184
|
-
|
156
|
+
Safrano.remove_nav_relation(assoc, parent)
|
185
157
|
entity.destroy(transaction: false)
|
186
158
|
end
|
187
159
|
|
@@ -227,7 +199,6 @@ module OData
|
|
227
199
|
odata_media_value_put(req)
|
228
200
|
elsif req.accept?(APPJSON)
|
229
201
|
data = JSON.parse(req.body.read)
|
230
|
-
@uribase = req.uribase
|
231
202
|
data.delete('__metadata')
|
232
203
|
|
233
204
|
if req.in_changeset
|
@@ -249,14 +220,12 @@ module OData
|
|
249
220
|
|
250
221
|
# validate payload column names
|
251
222
|
if (invalid = self.class.invalid_hash_data?(data))
|
252
|
-
::
|
223
|
+
::Safrano::Request::ON_CGST_ERROR.call(req)
|
253
224
|
return [422, EMPTY_HASH, ['Invalid attribute name: ', invalid.to_s]]
|
254
225
|
end
|
255
226
|
# TODO: check values/types
|
256
227
|
|
257
228
|
my_data_fields = self.class.data_fields
|
258
|
-
@uribase = req.uribase
|
259
|
-
# if req.accept?('application/json')
|
260
229
|
|
261
230
|
if req.in_changeset
|
262
231
|
set_fields(data, my_data_fields, missing: :skip)
|
@@ -269,73 +238,11 @@ module OData
|
|
269
238
|
end
|
270
239
|
end
|
271
240
|
|
272
|
-
#
|
273
|
-
#
|
274
|
-
module NavigationRedefinitions
|
275
|
-
def all
|
276
|
-
@child_method.call
|
277
|
-
end
|
278
|
-
|
279
|
-
def count
|
280
|
-
@child_method.call.count
|
281
|
-
end
|
282
|
-
|
283
|
-
def dataset
|
284
|
-
@child_dataset_method.call
|
285
|
-
end
|
286
|
-
|
287
|
-
def navigated_dataset
|
288
|
-
@child_dataset_method.call
|
289
|
-
end
|
290
|
-
|
291
|
-
def each
|
292
|
-
y = @child_method.call
|
293
|
-
y.each { |enty| yield enty }
|
294
|
-
end
|
295
|
-
|
296
|
-
# TODO: design... this is not DRY
|
297
|
-
def slug_field
|
298
|
-
superclass.slug_field
|
299
|
-
end
|
300
|
-
|
301
|
-
def type_name
|
302
|
-
superclass.type_name
|
303
|
-
end
|
304
|
-
|
305
|
-
def media_handler
|
306
|
-
superclass.media_handler
|
307
|
-
end
|
308
|
-
|
309
|
-
def default_template
|
310
|
-
superclass.default_template
|
311
|
-
end
|
312
|
-
|
313
|
-
def to_a
|
314
|
-
y = @child_method.call
|
315
|
-
y.to_a
|
316
|
-
end
|
317
|
-
end
|
318
|
-
# GetRelated that returns a anonymous Class (ie. representing a collection)
|
319
|
-
# subtype of the related object Class ( childklass )
|
241
|
+
# GetRelated that returns a collection object representing
|
242
|
+
# wrapping the related object Class ( childklass )
|
320
243
|
# (...to_many relationship )
|
321
244
|
def get_related(childattrib)
|
322
|
-
|
323
|
-
childklass = self.class.nav_collection_attribs[childattrib]
|
324
|
-
Class.new(childklass) do
|
325
|
-
# this makes use of Sequel's Model relationships; eg this is
|
326
|
-
# 'Race[12].Edition'
|
327
|
-
# where Race[12] would be our self and 'Edition' is the
|
328
|
-
# childattrib(collection)
|
329
|
-
@child_method = parent.method(childattrib.to_sym)
|
330
|
-
@child_dataset_method = parent.method("#{childattrib}_dataset".to_sym)
|
331
|
-
@nav_parent = parent
|
332
|
-
@navattr_reflection = parent.class.association_reflections[childattrib.to_sym]
|
333
|
-
prepare_pk
|
334
|
-
prepare_fields
|
335
|
-
# Now in this anonymous Class we can refine the "all, count and []
|
336
|
-
# methods, to take into account the relationship
|
337
|
-
extend NavigationRedefinitions
|
338
|
-
end
|
245
|
+
Safrano::OData::NavigatedCollection.new(childattrib, self)
|
339
246
|
end
|
340
247
|
|
341
248
|
# GetRelatedEntity that returns an single related Entity
|
@@ -350,27 +257,28 @@ module OData
|
|
350
257
|
# then we return a Nil... wrapper object. This object then
|
351
258
|
# allows to receive a POST operation that would actually create the nav attribute entity
|
352
259
|
|
353
|
-
ret = method(childattrib.to_sym).call ||
|
260
|
+
ret = method(childattrib.to_sym).call || Safrano::NilNavigationAttribute.new
|
354
261
|
|
355
262
|
ret.set_relation_info(self, childattrib)
|
356
263
|
|
357
264
|
ret
|
358
265
|
end
|
359
266
|
end
|
360
|
-
|
267
|
+
|
268
|
+
# end of module SafranoEntity
|
361
269
|
module Entity
|
362
270
|
include EntityBase
|
363
271
|
end
|
364
272
|
|
365
273
|
module NonMediaEntity
|
366
274
|
# non media entity metadata for json h
|
367
|
-
def metadata_h
|
368
|
-
{ uri: uri
|
275
|
+
def metadata_h
|
276
|
+
{ uri: uri,
|
369
277
|
type: type_name }
|
370
278
|
end
|
371
279
|
|
372
280
|
def values_for_odata
|
373
|
-
values
|
281
|
+
values
|
374
282
|
end
|
375
283
|
|
376
284
|
def odata_delete(req)
|
@@ -378,7 +286,7 @@ module OData
|
|
378
286
|
# delete
|
379
287
|
begin
|
380
288
|
odata_delete_relation_and_entity(req, @navattr_reflection, @nav_parent)
|
381
|
-
[200,
|
289
|
+
[200, EMPTY_HASH, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
|
382
290
|
rescue SequelAdapterError => e
|
383
291
|
BadRequestSequelAdapterError.new(e).odata_get(req)
|
384
292
|
end
|
@@ -398,35 +306,55 @@ module OData
|
|
398
306
|
end
|
399
307
|
end
|
400
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
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
401
339
|
module MediaEntity
|
402
340
|
# media entity metadata for json h
|
403
|
-
def metadata_h
|
404
|
-
{ uri: uri
|
341
|
+
def metadata_h
|
342
|
+
{ uri: uri,
|
405
343
|
type: type_name,
|
406
|
-
media_src: media_src
|
407
|
-
edit_media: edit_media
|
344
|
+
media_src: media_src,
|
345
|
+
edit_media: edit_media,
|
408
346
|
content_type: @values[:content_type] }
|
409
347
|
end
|
410
348
|
|
411
|
-
def media_src
|
349
|
+
def media_src
|
412
350
|
version = self.class.media_handler.ressource_version(self)
|
413
|
-
"#{uri
|
414
|
-
end
|
415
|
-
|
416
|
-
def edit_media(uribase)
|
417
|
-
"#{uri(uribase)}/$value"
|
351
|
+
"#{uri}/$value?version=#{version}"
|
418
352
|
end
|
419
353
|
|
420
|
-
|
421
|
-
|
422
|
-
type_name
|
354
|
+
def edit_media
|
355
|
+
"#{uri}/$value"
|
423
356
|
end
|
424
357
|
|
425
|
-
# # this is just ModelKlass/pk as a single string
|
426
|
-
# def qualified_media_path_id
|
427
|
-
# "#{self.class}/#{media_path_id}"
|
428
|
-
# end
|
429
|
-
|
430
358
|
def values_for_odata
|
431
359
|
ret = values.dup
|
432
360
|
ret.delete(:content_type)
|
@@ -443,7 +371,7 @@ module OData
|
|
443
371
|
# delete the relation(s) to parent(s) (if any) and then entity
|
444
372
|
odata_delete_relation_and_entity(req, @navattr_reflection, @nav_parent)
|
445
373
|
# result
|
446
|
-
[200,
|
374
|
+
[200, EMPTY_HASH, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
|
447
375
|
else # TODO: other formats
|
448
376
|
415
|
449
377
|
end
|
@@ -464,11 +392,14 @@ module OData
|
|
464
392
|
set_fields(emdata, model.data_fields, missing: :skip)
|
465
393
|
save(transaction: false)
|
466
394
|
else
|
395
|
+
|
467
396
|
update_fields(emdata, model.data_fields, missing: :skip)
|
397
|
+
|
468
398
|
end
|
469
399
|
model.media_handler.replace_file(data: data,
|
470
400
|
entity: self,
|
471
401
|
filename: filename)
|
402
|
+
|
472
403
|
ARY_204_EMPTY_HASH_ARY
|
473
404
|
end
|
474
405
|
end
|
@@ -494,8 +425,11 @@ module OData
|
|
494
425
|
module EntityMultiPK
|
495
426
|
include Entity
|
496
427
|
def pk_uri
|
497
|
-
|
498
|
-
|
428
|
+
pku = +''
|
429
|
+
self.class.odata_upk_parts.each_with_index { |upart, i|
|
430
|
+
pku = "#{pku}#{upart}#{pk[i]}"
|
431
|
+
}
|
432
|
+
pku
|
499
433
|
end
|
500
434
|
|
501
435
|
def media_path_id
|
@@ -506,5 +440,22 @@ module OData
|
|
506
440
|
pk_hash.values
|
507
441
|
end
|
508
442
|
end
|
509
|
-
|
510
|
-
|
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)
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end # end of Module OData
|