safrano 0.3.3 → 0.4.3

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/odata/attribute.rb +9 -8
  3. data/lib/odata/batch.rb +8 -8
  4. data/lib/odata/collection.rb +239 -92
  5. data/lib/odata/collection_filter.rb +40 -9
  6. data/lib/odata/collection_media.rb +159 -28
  7. data/lib/odata/collection_order.rb +46 -36
  8. data/lib/odata/common_logger.rb +37 -12
  9. data/lib/odata/entity.rb +188 -99
  10. data/lib/odata/error.rb +60 -12
  11. data/lib/odata/expand.rb +123 -0
  12. data/lib/odata/filter/base.rb +66 -0
  13. data/lib/odata/filter/error.rb +33 -0
  14. data/lib/odata/filter/parse.rb +6 -12
  15. data/lib/odata/filter/sequel.rb +42 -29
  16. data/lib/odata/filter/sequel_function_adapter.rb +147 -0
  17. data/lib/odata/filter/token.rb +5 -1
  18. data/lib/odata/filter/tree.rb +45 -29
  19. data/lib/odata/navigation_attribute.rb +60 -27
  20. data/lib/odata/relations.rb +2 -2
  21. data/lib/odata/select.rb +42 -0
  22. data/lib/odata/url_parameters.rb +51 -36
  23. data/lib/odata/walker.rb +6 -6
  24. data/lib/safrano.rb +23 -13
  25. data/lib/{safrano_core.rb → safrano/core.rb} +12 -4
  26. data/lib/{multipart.rb → safrano/multipart.rb} +17 -26
  27. data/lib/{odata_rack_builder.rb → safrano/odata_rack_builder.rb} +0 -1
  28. data/lib/{rack_app.rb → safrano/rack_app.rb} +12 -10
  29. data/lib/{request.rb → safrano/request.rb} +8 -14
  30. data/lib/{response.rb → safrano/response.rb} +1 -2
  31. data/lib/{sequel_join_by_paths.rb → safrano/sequel_join_by_paths.rb} +1 -1
  32. data/lib/{service.rb → safrano/service.rb} +162 -131
  33. data/lib/safrano/version.rb +3 -0
  34. data/lib/sequel/plugins/join_by_paths.rb +11 -10
  35. metadata +33 -16
  36. data/lib/version.rb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b25412d7e053724965ba296bdd43a6f0b3bbd8a62cee2f45a14edf18303ed6c1
4
- data.tar.gz: 1e9ad5ce553d13199e4ce79cecf7209ce1bb2695f5c476d8f5e5e16bd57bd4f9
3
+ metadata.gz: 497482614bfc6a87f8a42b9378a4fc8099ddb5b4f180d747b3717a6952f027b0
4
+ data.tar.gz: cd5236302671643fd9fbd9bcd200034f305ce7069e5d56ab2db2225dcfb4ce87
5
5
  SHA512:
6
- metadata.gz: ad1361bb963f77870f784ed5322318b2567bac06e6ef915dcda62f57df767a0893a988672e4ace8dbd64cdda0cbff8d78de2aa712d4ebceb8821ed109018c909
7
- data.tar.gz: d4f5f16fa548883e697479f313aa824c0798c38d71ead64172552ddfaec79fb53652063bf6dbfe36362cca5c30a80534665e8a12895fb2f343f1857a0dba8c1c
6
+ metadata.gz: 4415ca4dc24362f3c179f0cc40f1d73fac6fb7207c0571ef63f1af318fc345e0be8043d8aa1b5ab48e0ead1f5c904dcab07b84061b3ca47f6b7d3c38696d4ae8
7
+ data.tar.gz: 6646d12aadb3827b43c013a3da6c6223d072e5676f8676eac0db59ad8e3ac80d1a64ceb0f5e87d52bb75e5b556eecda701e2d473c4bdd3b650cb680b6ce19b96
@@ -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
@@ -13,15 +13,14 @@ module OData
13
13
  end
14
14
 
15
15
  def value
16
- # WARNING; this code is duplicated in entity.rb
17
- # (and the inverted transformation is in test/client.rb)
18
- # will require a more systematic solution some day
19
- # WARNING 2... this require more work to handle the timezones topci
16
+ # WARNING ... this require more work to handle the timezones topci
20
17
  # currently it is just set to make some minimal testcase work
21
18
  case (v = @entity.values[@name.to_sym])
22
19
  when Time
23
20
  # try to get back the database time zone and value
24
- (v + v.gmt_offset).utc.to_datetime
21
+ # (v + v.gmt_offset).utc.to_datetime
22
+ v.iso8601
23
+
25
24
  else
26
25
  v
27
26
  end
@@ -57,9 +56,11 @@ module OData
57
56
  [self, :end_with_value]
58
57
  end
59
58
 
59
+ ALLOWED_TRANSITIONS = [Safrano::TransitionEnd,
60
+ Safrano::TransitionValue].freeze
61
+
60
62
  def allowed_transitions
61
- [Safrano::TransitionEnd,
62
- Safrano::TransitionValue]
63
+ ALLOWED_TRANSITIONS
63
64
  end
64
65
  end
65
66
  include Transitions
@@ -1,5 +1,5 @@
1
- require 'rack_app.rb'
2
- require 'safrano_core.rb'
1
+ require_relative '../safrano/rack_app.rb'
2
+ require_relative '../safrano/core.rb'
3
3
  require 'rack/body_proxy'
4
4
  require_relative './common_logger.rb'
5
5
 
@@ -59,7 +59,7 @@ module OData
59
59
  # TODO: test ?
60
60
  if (logga = @full_req.env['safrano.logger_mw'])
61
61
  logga.batch_log(env, status, header, began_at)
62
- # TODO check why/if we need Rack::Utils::HeaderHash.new(header)
62
+ # TODO: check why/if we need Rack::Utils::HeaderHash.new(header)
63
63
  # and Rack::BodyProxy.new(body) ?
64
64
  end
65
65
  [status, header, body]
@@ -71,7 +71,7 @@ module OData
71
71
 
72
72
  headers.each do |name, value|
73
73
  env_key = name.upcase.tr('-', '_')
74
- env_key = 'HTTP_' + env_key unless env_key == 'CONTENT_TYPE'
74
+ env_key = "HTTP_#{env_key}" unless env_key == 'CONTENT_TYPE'
75
75
  converted_headers[env_key] = value
76
76
  end
77
77
 
@@ -104,11 +104,11 @@ module OData
104
104
  # jaune d'oeuf
105
105
  class DisabledHandler < HandlerBase
106
106
  def odata_post(_req)
107
- [404, {}, '$batch is not enabled ']
107
+ [404, EMPTY_HASH, '$batch is not enabled ']
108
108
  end
109
109
 
110
110
  def odata_get(_req)
111
- [404, {}, '$batch is not enabled ']
111
+ [404, EMPTY_HASH, '$batch is not enabled ']
112
112
  end
113
113
  end
114
114
  # battre le tout
@@ -138,12 +138,12 @@ module OData
138
138
 
139
139
  [202, resp_hdrs, @mult_response.body[0]]
140
140
  else
141
- [415, {}, 'Unsupported Media Type']
141
+ [415, EMPTY_HASH, 'Unsupported Media Type']
142
142
  end
143
143
  end
144
144
 
145
145
  def odata_get(_req)
146
- [405, {}, 'You cant GET $batch, POST it ']
146
+ [405, EMPTY_HASH, 'You cant GET $batch, POST it ']
147
147
  end
148
148
  end
149
149
  end
@@ -4,12 +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'
12
- require 'odata/collection_media.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'
13
15
 
14
16
  # small helper method
15
17
  # http://stackoverflow.com/
@@ -43,16 +45,25 @@ module OData
43
45
  # we will add this to our Model classes with "extend" --> self is the Class
44
46
  module EntityClassBase
45
47
  SINGLE_PK_URL_REGEXP = /\A\(\s*'?([\w\s]+)'?\s*\)(.*)/.freeze
46
- ONLY_INTEGER_RGX = /\A[+-]?\d+\z/
48
+ ONLY_INTEGER_RGX = /\A[+-]?\d+\z/.freeze
47
49
 
48
50
  attr_reader :nav_collection_url_regexp
49
51
  attr_reader :nav_entity_url_regexp
50
52
  attr_reader :entity_id_url_regexp
51
- attr_reader :attrib_paths_url_regexp
52
53
  attr_reader :nav_collection_attribs
53
54
  attr_reader :nav_entity_attribs
54
55
  attr_reader :data_fields
55
56
  attr_reader :inlinecount
57
+ attr_reader :default_template
58
+ attr_reader :uri
59
+ attr_reader :time_cols
60
+
61
+ # Sequel associations pointing to this model. Sequel provides association
62
+ # reflection information on the "from" side. But in some cases
63
+ # we will need the reverted way
64
+ # finally not needed and not used yet
65
+ # attr_accessor :assocs_to
66
+
56
67
  # set to parent entity in case the collection is a nav.collection
57
68
  # nil otherwise
58
69
  attr_reader :nav_parent
@@ -62,9 +73,6 @@ module OData
62
73
  # dataset
63
74
  attr_accessor :cx
64
75
 
65
- # array of the objects --> dataset.to_a
66
- attr_accessor :ax
67
-
68
76
  # url params
69
77
  attr_reader :params
70
78
 
@@ -84,7 +92,11 @@ module OData
84
92
  # the class-name is not the OData Type. In these subclass we redefine "type_name"
85
93
  # thus when we need the Odata type name, we shall use this method instead of just the collection class name
86
94
  def type_name
87
- to_s
95
+ @type_name
96
+ end
97
+
98
+ def build_type_name
99
+ @type_name = to_s
88
100
  end
89
101
 
90
102
  # convention: default for entity_set_name is the type name
@@ -96,10 +108,15 @@ module OData
96
108
  # TODO: automatically reset all attributes?
97
109
  @deferred_iblock = nil
98
110
  @entity_set_name = nil
111
+ @uri = nil
99
112
  @uparms = nil
100
113
  @params = nil
101
- @ax = nil
102
114
  @cx = nil
115
+ @@time_cols = nil
116
+ end
117
+
118
+ def build_uri(uribase)
119
+ @uri = "#{uribase}/#{entity_set_name}"
103
120
  end
104
121
 
105
122
  def execute_deferred_iblock
@@ -123,8 +140,7 @@ module OData
123
140
  new_entity.save(transaction: false)
124
141
  OData.create_nav_relation(new_entity, assoc, parent)
125
142
  parent.save(transaction: false)
126
- else
127
- # not supported
143
+ # else # not supported
128
144
  end
129
145
  end
130
146
  def odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
@@ -155,15 +171,7 @@ module OData
155
171
  end
156
172
 
157
173
  def odata_get_apply_params
158
- begin
159
- @cx = @uparms.apply_to_dataset(@cx)
160
- rescue OData::Filter::Parser::ErrorWrongColumnName
161
- @error = BadRequestFilterParseError
162
- return
163
- rescue OData::Filter::Parser::ErrorFunctionArgumentType
164
- @error = BadRequestFilterParseError
165
- return
166
- end
174
+ @cx = @uparms.apply_to_dataset(@cx)
167
175
  odata_get_inlinecount_w_sequel
168
176
 
169
177
  @cx = @cx.offset(@params['$skip']) if @params['$skip']
@@ -192,9 +200,7 @@ module OData
192
200
  def check_u_p_inlinecount
193
201
  return unless (icp = @params['$inlinecount'])
194
202
 
195
- unless (icp == 'allpages') || (icp == 'none')
196
- return BadRequestInlineCountParamError
197
- end
203
+ return BadRequestInlineCountParamError unless (icp == 'allpages') || (icp == 'none')
198
204
 
199
205
  nil
200
206
  end
@@ -207,26 +213,27 @@ module OData
207
213
  @uparms.check_order
208
214
  end
209
215
 
216
+ def check_u_p_expand
217
+ @uparms.check_expand
218
+ end
219
+
210
220
  def build_attribute_path_list
211
221
  @attribute_path_list = attribute_path_list
212
- @attrib_paths_url_regexp = @attribute_path_list.join('|')
213
222
  end
214
223
 
215
- def attribute_path_list(nodes = Set.new)
224
+ MAX_DEPTH = 6
225
+ def attribute_path_list(depth = 0)
226
+ ret = @columns.map(&:to_s)
216
227
  # break circles
217
- return [] if nodes.include?(entity_set_name)
228
+ return ret if depth > MAX_DEPTH
218
229
 
219
- ret = @columns.map(&:to_s)
220
- nodes.add entity_set_name
221
- if @nav_entity_attribs
222
- @nav_entity_attribs.each do |a, k|
223
- ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
224
- end
230
+ depth += 1
231
+
232
+ @nav_entity_attribs&.each do |a, k|
233
+ ret.concat(k.attribute_path_list(depth).map { |kc| "#{a}/#{kc}" })
225
234
  end
226
- if @nav_collection_attribs
227
- @nav_collection_attribs.each do |a, k|
228
- ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
229
- end
235
+ @nav_collection_attribs&.each do |a, k|
236
+ ret.concat(k.attribute_path_list(depth).map { |kc| "#{a}/#{kc}" })
230
237
  end
231
238
  ret
232
239
  end
@@ -235,20 +242,18 @@ module OData
235
242
  return nil unless @params
236
243
 
237
244
  check_u_p_top || check_u_p_skip || check_u_p_orderby ||
238
- check_u_p_filter || check_u_p_inlinecount
245
+ check_u_p_filter || check_u_p_expand || check_u_p_inlinecount
239
246
  end
240
247
 
241
248
  def initialize_dataset
242
249
  @cx = self
243
- @ax = nil
244
250
  @cx = navigated_dataset if @cx.nav_parent
245
251
  @model = if @cx.respond_to? :model
246
252
  @cx.model
247
253
  else
248
254
  @cx
249
255
  end
250
- @jh = @model.join_by_paths_helper
251
- @uparms = UrlParameters.new(@jh, @params)
256
+ @uparms = UrlParameters4Coll.new(@model, @params)
252
257
  end
253
258
 
254
259
  # finally return the requested output according to format, options etc
@@ -268,15 +273,31 @@ module OData
268
273
  end
269
274
  end
270
275
 
276
+ # validation/error handling methods.
277
+ # normal processing is done in the passed proc
278
+
279
+ def with_validated_get(req)
280
+ begin
281
+ initialize_dataset
282
+ return yield unless (@error = check_url_params)
283
+ rescue OData::Filter::Parser::ErrorWrongColumnName
284
+ @error = BadRequestFilterParseError
285
+ rescue OData::Filter::Parser::ErrorFunctionArgumentType
286
+ @error = BadRequestFilterParseError
287
+ rescue OData::Filter::FunctionNotImplemented => e
288
+ @error = e.odata_error
289
+ rescue OData::Filter::Parser::ErrorInvalidFunction => e
290
+ @error = e.odata_error
291
+ end
292
+
293
+ @error.odata_get(req) if @error
294
+ end
295
+
271
296
  # on model class level we return the collection
272
297
  def odata_get(req)
273
298
  @params = req.params
274
- @uribase = req.uribase
275
- initialize_dataset
276
299
 
277
- if (@error = check_url_params)
278
- @error.odata_get(req)
279
- else
300
+ with_validated_get(req) do
280
301
  odata_get_apply_params
281
302
  odata_get_output(req)
282
303
  end
@@ -288,7 +309,11 @@ module OData
288
309
 
289
310
  # add metadata xml to the passed REXML schema object
290
311
  def add_metadata_rexml(schema)
291
- enty = schema.add_element('EntityType', 'Name' => to_s)
312
+ enty = if @media_handler
313
+ schema.add_element('EntityType', 'Name' => to_s, 'HasStream' => 'true')
314
+ else
315
+ schema.add_element('EntityType', 'Name' => to_s)
316
+ end
292
317
  # with their properties
293
318
  db_schema.each do |pnam, prop|
294
319
  if prop[:primary_key] == true
@@ -304,47 +329,119 @@ module OData
304
329
  end
305
330
 
306
331
  # metadata REXML data for a single Nav attribute
307
- def metadata_nav_rexml_attribs(assoc, cmap, relman, xnamespace)
332
+ def metadata_nav_rexml_attribs(assoc, to_klass, relman, xnamespace)
308
333
  from = type_name
309
- to = cmap[assoc.to_s].type_name
334
+ to = to_klass.type_name
310
335
  relman.get_metadata_xml_attribs(from,
311
336
  to,
312
- association_reflection(assoc)[:type],
313
- xnamespace)
337
+ association_reflection(assoc.to_sym)[:type],
338
+ xnamespace,
339
+ assoc)
314
340
  end
315
341
 
316
342
  # and their Nav attributes == Sequel Model association
317
- def add_metadata_navs_rexml(schema_enty, cmap, relman, xnamespace)
318
- associations.each do |assoc|
319
- # associated objects need to be in the map...
320
- next unless cmap[assoc.to_s]
321
-
322
- nattrs = metadata_nav_rexml_attribs(assoc, cmap, relman, xnamespace)
343
+ def add_metadata_navs_rexml(schema_enty, relman, xnamespace)
344
+ @nav_entity_attribs&.each do |ne, klass|
345
+ nattr = metadata_nav_rexml_attribs(ne,
346
+ klass,
347
+ relman,
348
+ xnamespace)
349
+ schema_enty.add_element('NavigationProperty', nattr)
350
+ end
323
351
 
324
- schema_enty.add_element('NavigationProperty', nattrs)
352
+ @nav_collection_attribs&.each do |nc, klass|
353
+ nattr = metadata_nav_rexml_attribs(nc,
354
+ klass,
355
+ relman,
356
+ xnamespace)
357
+ schema_enty.add_element('NavigationProperty', nattr)
325
358
  end
326
359
  end
327
360
 
328
361
  D = 'd'.freeze
329
- DJopen = '{"d":'.freeze
330
- DJclose = '}'.freeze
362
+ DJ_OPEN = '{"d":'.freeze
363
+ DJ_CLOSE = '}'.freeze
331
364
  def to_odata_links_json(service:)
332
- innerj = service.get_coll_odata_links_h(array: get_a,
333
- uribase: @uribase,
365
+ innerj = service.get_coll_odata_links_h(array: @cx.all,
334
366
  icount: @inlinecount).to_json
335
- "#{DJopen}#{innerj}#{DJclose}"
367
+ "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
336
368
  end
337
369
 
338
- def to_odata_json(service:)
339
- innerj = service.get_coll_odata_h(array: get_a,
340
- expand: @params['$expand'],
341
- uribase: @uribase,
342
- icount: @inlinecount).to_json
343
- "#{DJopen}#{innerj}#{DJclose}"
370
+ # def output_template(expand: nil, select: nil)
371
+ def output_template(uparms)
372
+ # output_template_deep(expand_list: expand_list, select: select)
373
+ output_template_deep(expand_list: uparms.expand.template, select: uparms.select)
374
+ end
375
+
376
+ # Recursive
377
+ def output_template_deep(expand_list:, select: OData::SelectBase::ALL)
378
+ return default_template if expand_list.empty? && select.all_props?
379
+
380
+ template = {}
381
+ expand_e = {}
382
+ expand_c = {}
383
+ deferr = []
384
+
385
+ # 1. handle non-navigation properties, only consider $select
386
+ # 2. handle navigations properties, need to check $select and $expand
387
+ if select.all_props?
388
+ template[:all_values] = EMPTYH
389
+ # include all nav attributes -->
390
+ @nav_entity_attribs&.each do |attr, klass|
391
+ if expand_list.key?(attr)
392
+ expand_e[attr] = klass.output_template_deep(expand_list: expand_list[attr])
393
+ else
394
+ deferr << attr
395
+ end
396
+ end
397
+
398
+ @nav_collection_attribs&.each do |attr, klass|
399
+ if expand_list.key?(attr)
400
+ expand_c[attr] = klass.output_template_deep(expand_list: expand_list[attr])
401
+ else
402
+ deferr << attr
403
+ end
404
+ end
405
+
406
+ else
407
+ template[:selected_vals] = @columns.map(&:to_s) & select.props
408
+ # include only selected nav attribs-->need additional intersection step
409
+ if @nav_entity_attribs
410
+ selected_nav_e = @nav_entity_attribs.keys & select.props
411
+
412
+ selected_nav_e&.each do |attr|
413
+ if expand_list.key?(attr)
414
+ klass = @nav_entity_attribs[attr]
415
+ expand_e[attr] = klass.output_template_deep(expand_list: expand_list[attr])
416
+ else
417
+ deferr << attr
418
+ end
419
+ end
420
+ end
421
+ if @nav_collection_attribs
422
+ selected_nav_c = @nav_collection_attribs.keys & select.props
423
+ selected_nav_c&.each do |attr|
424
+ if expand_list.key?(attr)
425
+ klass = @nav_collection_attribs[attr]
426
+ expand_c[attr] = klass.output_template_deep(expand_list: expand_list[attr])
427
+ else
428
+ deferr << attr
429
+ end
430
+ end
431
+ end
432
+ end
433
+ template[:expand_e] = expand_e if expand_e
434
+ template[:expand_c] = expand_c if expand_c
435
+ template[:deferr] = deferr if deferr
436
+ template
344
437
  end
345
438
 
346
- def get_a
347
- @ax.nil? ? @cx.to_a : @ax
439
+ def to_odata_json(service:)
440
+ template = output_template(@uparms)
441
+ innerj = service.get_coll_odata_h(array: @cx.all,
442
+ template: template,
443
+ icount: @inlinecount).to_json
444
+ "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
348
445
  end
349
446
 
350
447
  # this functionally similar to the Sequel Rels (many_to_one etc)
@@ -356,9 +453,8 @@ module OData
356
453
  assoc = all_association_reflections.find do |a|
357
454
  a[:name] == assoc_symb && a[:model] == self
358
455
  end
359
- unless assoc
360
- raise OData::API::ModelAssociationNameError.new(self, assoc_symb)
361
- end
456
+
457
+ raise OData::API::ModelAssociationNameError.new(self, assoc_symb) unless assoc
362
458
 
363
459
  attr_class = assoc[:class_name].constantize
364
460
  lattr_name_str = (attr_name_str || assoc_symb.to_s)
@@ -373,9 +469,8 @@ module OData
373
469
  assoc = all_association_reflections.find do |a|
374
470
  a[:name] == assoc_symb && a[:model] == self
375
471
  end
376
- unless assoc
377
- raise OData::API::ModelAssociationNameError.new(self, assoc_symb)
378
- end
472
+
473
+ raise OData::API::ModelAssociationNameError.new(self, assoc_symb) unless assoc
379
474
 
380
475
  attr_class = assoc[:class_name].constantize
381
476
  lattr_name_str = (attr_name_str || assoc_symb.to_s)
@@ -383,10 +478,38 @@ module OData
383
478
  @nav_entity_url_regexp = @nav_entity_attribs.keys.join('|')
384
479
  end
385
480
 
481
+ EMPTYH = {}.freeze
482
+
483
+ def build_default_template
484
+ template = { all_values: EMPTYH }
485
+ if @nav_entity_attribs || @nav_collection_attribs
486
+ template[:deferr] = (@nav_entity_attribs&.keys || []) + (@nav_collection_attribs&.keys || EMPTY_ARRAY)
487
+ end
488
+ template
489
+ end
386
490
  # old names...
387
491
  # alias_method :add_nav_prop_collection, :addNavCollectionAttrib
388
492
  # alias_method :add_nav_prop_single, :addNavEntityAttrib
389
493
 
494
+ def finalize_publishing
495
+ build_type_name
496
+
497
+ # finalize media handler
498
+ @media_handler.register(self) if @media_handler
499
+
500
+ # build default output template structure
501
+ @default_template = build_default_template
502
+
503
+ # Time columns
504
+ @time_cols = db_schema.select { |c, v| v[:type] == :datetime }.map { |c, v| c }
505
+
506
+ # and finally build the path list and allowed tr's
507
+ build_attribute_path_list
508
+
509
+ build_allowed_transitions
510
+ build_entity_allowed_transitions
511
+ end
512
+
390
513
  def prepare_pk
391
514
  if primary_key.is_a? Array
392
515
  @pk_names = []
@@ -435,11 +558,7 @@ module OData
435
558
  def check_odata_val_type(val, type)
436
559
  case type
437
560
  when :integer
438
- if (val =~ ONLY_INTEGER_RGX)
439
- [true, Integer(val)]
440
- else
441
- [false, val]
442
- end
561
+ val =~ ONLY_INTEGER_RGX ? [true, Integer(val)] : [false, val]
443
562
  when :string
444
563
  [true, val]
445
564
  else
@@ -479,10 +598,38 @@ module OData
479
598
  end
480
599
 
481
600
  def allowed_transitions
482
- [Safrano::TransitionEnd,
483
- Safrano::TransitionCount,
484
- Safrano::Transition.new(entity_id_url_regexp,
485
- trans: 'transition_id')]
601
+ @allowed_transitions
602
+ end
603
+
604
+ def entity_allowed_transitions
605
+ @entity_allowed_transitions
606
+ end
607
+
608
+ def build_allowed_transitions
609
+ @allowed_transitions = [Safrano::TransitionEnd,
610
+ Safrano::TransitionCount,
611
+ Safrano::Transition.new(entity_id_url_regexp,
612
+ trans: 'transition_id')].freeze
613
+ end
614
+
615
+ def build_entity_allowed_transitions
616
+ @entity_allowed_transitions = [
617
+ Safrano::TransitionEnd,
618
+ Safrano::TransitionCount,
619
+ Safrano::TransitionLinks,
620
+ Safrano::TransitionValue,
621
+ Safrano::Transition.new(transition_attribute_regexp, trans: 'transition_attribute')
622
+ ]
623
+ if (ncurgx = @nav_collection_url_regexp)
624
+ @entity_allowed_transitions <<
625
+ Safrano::Transition.new(%r{\A/(#{ncurgx})(.*)\z}, trans: 'transition_nav_collection')
626
+ end
627
+ if (neurgx = @nav_entity_url_regexp)
628
+ @entity_allowed_transitions <<
629
+ Safrano::Transition.new(%r{\A/(#{neurgx})(.*)\z}, trans: 'transition_nav_entity')
630
+ end
631
+ @entity_allowed_transitions.freeze
632
+ @entity_allowed_transitions
486
633
  end
487
634
  end
488
635
  include Transitions
@@ -500,7 +647,7 @@ module OData
500
647
  md.shift # remove first element which is the whole match
501
648
  mdc = []
502
649
  error = false
503
- primary_key.each_with_index { |pk, i|
650
+ primary_key.each_with_index do |pk, i|
504
651
  ck, casted = check_odata_val_type(md[i], db_schema[pk][:type])
505
652
  if ck
506
653
  mdc << casted
@@ -508,7 +655,7 @@ module OData
508
655
  error = true
509
656
  break
510
657
  end
511
- }
658
+ end
512
659
  if error
513
660
  [false, md]
514
661
  else
@@ -538,7 +685,7 @@ module OData
538
685
  # validate payload column names
539
686
  if (invalid = invalid_hash_data?(data))
540
687
  ::OData::Request::ON_CGST_ERROR.call(req)
541
- return [422, {}, ['Invalid attribute name: ', invalid.to_s]]
688
+ return [422, EMPTY_HASH, ['Invalid attribute name: ', invalid.to_s]]
542
689
  end
543
690
 
544
691
  if req.accept?(APPJSON)