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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '048c3efb69194f00a6ed5593c23300ae7b9d46f7d98a546d443bd5d9c5d2c6c8'
4
- data.tar.gz: 825698055233240072faee6e038e531884e23d938fc1ed9708e98d27cf0db3c6
3
+ metadata.gz: 6397b8b41d6241576f613abfa0e36c8f6973fa96542b60d388b15954aaa3a92b
4
+ data.tar.gz: 3f36fe0d18bd9659d4e3ce27f501ef1540e82f24a26418c28760aae869de85d9
5
5
  SHA512:
6
- metadata.gz: d2697db38029b2701a418a620b74e7b8080d368cd34461073de46065d7057cb3b852cc62795cabb1bbbad82d10607bff96b899beb483a3c0f03e028cf8cb8a31
7
- data.tar.gz: 12a8674563e67e2f3c4e475d5867f8965e0095a0ff0afea80079f6e7e59c56921abc506d33b07ca483f5f2c16024176a6e3679db26b09e979a9f9fb6fac8d755
6
+ metadata.gz: f6f666fb8e2fb136fe7ca0c215e19c24648d9a1a4494c3cdb1a53f45561cf827c2670be56bcf74005828b3a9ebf78e2efc63e8b89db35fefb9f70d4c730e7ebb
7
+ data.tar.gz: 2cdfe6b8cb732184dacb256e5c114361f41da91f5a0f821c2f61a5b7ae541b0aa8f5ad45b23598c0d5301311c193f9f6e51d6e8b8def7eac78a54176f92cfd98
@@ -1,5 +1,5 @@
1
1
  require 'json'
2
- require_relative '../safrano_core.rb'
2
+ require_relative '../safrano/core.rb'
3
3
  require_relative './entity.rb'
4
4
 
5
5
  module OData
@@ -1,5 +1,7 @@
1
- require 'rack_app.rb'
2
- require 'safrano_core.rb'
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 = 'HTTP_' + env_key unless env_key == 'CONTENT_TYPE'
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, {}, '$batch is not enabled ']
107
+ [404, EMPTY_HASH, '$batch is not enabled ']
94
108
  end
95
109
 
96
110
  def odata_get(_req)
97
- [404, {}, '$batch is not enabled ']
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 == 'multipart/mixed'
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, {}, 'Unsupported Media Type']
141
+ [415, EMPTY_HASH, 'Unsupported Media Type']
128
142
  end
129
143
  end
130
144
 
131
145
  def odata_get(_req)
132
- [405, {}, 'You cant GET $batch, POST it ']
146
+ [405, EMPTY_HASH, 'You cant GET $batch, POST it ']
133
147
  end
134
148
  end
135
149
  end
@@ -4,11 +4,14 @@
4
4
 
5
5
  require 'json'
6
6
  require 'rexml/document'
7
- require 'safrano_core.rb'
8
- require 'odata/error.rb'
9
- require 'odata/collection_filter.rb'
10
- require 'odata/collection_order.rb'
11
- require 'odata/url_parameters.rb'
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 model table name
96
+ # convention: default for entity_set_name is the type name
83
97
  def entity_set_name
84
- @entity_set_name = (@entity_set_name || table_name.to_s)
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, in_changeset: false)
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 [] if nodes.include?(entity_set_name)
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
- if @nav_entity_attribs
190
- @nav_entity_attribs.each do |a, k|
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
- if @nav_collection_attribs
195
- @nav_collection_attribs.each do |a, k|
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
- @ax = nil
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
- @jh = @model.join_by_paths_helper
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 = schema.add_element('EntityType', 'Name' => to_s)
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, cmap, relman, xnamespace)
310
+ def metadata_nav_rexml_attribs(assoc, to_klass, relman, xnamespace)
272
311
  from = type_name
273
- to = cmap[assoc.to_s].type_name
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, cmap, relman, xnamespace)
282
- associations.each do |assoc|
283
- # associated objects need to be in the map...
284
- next unless cmap[assoc.to_s]
285
-
286
- nattrs = metadata_nav_rexml_attribs(assoc, cmap, relman, xnamespace)
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
- schema_enty.add_element('NavigationProperty', nattrs)
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
- { 'd' => service.get_coll_odata_links_h(array: get_a,
343
+ innerj = service.get_coll_odata_links_h(array: get_a,
294
344
  uribase: @uribase,
295
- icount: @inlinecount) }.to_json
345
+ icount: @inlinecount).to_json
346
+ "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
296
347
  end
297
348
 
298
- def to_odata_json(service:)
299
- { 'd' => service.get_coll_odata_h(array: get_a,
300
- expand: @params['$expand'],
301
- uribase: @uribase,
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
- def get_a
306
- @ax.nil? ? @cx.to_a : @ax
307
- end
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
- def odata_post(req)
310
- # TODO: this is for v2 only...
311
- on_error = (proc { raise Sequel::Rollback } if req.in_changeset)
359
+ template = {}
360
+ expand_e = {}
361
+ expand_c = {}
362
+ deferr = []
312
363
 
313
- req.with_parsed_data(on_error: on_error) do |data|
314
- data.delete('__metadata')
315
- # validate payload column names
316
- if (invalid = invalid_hash_data?(data))
317
- on_error.call if on_error
318
- return [422, {}, ['Invalid attribute name: ', invalid.to_s]]
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
- if req.accept?(APPJSON)
322
-
323
- new_entity = new_from_hson_h(data, in_changeset: req.in_changeset)
324
- req.register_content_id_ref(new_entity)
325
- new_entity.copy_request_infos(req)
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
- [201, CT_JSON, new_entity.to_odata_post_json(service: req.service)]
328
- else # TODO: other formats
329
- 415
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
- unless assoc
344
- raise OData::API::ModelAssociationNameError.new(self, assoc_symb)
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
- unless assoc
361
- raise OData::API::ModelAssociationNameError.new(self, assoc_symb)
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
- if (val =~ ONLY_INTEGER_RGX)
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 { |pk, i|
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