safrano 0.3.2 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/odata/attribute.rb +1 -1
- data/lib/odata/batch.rb +24 -10
- data/lib/odata/collection.rb +242 -96
- data/lib/odata/collection_filter.rb +40 -9
- data/lib/odata/collection_media.rb +279 -0
- data/lib/odata/collection_order.rb +46 -36
- data/lib/odata/common_logger.rb +59 -0
- data/lib/odata/entity.rb +268 -54
- data/lib/odata/error.rb +58 -17
- data/lib/odata/expand.rb +123 -0
- data/lib/odata/filter/error.rb +6 -0
- data/lib/odata/filter/parse.rb +4 -12
- data/lib/odata/filter/sequel.rb +11 -13
- data/lib/odata/filter/tree.rb +11 -15
- data/lib/odata/navigation_attribute.rb +150 -0
- data/lib/odata/relations.rb +2 -2
- data/lib/odata/select.rb +42 -0
- data/lib/odata/url_parameters.rb +51 -36
- data/lib/odata/walker.rb +12 -4
- data/lib/safrano.rb +23 -12
- data/lib/{safrano_core.rb → safrano/core.rb} +14 -3
- data/lib/{multipart.rb → safrano/multipart.rb} +51 -29
- data/lib/{odata_rack_builder.rb → safrano/odata_rack_builder.rb} +1 -1
- data/lib/{rack_app.rb → safrano/rack_app.rb} +15 -10
- data/lib/{request.rb → safrano/request.rb} +21 -8
- data/lib/{response.rb → safrano/response.rb} +1 -2
- data/lib/{sequel_join_by_paths.rb → safrano/sequel_join_by_paths.rb} +1 -1
- data/lib/{service.rb → safrano/service.rb} +93 -97
- data/lib/safrano/version.rb +3 -0
- data/lib/sequel/plugins/join_by_paths.rb +11 -10
- metadata +34 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6397b8b41d6241576f613abfa0e36c8f6973fa96542b60d388b15954aaa3a92b
|
4
|
+
data.tar.gz: 3f36fe0d18bd9659d4e3ce27f501ef1540e82f24a26418c28760aae869de85d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6f666fb8e2fb136fe7ca0c215e19c24648d9a1a4494c3cdb1a53f45561cf827c2670be56bcf74005828b3a9ebf78e2efc63e8b89db35fefb9f70d4c730e7ebb
|
7
|
+
data.tar.gz: 2cdfe6b8cb732184dacb256e5c114361f41da91f5a0f821c2f61a5b7ae541b0aa8f5ad45b23598c0d5301311c193f9f6e51d6e8b8def7eac78a54176f92cfd98
|
data/lib/odata/attribute.rb
CHANGED
data/lib/odata/batch.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative '../safrano/rack_app.rb'
|
2
|
+
require_relative '../safrano/core.rb'
|
3
|
+
require 'rack/body_proxy'
|
4
|
+
require_relative './common_logger.rb'
|
3
5
|
|
4
6
|
module OData
|
5
7
|
# Support for OData multipart $batch Requests
|
@@ -38,7 +40,7 @@ module OData
|
|
38
40
|
def batch_call(part_req)
|
39
41
|
env = batch_env(part_req)
|
40
42
|
env['HTTP_HOST'] = @full_req.env['HTTP_HOST']
|
41
|
-
|
43
|
+
began_at = Rack::Utils.clock_time
|
42
44
|
@request = OData::Request.new(env)
|
43
45
|
@response = OData::Response.new
|
44
46
|
|
@@ -51,7 +53,16 @@ module OData
|
|
51
53
|
before
|
52
54
|
dispatch
|
53
55
|
|
54
|
-
@response.finish
|
56
|
+
status, header, body = @response.finish
|
57
|
+
# Logging of sub-requests with ODataCommonLogger.
|
58
|
+
# A bit hacky but working
|
59
|
+
# TODO: test ?
|
60
|
+
if (logga = @full_req.env['safrano.logger_mw'])
|
61
|
+
logga.batch_log(env, status, header, began_at)
|
62
|
+
# TODO: check why/if we need Rack::Utils::HeaderHash.new(header)
|
63
|
+
# and Rack::BodyProxy.new(body) ?
|
64
|
+
end
|
65
|
+
[status, header, body]
|
55
66
|
end
|
56
67
|
|
57
68
|
# shamelessely copied from Rack::TEST:Session
|
@@ -60,7 +71,7 @@ module OData
|
|
60
71
|
|
61
72
|
headers.each do |name, value|
|
62
73
|
env_key = name.upcase.tr('-', '_')
|
63
|
-
env_key =
|
74
|
+
env_key = "HTTP_#{env_key}" unless env_key == 'CONTENT_TYPE'
|
64
75
|
converted_headers[env_key] = value
|
65
76
|
end
|
66
77
|
|
@@ -71,7 +82,10 @@ module OData
|
|
71
82
|
@env = ::Rack::MockRequest.env_for(mime_req.uri,
|
72
83
|
method: mime_req.http_method,
|
73
84
|
input: mime_req.content)
|
85
|
+
# Logging of sub-requests
|
86
|
+
@env[Rack::RACK_ERRORS] = @full_req.env[Rack::RACK_ERRORS]
|
74
87
|
@env.merge! headers_for_env(mime_req.hd)
|
88
|
+
|
75
89
|
@env
|
76
90
|
end
|
77
91
|
end
|
@@ -90,11 +104,11 @@ module OData
|
|
90
104
|
# jaune d'oeuf
|
91
105
|
class DisabledHandler < HandlerBase
|
92
106
|
def odata_post(_req)
|
93
|
-
[404,
|
107
|
+
[404, EMPTY_HASH, '$batch is not enabled ']
|
94
108
|
end
|
95
109
|
|
96
110
|
def odata_get(_req)
|
97
|
-
[404,
|
111
|
+
[404, EMPTY_HASH, '$batch is not enabled ']
|
98
112
|
end
|
99
113
|
end
|
100
114
|
# battre le tout
|
@@ -112,7 +126,7 @@ module OData
|
|
112
126
|
def odata_post(req)
|
113
127
|
@request = req
|
114
128
|
|
115
|
-
if @request.media_type ==
|
129
|
+
if @request.media_type == OData::MP_MIXED
|
116
130
|
|
117
131
|
batcha = @request.create_batch_app
|
118
132
|
@mult_request = @request.parse_multipart
|
@@ -124,12 +138,12 @@ module OData
|
|
124
138
|
|
125
139
|
[202, resp_hdrs, @mult_response.body[0]]
|
126
140
|
else
|
127
|
-
[415,
|
141
|
+
[415, EMPTY_HASH, 'Unsupported Media Type']
|
128
142
|
end
|
129
143
|
end
|
130
144
|
|
131
145
|
def odata_get(_req)
|
132
|
-
[405,
|
146
|
+
[405, EMPTY_HASH, 'You cant GET $batch, POST it ']
|
133
147
|
end
|
134
148
|
end
|
135
149
|
end
|
data/lib/odata/collection.rb
CHANGED
@@ -4,11 +4,14 @@
|
|
4
4
|
|
5
5
|
require 'json'
|
6
6
|
require 'rexml/document'
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
require_relative '../safrano/core.rb'
|
8
|
+
require_relative 'error.rb'
|
9
|
+
require_relative 'collection_filter.rb'
|
10
|
+
require_relative 'collection_order.rb'
|
11
|
+
require_relative 'expand.rb'
|
12
|
+
require_relative 'select.rb'
|
13
|
+
require_relative 'url_parameters.rb'
|
14
|
+
require_relative 'collection_media.rb'
|
12
15
|
|
13
16
|
# small helper method
|
14
17
|
# http://stackoverflow.com/
|
@@ -42,25 +45,32 @@ module OData
|
|
42
45
|
# we will add this to our Model classes with "extend" --> self is the Class
|
43
46
|
module EntityClassBase
|
44
47
|
SINGLE_PK_URL_REGEXP = /\A\(\s*'?([\w\s]+)'?\s*\)(.*)/.freeze
|
45
|
-
ONLY_INTEGER_RGX = /\A[+-]?\d+\z
|
48
|
+
ONLY_INTEGER_RGX = /\A[+-]?\d+\z/.freeze
|
46
49
|
|
47
50
|
attr_reader :nav_collection_url_regexp
|
48
51
|
attr_reader :nav_entity_url_regexp
|
49
52
|
attr_reader :entity_id_url_regexp
|
50
|
-
attr_reader :attrib_paths_url_regexp
|
51
53
|
attr_reader :nav_collection_attribs
|
52
54
|
attr_reader :nav_entity_attribs
|
53
55
|
attr_reader :data_fields
|
54
56
|
attr_reader :inlinecount
|
57
|
+
attr_reader :default_template
|
58
|
+
|
59
|
+
# Sequel associations pointing to this model. Sequel provides association
|
60
|
+
# reflection information on the "from" side. But in some cases
|
61
|
+
# we will need the reverted way
|
62
|
+
# finally not needed and not used yet
|
63
|
+
# attr_accessor :assocs_to
|
64
|
+
|
65
|
+
# set to parent entity in case the collection is a nav.collection
|
66
|
+
# nil otherwise
|
67
|
+
attr_reader :nav_parent
|
55
68
|
|
56
69
|
attr_accessor :namespace
|
57
70
|
|
58
71
|
# dataset
|
59
72
|
attr_accessor :cx
|
60
73
|
|
61
|
-
# array of the objects --> dataset.to_a
|
62
|
-
attr_accessor :ax
|
63
|
-
|
64
74
|
# url params
|
65
75
|
attr_reader :params
|
66
76
|
|
@@ -75,13 +85,26 @@ module OData
|
|
75
85
|
attr_accessor :deferred_iblock
|
76
86
|
|
77
87
|
# convention: entityType is the Ruby Model class --> name is just to_s
|
88
|
+
# Warning: for handling Navigation relations, we use anonymous collection classes
|
89
|
+
# dynamically subtyped from a Model class, and in such an anonymous class
|
90
|
+
# the class-name is not the OData Type. In these subclass we redefine "type_name"
|
91
|
+
# thus when we need the Odata type name, we shall use this method instead of just the collection class name
|
78
92
|
def type_name
|
79
93
|
to_s
|
80
94
|
end
|
81
95
|
|
82
|
-
# convention: default for entity_set_name is the
|
96
|
+
# convention: default for entity_set_name is the type name
|
83
97
|
def entity_set_name
|
84
|
-
@entity_set_name = (@entity_set_name ||
|
98
|
+
@entity_set_name = (@entity_set_name || type_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def reset
|
102
|
+
# TODO: automatically reset all attributes?
|
103
|
+
@deferred_iblock = nil
|
104
|
+
@entity_set_name = nil
|
105
|
+
@uparms = nil
|
106
|
+
@params = nil
|
107
|
+
@cx = nil
|
85
108
|
end
|
86
109
|
|
87
110
|
def execute_deferred_iblock
|
@@ -89,19 +112,36 @@ module OData
|
|
89
112
|
end
|
90
113
|
|
91
114
|
# Factory json-> Model Object instance
|
92
|
-
def new_from_hson_h(hash
|
115
|
+
def new_from_hson_h(hash)
|
93
116
|
enty = new
|
94
|
-
hash.delete('__metadata')
|
95
|
-
# DONE: move this somewhere else where it's evaluated only once at setup
|
96
|
-
# data_fields = db_schema.map do |col, cattr|
|
97
|
-
# cattr[:primary_key] ? nil : col
|
98
|
-
# end.select { |col| col }
|
99
117
|
enty.set_fields(hash, @data_fields, missing: :skip)
|
100
|
-
# in-changeset requests get their own transaction
|
101
|
-
enty.save(transaction: !in_changeset)
|
102
118
|
enty
|
103
119
|
end
|
104
120
|
|
121
|
+
CREATE_AND_SAVE_ENTY_AND_REL = lambda do |new_entity, assoc, parent|
|
122
|
+
# in-changeset requests get their own transaction
|
123
|
+
case assoc[:type]
|
124
|
+
when :one_to_many, :one_to_one
|
125
|
+
OData.create_nav_relation(new_entity, assoc, parent)
|
126
|
+
new_entity.save(transaction: false)
|
127
|
+
when :many_to_one
|
128
|
+
new_entity.save(transaction: false)
|
129
|
+
OData.create_nav_relation(new_entity, assoc, parent)
|
130
|
+
parent.save(transaction: false)
|
131
|
+
# else # not supported
|
132
|
+
end
|
133
|
+
end
|
134
|
+
def odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
|
135
|
+
if req.in_changeset
|
136
|
+
# in-changeset requests get their own transaction
|
137
|
+
CREATE_AND_SAVE_ENTY_AND_REL.call(new_entity, assoc, parent)
|
138
|
+
else
|
139
|
+
db.transaction do
|
140
|
+
CREATE_AND_SAVE_ENTY_AND_REL.call(new_entity, assoc, parent)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
105
145
|
def odata_get_inlinecount_w_sequel
|
106
146
|
return unless (icp = @params['$inlinecount'])
|
107
147
|
|
@@ -114,10 +154,6 @@ module OData
|
|
114
154
|
end
|
115
155
|
end
|
116
156
|
|
117
|
-
def navigated_coll
|
118
|
-
false
|
119
|
-
end
|
120
|
-
|
121
157
|
def attrib_path_valid?(path)
|
122
158
|
@attribute_path_list.include? path
|
123
159
|
end
|
@@ -160,9 +196,7 @@ module OData
|
|
160
196
|
def check_u_p_inlinecount
|
161
197
|
return unless (icp = @params['$inlinecount'])
|
162
198
|
|
163
|
-
unless (icp == 'allpages') || (icp == 'none')
|
164
|
-
return BadRequestInlineCountParamError
|
165
|
-
end
|
199
|
+
return BadRequestInlineCountParamError unless (icp == 'allpages') || (icp == 'none')
|
166
200
|
|
167
201
|
nil
|
168
202
|
end
|
@@ -175,26 +209,25 @@ module OData
|
|
175
209
|
@uparms.check_order
|
176
210
|
end
|
177
211
|
|
212
|
+
def check_u_p_expand
|
213
|
+
@uparms.check_expand
|
214
|
+
end
|
215
|
+
|
178
216
|
def build_attribute_path_list
|
179
217
|
@attribute_path_list = attribute_path_list
|
180
|
-
@attrib_paths_url_regexp = @attribute_path_list.join('|')
|
181
218
|
end
|
182
219
|
|
183
220
|
def attribute_path_list(nodes = Set.new)
|
184
221
|
# break circles
|
185
|
-
return
|
222
|
+
return EMPTY_ARRAY if nodes.include?(entity_set_name)
|
186
223
|
|
187
224
|
ret = @columns.map(&:to_s)
|
188
225
|
nodes.add entity_set_name
|
189
|
-
|
190
|
-
|
191
|
-
ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
|
192
|
-
end
|
226
|
+
@nav_entity_attribs&.each do |a, k|
|
227
|
+
ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
|
193
228
|
end
|
194
|
-
|
195
|
-
|
196
|
-
ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
|
197
|
-
end
|
229
|
+
@nav_collection_attribs&.each do |a, k|
|
230
|
+
ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
|
198
231
|
end
|
199
232
|
ret
|
200
233
|
end
|
@@ -203,20 +236,18 @@ module OData
|
|
203
236
|
return nil unless @params
|
204
237
|
|
205
238
|
check_u_p_top || check_u_p_skip || check_u_p_orderby ||
|
206
|
-
check_u_p_filter || check_u_p_inlinecount
|
239
|
+
check_u_p_filter || check_u_p_expand || check_u_p_inlinecount
|
207
240
|
end
|
208
241
|
|
209
242
|
def initialize_dataset
|
210
243
|
@cx = self
|
211
|
-
@
|
212
|
-
@cx = navigated_dataset if @cx.navigated_coll
|
244
|
+
@cx = navigated_dataset if @cx.nav_parent
|
213
245
|
@model = if @cx.respond_to? :model
|
214
246
|
@cx.model
|
215
247
|
else
|
216
248
|
@cx
|
217
249
|
end
|
218
|
-
@
|
219
|
-
@uparms = UrlParameters.new(@jh, @params)
|
250
|
+
@uparms = UrlParameters4Coll.new(@model, @params)
|
220
251
|
end
|
221
252
|
|
222
253
|
# finally return the requested output according to format, options etc
|
@@ -227,9 +258,9 @@ module OData
|
|
227
258
|
[200, CT_TEXT, @cx.count.to_s]
|
228
259
|
elsif req.accept?(APPJSON)
|
229
260
|
if req.walker.do_links
|
230
|
-
[200, CT_JSON, to_odata_links_json(service: req.service)]
|
261
|
+
[200, CT_JSON, [to_odata_links_json(service: req.service)]]
|
231
262
|
else
|
232
|
-
[200, CT_JSON, to_odata_json(service: req.service)]
|
263
|
+
[200, CT_JSON, [to_odata_json(service: req.service)]]
|
233
264
|
end
|
234
265
|
else # TODO: other formats
|
235
266
|
406
|
@@ -250,9 +281,17 @@ module OData
|
|
250
281
|
end
|
251
282
|
end
|
252
283
|
|
284
|
+
def odata_post(req)
|
285
|
+
odata_create_entity_and_relation(req, @navattr_reflection, @nav_parent)
|
286
|
+
end
|
287
|
+
|
253
288
|
# add metadata xml to the passed REXML schema object
|
254
289
|
def add_metadata_rexml(schema)
|
255
|
-
enty =
|
290
|
+
enty = if @media_handler
|
291
|
+
schema.add_element('EntityType', 'Name' => to_s, 'HasStream' => 'true')
|
292
|
+
else
|
293
|
+
schema.add_element('EntityType', 'Name' => to_s)
|
294
|
+
end
|
256
295
|
# with their properties
|
257
296
|
db_schema.each do |pnam, prop|
|
258
297
|
if prop[:primary_key] == true
|
@@ -268,67 +307,125 @@ module OData
|
|
268
307
|
end
|
269
308
|
|
270
309
|
# metadata REXML data for a single Nav attribute
|
271
|
-
def metadata_nav_rexml_attribs(assoc,
|
310
|
+
def metadata_nav_rexml_attribs(assoc, to_klass, relman, xnamespace)
|
272
311
|
from = type_name
|
273
|
-
to =
|
312
|
+
to = to_klass.type_name
|
274
313
|
relman.get_metadata_xml_attribs(from,
|
275
314
|
to,
|
276
|
-
association_reflection(assoc)[:type],
|
277
|
-
xnamespace
|
315
|
+
association_reflection(assoc.to_sym)[:type],
|
316
|
+
xnamespace,
|
317
|
+
assoc)
|
278
318
|
end
|
279
319
|
|
280
320
|
# and their Nav attributes == Sequel Model association
|
281
|
-
def add_metadata_navs_rexml(schema_enty,
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
321
|
+
def add_metadata_navs_rexml(schema_enty, relman, xnamespace)
|
322
|
+
@nav_entity_attribs&.each do |ne, klass|
|
323
|
+
nattr = metadata_nav_rexml_attribs(ne,
|
324
|
+
klass,
|
325
|
+
relman,
|
326
|
+
xnamespace)
|
327
|
+
schema_enty.add_element('NavigationProperty', nattr)
|
328
|
+
end
|
287
329
|
|
288
|
-
|
330
|
+
@nav_collection_attribs&.each do |nc, klass|
|
331
|
+
nattr = metadata_nav_rexml_attribs(nc,
|
332
|
+
klass,
|
333
|
+
relman,
|
334
|
+
xnamespace)
|
335
|
+
schema_enty.add_element('NavigationProperty', nattr)
|
289
336
|
end
|
290
337
|
end
|
291
338
|
|
339
|
+
D = 'd'.freeze
|
340
|
+
DJ_OPEN = '{"d":'.freeze
|
341
|
+
DJ_CLOSE = '}'.freeze
|
292
342
|
def to_odata_links_json(service:)
|
293
|
-
|
343
|
+
innerj = service.get_coll_odata_links_h(array: get_a,
|
294
344
|
uribase: @uribase,
|
295
|
-
icount: @inlinecount)
|
345
|
+
icount: @inlinecount).to_json
|
346
|
+
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
296
347
|
end
|
297
348
|
|
298
|
-
def
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
icount: @inlinecount) }.to_json
|
349
|
+
# def output_template(expand: nil, select: nil)
|
350
|
+
def output_template(uparms)
|
351
|
+
# output_template_deep(expand_list: expand_list, select: select)
|
352
|
+
output_template_deep(expand_list: uparms.expand.template, select: uparms.select)
|
303
353
|
end
|
304
354
|
|
305
|
-
|
306
|
-
|
307
|
-
|
355
|
+
# Recursive
|
356
|
+
def output_template_deep(expand_list:, select: OData::SelectBase::ALL)
|
357
|
+
return default_template if expand_list.empty? && select.all_props?
|
308
358
|
|
309
|
-
|
310
|
-
|
311
|
-
|
359
|
+
template = {}
|
360
|
+
expand_e = {}
|
361
|
+
expand_c = {}
|
362
|
+
deferr = []
|
312
363
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
364
|
+
# 1. handle non-navigation properties, only consider $select
|
365
|
+
# 2. handle navigations properties, need to check $select and $expand
|
366
|
+
if select.all_props?
|
367
|
+
template[:all_values] = EMPTYH
|
368
|
+
# include all nav attributes -->
|
369
|
+
@nav_entity_attribs&.each do |attr, klass|
|
370
|
+
if expand_list.key?(attr)
|
371
|
+
expand_e[attr] = klass.output_template_deep(expand_list: expand_list[attr])
|
372
|
+
else
|
373
|
+
deferr << attr
|
374
|
+
end
|
319
375
|
end
|
320
376
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
377
|
+
@nav_collection_attribs&.each do |attr, klass|
|
378
|
+
if expand_list.key?(attr)
|
379
|
+
expand_c[attr] = klass.output_template_deep(expand_list: expand_list[attr])
|
380
|
+
else
|
381
|
+
deferr << attr
|
382
|
+
end
|
383
|
+
end
|
326
384
|
|
327
|
-
|
328
|
-
|
329
|
-
|
385
|
+
else
|
386
|
+
template[:selected_vals] = @columns.map(&:to_s) & select.props
|
387
|
+
# include only selected nav attribs-->need additional intersection step
|
388
|
+
if @nav_entity_attribs
|
389
|
+
selected_nav_e = @nav_entity_attribs.keys & select.props
|
390
|
+
|
391
|
+
selected_nav_e&.each do |attr|
|
392
|
+
if expand_list.key?(attr)
|
393
|
+
klass = @nav_entity_attribs[attr]
|
394
|
+
expand_e[attr] = klass.output_template_deep(expand_list: expand_list[attr])
|
395
|
+
else
|
396
|
+
deferr << attr
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
if @nav_collection_attribs
|
401
|
+
selected_nav_c = @nav_collection_attribs.keys & select.props
|
402
|
+
selected_nav_c&.each do |attr|
|
403
|
+
if expand_list.key?(attr)
|
404
|
+
klass = @nav_collection_attribs[attr]
|
405
|
+
expand_c[attr] = klass.output_template_deep(expand_list: expand_list[attr])
|
406
|
+
else
|
407
|
+
deferr << attr
|
408
|
+
end
|
409
|
+
end
|
330
410
|
end
|
331
411
|
end
|
412
|
+
template[:expand_e] = expand_e if expand_e
|
413
|
+
template[:expand_c] = expand_c if expand_c
|
414
|
+
template[:deferr] = deferr if deferr
|
415
|
+
template
|
416
|
+
end
|
417
|
+
|
418
|
+
def to_odata_json(service:)
|
419
|
+
template = output_template(@uparms)
|
420
|
+
innerj = service.get_coll_odata_h(array: get_a,
|
421
|
+
template: template,
|
422
|
+
uribase: @uribase,
|
423
|
+
icount: @inlinecount).to_json
|
424
|
+
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
425
|
+
end
|
426
|
+
|
427
|
+
def get_a
|
428
|
+
@cx.all
|
332
429
|
end
|
333
430
|
|
334
431
|
# this functionally similar to the Sequel Rels (many_to_one etc)
|
@@ -340,9 +437,8 @@ module OData
|
|
340
437
|
assoc = all_association_reflections.find do |a|
|
341
438
|
a[:name] == assoc_symb && a[:model] == self
|
342
439
|
end
|
343
|
-
|
344
|
-
|
345
|
-
end
|
440
|
+
|
441
|
+
raise OData::API::ModelAssociationNameError.new(self, assoc_symb) unless assoc
|
346
442
|
|
347
443
|
attr_class = assoc[:class_name].constantize
|
348
444
|
lattr_name_str = (attr_name_str || assoc_symb.to_s)
|
@@ -357,9 +453,8 @@ module OData
|
|
357
453
|
assoc = all_association_reflections.find do |a|
|
358
454
|
a[:name] == assoc_symb && a[:model] == self
|
359
455
|
end
|
360
|
-
|
361
|
-
|
362
|
-
end
|
456
|
+
|
457
|
+
raise OData::API::ModelAssociationNameError.new(self, assoc_symb) unless assoc
|
363
458
|
|
364
459
|
attr_class = assoc[:class_name].constantize
|
365
460
|
lattr_name_str = (attr_name_str || assoc_symb.to_s)
|
@@ -367,10 +462,30 @@ module OData
|
|
367
462
|
@nav_entity_url_regexp = @nav_entity_attribs.keys.join('|')
|
368
463
|
end
|
369
464
|
|
465
|
+
EMPTYH = {}.freeze
|
466
|
+
|
467
|
+
def build_default_template
|
468
|
+
template = { all_values: EMPTYH }
|
469
|
+
if @nav_entity_attribs || @nav_collection_attribs
|
470
|
+
template[:deferr] = (@nav_entity_attribs&.keys || []) + (@nav_collection_attribs&.keys || EMPTY_ARRAY)
|
471
|
+
end
|
472
|
+
template
|
473
|
+
end
|
370
474
|
# old names...
|
371
475
|
# alias_method :add_nav_prop_collection, :addNavCollectionAttrib
|
372
476
|
# alias_method :add_nav_prop_single, :addNavEntityAttrib
|
373
477
|
|
478
|
+
def finalize_publishing
|
479
|
+
# finalize media handler
|
480
|
+
@media_handler.register(self) if @media_handler
|
481
|
+
|
482
|
+
# build default output template structure
|
483
|
+
@default_template = build_default_template
|
484
|
+
|
485
|
+
# and finally build the path list
|
486
|
+
build_attribute_path_list
|
487
|
+
end
|
488
|
+
|
374
489
|
def prepare_pk
|
375
490
|
if primary_key.is_a? Array
|
376
491
|
@pk_names = []
|
@@ -419,11 +534,7 @@ module OData
|
|
419
534
|
def check_odata_val_type(val, type)
|
420
535
|
case type
|
421
536
|
when :integer
|
422
|
-
|
423
|
-
[true, Integer(val)]
|
424
|
-
else
|
425
|
-
[false, val]
|
426
|
-
end
|
537
|
+
val =~ ONLY_INTEGER_RGX ? [true, Integer(val)] : [false, val]
|
427
538
|
when :string
|
428
539
|
[true, val]
|
429
540
|
else
|
@@ -471,6 +582,7 @@ module OData
|
|
471
582
|
end
|
472
583
|
include Transitions
|
473
584
|
end
|
585
|
+
|
474
586
|
# special handling for composite key
|
475
587
|
module EntityClassMultiPK
|
476
588
|
include EntityClassBase
|
@@ -483,7 +595,7 @@ module OData
|
|
483
595
|
md.shift # remove first element which is the whole match
|
484
596
|
mdc = []
|
485
597
|
error = false
|
486
|
-
primary_key.each_with_index
|
598
|
+
primary_key.each_with_index do |pk, i|
|
487
599
|
ck, casted = check_odata_val_type(md[i], db_schema[pk][:type])
|
488
600
|
if ck
|
489
601
|
mdc << casted
|
@@ -491,7 +603,7 @@ module OData
|
|
491
603
|
error = true
|
492
604
|
break
|
493
605
|
end
|
494
|
-
|
606
|
+
end
|
495
607
|
if error
|
496
608
|
[false, md]
|
497
609
|
else
|
@@ -508,4 +620,38 @@ module OData
|
|
508
620
|
check_odata_val_type(id, db_schema[primary_key][:type])
|
509
621
|
end
|
510
622
|
end
|
623
|
+
|
624
|
+
# normal handling for non-media entity
|
625
|
+
module EntityClassNonMedia
|
626
|
+
# POST for non-media entity collection -->
|
627
|
+
# 1. Create and add entity from payload
|
628
|
+
# 2. Create relationship if needed
|
629
|
+
def odata_create_entity_and_relation(req, assoc, parent)
|
630
|
+
# TODO: this is for v2 only...
|
631
|
+
req.with_parsed_data do |data|
|
632
|
+
data.delete('__metadata')
|
633
|
+
# validate payload column names
|
634
|
+
if (invalid = invalid_hash_data?(data))
|
635
|
+
::OData::Request::ON_CGST_ERROR.call(req)
|
636
|
+
return [422, EMPTY_HASH, ['Invalid attribute name: ', invalid.to_s]]
|
637
|
+
end
|
638
|
+
|
639
|
+
if req.accept?(APPJSON)
|
640
|
+
new_entity = new_from_hson_h(data)
|
641
|
+
if parent
|
642
|
+
odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
|
643
|
+
else
|
644
|
+
# in-changeset requests get their own transaction
|
645
|
+
new_entity.save(transaction: !req.in_changeset)
|
646
|
+
end
|
647
|
+
req.register_content_id_ref(new_entity)
|
648
|
+
new_entity.copy_request_infos(req)
|
649
|
+
|
650
|
+
[201, CT_JSON, new_entity.to_odata_post_json(service: req.service)]
|
651
|
+
else # TODO: other formats
|
652
|
+
415
|
653
|
+
end
|
654
|
+
end
|
655
|
+
end
|
656
|
+
end
|
511
657
|
end
|