safrano 0.3.2 → 0.4.2
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/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
|