safrano 0.4.4 → 0.5.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 +2 -2
- data/lib/odata/collection.rb +15 -10
- data/lib/odata/collection_filter.rb +5 -5
- data/lib/odata/collection_media.rb +63 -25
- data/lib/odata/complex_type.rb +185 -43
- data/lib/odata/entity.rb +31 -8
- data/lib/odata/error.rb +28 -0
- data/lib/odata/filter/base.rb +5 -0
- data/lib/odata/filter/parse.rb +6 -0
- data/lib/odata/filter/sequel.rb +15 -0
- data/lib/odata/filter/token.rb +13 -10
- data/lib/odata/filter/tree.rb +4 -4
- data/lib/odata/function_import.rb +35 -34
- data/lib/odata/model_ext.rb +28 -5
- data/lib/odata/transition.rb +22 -2
- data/lib/odata/walker.rb +23 -5
- data/lib/safrano/contract.rb +7 -9
- data/lib/safrano/core.rb +12 -12
- data/lib/safrano/deprecation.rb +5 -5
- data/lib/safrano/multipart.rb +4 -4
- data/lib/safrano/rack_app.rb +1 -1
- data/lib/safrano/request.rb +3 -2
- data/lib/safrano/service.rb +38 -21
- data/lib/safrano/version.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00bf6cd3a561928000b8bf53bbc71f139b162f92adc229e625f010dc6f1ce3c7
|
4
|
+
data.tar.gz: bf154e1b55e4d32e4640d6c993566a45eae8a2bce44b23dd979a9946d3633dc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b2fad0321b45bae50f3435b688b31f99d320cfa4f81f14867f64659faa0c4e8ece7bc4f1f7ecc2db2c7710c08f7726bb4d758bf5d09d487a54199afea50119a
|
7
|
+
data.tar.gz: b9a70b4a08fb7a2cc31df7e7637f635a42a85433660f5061f6897e1fb463496252c8d611fb1023e5f27202bbc478b58569d2ad42b1c3359db14e34c92f4ec35c
|
data/lib/odata/attribute.rb
CHANGED
@@ -53,7 +53,7 @@ module Safrano
|
|
53
53
|
# methods related to transitions to next state (cf. walker)
|
54
54
|
module Transitions
|
55
55
|
def transition_end(_match_result)
|
56
|
-
|
56
|
+
Transition::RESULT_END
|
57
57
|
end
|
58
58
|
|
59
59
|
def transition_value(_match_result)
|
@@ -64,7 +64,7 @@ module Safrano
|
|
64
64
|
Safrano::TransitionValue].freeze
|
65
65
|
|
66
66
|
def allowed_transitions
|
67
|
-
ALLOWED_TRANSITIONS
|
67
|
+
Transitions::ALLOWED_TRANSITIONS
|
68
68
|
end
|
69
69
|
end
|
70
70
|
include Transitions
|
data/lib/odata/collection.rb
CHANGED
@@ -60,10 +60,13 @@ module Safrano
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def initialize_dataset(dtset = nil)
|
63
|
-
@cx = dtset || @modelk
|
63
|
+
@cx = @cx || dtset || @modelk
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize_uparms
|
64
67
|
@uparms = UrlParameters4Coll.new(@cx, @params)
|
65
68
|
end
|
66
|
-
|
69
|
+
|
67
70
|
def odata_get_apply_params
|
68
71
|
@uparms.apply_to_dataset(@cx).map_result! do |dataset|
|
69
72
|
@cx = dataset
|
@@ -77,17 +80,20 @@ module Safrano
|
|
77
80
|
end
|
78
81
|
end
|
79
82
|
|
80
|
-
D = 'd'
|
81
|
-
DJ_OPEN = '{"d":'
|
82
|
-
DJ_CLOSE = '}'
|
83
|
+
D = 'd'
|
84
|
+
DJ_OPEN = '{"d":'
|
85
|
+
DJ_CLOSE = '}'
|
83
86
|
EMPTYH = {}.freeze
|
84
87
|
|
85
88
|
def to_odata_json(request:)
|
86
89
|
template = @modelk.output_template(expand_list: @uparms.expand.template,
|
87
90
|
select: @uparms.select)
|
91
|
+
# TODO: Error handling if database contains binary BLOB data that cant be
|
92
|
+
# interpreted as UTF-8 then JSON will fail here
|
88
93
|
innerj = request.service.get_coll_odata_h(array: @cx.all,
|
89
94
|
template: template,
|
90
95
|
icount: @inlinecount).to_json
|
96
|
+
|
91
97
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
92
98
|
end
|
93
99
|
|
@@ -128,9 +134,9 @@ module Safrano
|
|
128
134
|
|
129
135
|
# on model class level we return the collection
|
130
136
|
def odata_get(req)
|
131
|
-
@params = req.params
|
132
|
-
initialize_dataset
|
133
|
-
|
137
|
+
@params = @params || req.params
|
138
|
+
initialize_dataset
|
139
|
+
initialize_uparms
|
134
140
|
@uparms.check_all.if_valid { |_ret|
|
135
141
|
odata_get_apply_params.if_valid { |_ret|
|
136
142
|
odata_get_output(req)
|
@@ -167,8 +173,7 @@ module Safrano
|
|
167
173
|
end
|
168
174
|
|
169
175
|
def initialize_dataset(dtset = nil)
|
170
|
-
@cx = dtset || navigated_dataset
|
171
|
-
@uparms = UrlParameters4Coll.new(@cx, @params)
|
176
|
+
@cx = @cx || dtset || navigated_dataset
|
172
177
|
end
|
173
178
|
# redefinitions of the main methods for a navigated collection
|
174
179
|
# (eg. all Books of Author[2] is Author[2].Books.all )
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'odata/error
|
3
|
+
require 'odata/error'
|
4
4
|
|
5
|
-
require_relative 'filter/parse
|
6
|
-
require_relative 'filter/sequel
|
5
|
+
require_relative 'filter/parse'
|
6
|
+
require_relative 'filter/sequel'
|
7
7
|
|
8
8
|
# filter base class and subclass in our OData namespace
|
9
9
|
module Safrano
|
@@ -45,12 +45,12 @@ module Safrano
|
|
45
45
|
# the join-helper is shared by the order-by object and was potentially already
|
46
46
|
# partly built on order-by object creation.
|
47
47
|
def finalize(jh)
|
48
|
-
@filtexpr = @ast.if_valid
|
48
|
+
@filtexpr = @ast.if_valid { |ast| ast.sequel_expr(jh) }
|
49
49
|
end
|
50
50
|
|
51
51
|
def apply_to_dataset(dtcx)
|
52
52
|
# normally finalize is called before, and thus @filtexpr is set
|
53
|
-
@filtexpr.map_result!
|
53
|
+
@filtexpr.map_result! { |f| dtcx.where(f) }
|
54
54
|
end
|
55
55
|
|
56
56
|
# Note: this is really only *parse* error, ie the error encounterd while
|
@@ -2,12 +2,17 @@
|
|
2
2
|
|
3
3
|
require 'rack'
|
4
4
|
require 'fileutils'
|
5
|
-
require_relative './navigation_attribute
|
5
|
+
require_relative './navigation_attribute'
|
6
6
|
|
7
7
|
module Safrano
|
8
8
|
module Media
|
9
9
|
# base class for Media Handler
|
10
10
|
class Handler
|
11
|
+
def check_before_create(data:,
|
12
|
+
entity:,
|
13
|
+
filename:)
|
14
|
+
Contract::OK
|
15
|
+
end
|
11
16
|
end
|
12
17
|
|
13
18
|
# Simple static File/Directory based media store handler
|
@@ -24,9 +29,16 @@ module Safrano
|
|
24
29
|
|
25
30
|
def register
|
26
31
|
@abs_klass_dir = File.absolute_path(@media_dir_name, @root)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_abs_class_dir
|
27
35
|
FileUtils.makedirs @abs_klass_dir unless Dir.exist?(@abs_klass_dir)
|
28
36
|
end
|
29
37
|
|
38
|
+
def finalize
|
39
|
+
create_abs_class_dir
|
40
|
+
end
|
41
|
+
|
30
42
|
# minimal working implementation...
|
31
43
|
# Note: @file_server works relative to @root directory
|
32
44
|
def odata_get(request:, entity:)
|
@@ -47,13 +59,13 @@ module Safrano
|
|
47
59
|
end
|
48
60
|
|
49
61
|
# relative to @root
|
50
|
-
# eg Photo/1/
|
62
|
+
# eg Photo/1/1
|
51
63
|
def filename(entity)
|
52
64
|
Dir.chdir(abs_path(entity)) do
|
53
65
|
# simple design: one file per directory, and the directory
|
54
66
|
# contains the media entity-id --> implicit link between the media
|
55
67
|
# entity
|
56
|
-
File.join(media_path(entity), Dir.glob('*').
|
68
|
+
File.join(media_path(entity), Dir.glob('*').max)
|
57
69
|
end
|
58
70
|
end
|
59
71
|
|
@@ -62,6 +74,11 @@ module Safrano
|
|
62
74
|
File.absolute_path(media_path(entity), @root)
|
63
75
|
end
|
64
76
|
|
77
|
+
# absolute filename
|
78
|
+
def abs_filename(entity)
|
79
|
+
File.absolute_path(filename(entity), @root)
|
80
|
+
end
|
81
|
+
|
65
82
|
# this is relative to abs_klass_dir(entity) eg to /@root/Photo
|
66
83
|
# simplest implementation is media_directory = entity.media_path_id
|
67
84
|
# --> we get a 1 level depth flat directory structure
|
@@ -101,7 +118,7 @@ module Safrano
|
|
101
118
|
def ressource_version(entity)
|
102
119
|
Dir.chdir(@abs_klass_dir) do
|
103
120
|
in_media_directory(entity) do
|
104
|
-
Dir.glob('*').
|
121
|
+
Dir.glob('*').max
|
105
122
|
end
|
106
123
|
end
|
107
124
|
end
|
@@ -135,15 +152,14 @@ module Safrano
|
|
135
152
|
|
136
153
|
# this is relative to abs_klass_dir(entity) eg to /@root/Photo
|
137
154
|
# tree-structure
|
138
|
-
# media_path_ids = 1 --> 1
|
139
|
-
# media_path_ids = 15 --> 1/5
|
140
|
-
# media_path_ids = 555 --> 5/5/5
|
141
|
-
# media_path_ids = 5,5,5 --> 5/00/5/00/5
|
142
|
-
# media_path_ids = 5,00,5 --> 5/00/0/0/00/5
|
143
|
-
# media_path_ids = 5,xyz,5 --> 5/00/x/y/z/00/5
|
155
|
+
# media_path_ids = 1 --> 1/v
|
156
|
+
# media_path_ids = 15 --> 1/5/v
|
157
|
+
# media_path_ids = 555 --> 5/5/5/v
|
158
|
+
# media_path_ids = 5,5,5 --> 5/00/5/00/5/v
|
159
|
+
# media_path_ids = 5,00,5 --> 5/00/0/0/00/5/v
|
160
|
+
# media_path_ids = 5,xyz,5 --> 5/00/x/y/z/00/5/v
|
144
161
|
def media_directory(entity)
|
145
162
|
StaticTree.path_builder(entity.media_path_ids)
|
146
|
-
# entity.media_path_ids.map{|id| id.to_s.chars.join('/')}.join(@sep)
|
147
163
|
end
|
148
164
|
|
149
165
|
def in_media_directory(entity)
|
@@ -201,9 +217,14 @@ module Safrano
|
|
201
217
|
@media_handler = Safrano::Media::Static.new(mediaklass: self)
|
202
218
|
end
|
203
219
|
|
220
|
+
def finalize_media
|
221
|
+
@media_handler.finalize
|
222
|
+
end
|
223
|
+
|
204
224
|
def use(klass, args)
|
205
225
|
args[:mediaklass] = self
|
206
226
|
@media_handler = klass.new(**args)
|
227
|
+
@media_handler.create_abs_class_dir
|
207
228
|
end
|
208
229
|
|
209
230
|
# API method for setting the model field mapped to SLUG on upload
|
@@ -250,22 +271,39 @@ module Safrano
|
|
250
271
|
missing: :skip)
|
251
272
|
end
|
252
273
|
|
253
|
-
#
|
254
|
-
if
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
274
|
+
# call before_create_entity media hook
|
275
|
+
new_entity.before_create_media_entity(data: data, mimetype: mimetype) if new_entity.respond_to? :before_create_media_entity
|
276
|
+
|
277
|
+
media_handler.check_before_create(data: data,
|
278
|
+
entity: new_entity,
|
279
|
+
filename: filename).if_valid { |_ret|
|
280
|
+
# to_one rels are create with FK data set on the parent entity
|
281
|
+
if parent
|
282
|
+
odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
|
283
|
+
else
|
284
|
+
# in-changeset requests get their own transaction
|
285
|
+
new_entity.save(transaction: !req.in_changeset)
|
286
|
+
end
|
287
|
+
|
288
|
+
req.register_content_id_ref(new_entity)
|
289
|
+
new_entity.copy_request_infos(req)
|
290
|
+
|
291
|
+
# call before_create_media hook
|
292
|
+
new_entity.before_create_media if new_entity.respond_to? :before_create_media
|
293
|
+
|
294
|
+
media_handler.save_file(data: data,
|
295
|
+
entity: new_entity,
|
296
|
+
filename: filename)
|
297
|
+
|
298
|
+
# call after_create_media hook
|
299
|
+
new_entity.after_create_media if new_entity.respond_to? :after_create_media
|
260
300
|
|
261
|
-
|
262
|
-
|
301
|
+
# json is default content type so we dont need to specify it here again
|
302
|
+
# Contract.valid([201, EMPTY_HASH, new_entity.to_odata_post_json(service: req.service)])
|
303
|
+
# TODO quirks array mode !
|
304
|
+
Contract.valid([201, EMPTY_HASH, new_entity.to_odata_create_json(request: req)])
|
305
|
+
}.tap_error { |e| return e.odata_get(req) }.result
|
263
306
|
|
264
|
-
media_handler.save_file(data: data,
|
265
|
-
entity: new_entity,
|
266
|
-
filename: filename)
|
267
|
-
# json is default content type so we dont need to specify it here again
|
268
|
-
[201, EMPTY_HASH, new_entity.to_odata_post_json(service: req.service)]
|
269
307
|
else # TODO: other formats
|
270
308
|
415
|
271
309
|
end
|
data/lib/odata/complex_type.rb
CHANGED
@@ -2,76 +2,184 @@
|
|
2
2
|
|
3
3
|
module Safrano
|
4
4
|
module FunctionImport
|
5
|
+
EMPTY_HASH = {}.freeze
|
5
6
|
class ResultDefinition
|
6
|
-
D = 'd'
|
7
|
-
DJ_OPEN = '{"d":'
|
8
|
-
DJ_CLOSE = '}'
|
9
|
-
METAK = '__metadata'
|
10
|
-
TYPEK = 'type'
|
11
|
-
VALUEK = 'value'
|
12
|
-
RESULTSK = 'results'
|
13
|
-
COLLECTION = 'Collection'
|
14
|
-
|
15
|
-
def
|
16
|
-
|
7
|
+
D = 'd'
|
8
|
+
DJ_OPEN = '{"d":'
|
9
|
+
DJ_CLOSE = '}'
|
10
|
+
METAK = '__metadata'
|
11
|
+
TYPEK = 'type'
|
12
|
+
VALUEK = 'value'
|
13
|
+
RESULTSK = 'results'
|
14
|
+
COLLECTION = 'Collection'
|
15
|
+
|
16
|
+
def allowed_transitions
|
17
|
+
[Safrano::TransitionEnd]
|
17
18
|
end
|
18
|
-
|
19
|
-
def
|
20
|
-
|
19
|
+
|
20
|
+
def transition_end(_match_result)
|
21
|
+
Safrano::Transition::RESULT_END
|
21
22
|
end
|
22
|
-
|
23
|
-
|
23
|
+
|
24
|
+
# we will have this on class and instance level for making things simpler first
|
25
|
+
def self.klassmod
|
26
|
+
@klassmod
|
27
|
+
end
|
28
|
+
|
29
|
+
# return a subclass of ResultAsComplexType
|
30
|
+
def self.asComplexType(klassmod)
|
31
|
+
Class.new(ResultAsComplexType) do
|
32
|
+
@klassmod = klassmod
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# return a subclass of ResultAsComplexType
|
37
|
+
def self.asComplexTypeColl(klassmod)
|
38
|
+
Class.new(ResultAsComplexTypeColl) do
|
39
|
+
@klassmod = klassmod
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.asPrimitiveType(klassmod)
|
44
|
+
Class.new(ResultAsPrimitiveType) do
|
45
|
+
@klassmod = klassmod
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.asPrimitiveTypeColl(klassmod)
|
50
|
+
Class.new(ResultAsPrimitiveTypeColl) do
|
51
|
+
@klassmod = klassmod
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.asEntity(klassmod)
|
56
|
+
Class.new(ResultAsEntity) do
|
57
|
+
@klassmod = klassmod
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.asEntityColl(klassmod)
|
62
|
+
Class.new(ResultAsEntityColl) do
|
63
|
+
@klassmod = klassmod
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(value)
|
68
|
+
@value = value
|
69
|
+
end
|
70
|
+
|
71
|
+
def odata_get(req)
|
72
|
+
[200, EMPTY_HASH, [to_odata_json(req)]]
|
73
|
+
end
|
74
|
+
def self.type_metadata
|
24
75
|
@klassmod.type_name
|
25
76
|
end
|
77
|
+
def type_metadata
|
78
|
+
self.class.type_metadata
|
79
|
+
end
|
80
|
+
|
81
|
+
# needed for ComplexType result
|
82
|
+
def to_odata_json(_req)
|
83
|
+
"#{DJ_OPEN}#{@value.odata_h.to_json}#{DJ_CLOSE}"
|
84
|
+
end
|
85
|
+
|
86
|
+
# wrapper
|
87
|
+
# for OData Entity and Collections, return them directly
|
88
|
+
# for others, ie ComplexType, Prims etc, return the ResultDefinition-subclass wrapped result
|
89
|
+
def self.do_execute_func_result(result, _req, apply_query_params=false)
|
90
|
+
self.new(result)
|
91
|
+
end
|
92
|
+
|
26
93
|
end
|
94
|
+
|
27
95
|
class ResultAsComplexType < ResultDefinition
|
96
|
+
def self.type_metadata
|
97
|
+
@klassmod.type_name
|
98
|
+
end
|
28
99
|
end
|
100
|
+
|
29
101
|
class ResultAsComplexTypeColl < ResultDefinition
|
30
|
-
def type_metadata
|
102
|
+
def self.type_metadata
|
31
103
|
"Collection(#{@klassmod.type_name})"
|
32
104
|
end
|
33
105
|
|
34
|
-
def to_odata_json(
|
35
|
-
"#{DJ_OPEN}#{{ RESULTSK => coll.map { |c| c.odata_h } }.to_json}#{DJ_CLOSE}"
|
106
|
+
def to_odata_json(req)
|
107
|
+
# "#{DJ_OPEN}#{{ RESULTSK => coll.map { |c| c.odata_h } }.to_json}#{DJ_CLOSE}"
|
108
|
+
template = self.class.klassmod.output_template
|
109
|
+
# TODO: Error handling if database contains binary BLOB data that cant be
|
110
|
+
# interpreted as UTF-8 then JSON will fail here
|
111
|
+
|
112
|
+
innerh = req.service.get_coll_odata_h(array: @value,
|
113
|
+
template: template)
|
114
|
+
|
115
|
+
innerj = innerh.to_json
|
116
|
+
|
117
|
+
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
36
118
|
end
|
37
119
|
end
|
120
|
+
|
38
121
|
class ResultAsEntity < ResultDefinition
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
to_odata_json(request: req)
|
43
|
-
end
|
122
|
+
|
123
|
+
def self.type_metadata
|
124
|
+
@klassmod.type_name
|
44
125
|
end
|
126
|
+
|
127
|
+
|
128
|
+
# wrapper
|
129
|
+
# for OData Entity return them directly
|
130
|
+
def self.do_execute_func_result(result, _req, apply_query_params=false)
|
131
|
+
# note: Sequel entities instances seem to be thread safe, so we can
|
132
|
+
# safely add request-dependant data (eg. req.params) there
|
133
|
+
apply_query_params ? result : result.inactive_query_params
|
134
|
+
end
|
135
|
+
|
45
136
|
end
|
137
|
+
|
46
138
|
class ResultAsEntityColl < ResultDefinition
|
47
|
-
|
139
|
+
|
140
|
+
def self.type_metadata
|
48
141
|
"Collection(#{@klassmod.type_name})"
|
49
142
|
end
|
50
|
-
|
51
|
-
|
143
|
+
|
144
|
+
# wrapper
|
145
|
+
# for OData Entity Collection return them directly
|
146
|
+
def self.do_execute_func_result(result, req, apply_query_params=false)
|
52
147
|
coll = Safrano::OData::Collection.new(@klassmod)
|
148
|
+
# instance_exec has other instance variables; @values would be nil in the block below
|
149
|
+
# need to pass a local copy
|
150
|
+
dtset = result
|
53
151
|
coll.instance_exec do
|
54
|
-
|
55
|
-
|
152
|
+
|
153
|
+
@params = apply_query_params ? req.params : EMPTY_HASH
|
154
|
+
initialize_dataset(dtset)
|
155
|
+
initialize_uparms
|
56
156
|
end
|
57
|
-
coll
|
157
|
+
coll
|
58
158
|
end
|
159
|
+
|
59
160
|
end
|
161
|
+
|
60
162
|
class ResultAsPrimitiveType < ResultDefinition
|
61
|
-
def
|
163
|
+
def self.type_metadata
|
164
|
+
@klassmod.type_name
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_odata_json(_req)
|
62
168
|
{ D => { METAK => { TYPEK => type_metadata },
|
63
|
-
VALUEK =>
|
169
|
+
VALUEK => self.class.klassmod.odata_value(@value) } }.to_json
|
64
170
|
end
|
65
171
|
end
|
172
|
+
|
66
173
|
class ResultAsPrimitiveTypeColl < ResultDefinition
|
67
|
-
def type_metadata
|
174
|
+
def self.type_metadata
|
68
175
|
"Collection(#{@klassmod.type_name})"
|
69
176
|
end
|
70
177
|
|
71
|
-
def to_odata_json(
|
72
|
-
{ D => { METAK => { TYPEK => type_metadata },
|
73
|
-
RESULTSK =>
|
178
|
+
def to_odata_json(_req)
|
179
|
+
{ D => { METAK => { TYPEK => self.class.type_metadata },
|
180
|
+
RESULTSK => self.class.klassmod.odata_collection(@value) } }.to_json
|
74
181
|
end
|
182
|
+
|
75
183
|
end
|
76
184
|
end
|
77
185
|
|
@@ -80,7 +188,8 @@ module Safrano
|
|
80
188
|
# with added OData functionality
|
81
189
|
class ComplexType
|
82
190
|
attr_reader :values
|
83
|
-
|
191
|
+
EMPTYH = {}.freeze
|
192
|
+
|
84
193
|
@namespace = nil
|
85
194
|
def self.namespace
|
86
195
|
@namespace
|
@@ -89,19 +198,52 @@ module Safrano
|
|
89
198
|
def self.props
|
90
199
|
@props
|
91
200
|
end
|
92
|
-
|
201
|
+
|
202
|
+
def type_name
|
203
|
+
self.class.type_name
|
204
|
+
end
|
205
|
+
|
206
|
+
def metadata_h
|
207
|
+
{ type: type_name }
|
208
|
+
end
|
209
|
+
|
210
|
+
def casted_values
|
211
|
+
# MVP... TODO: handle time mappings like in Entity models
|
212
|
+
values
|
213
|
+
end
|
214
|
+
|
215
|
+
# needed for nested json output
|
216
|
+
# this is a simpler version of model_ext#output_template
|
217
|
+
def self.default_template
|
218
|
+
template = {}
|
219
|
+
expand_e = {}
|
220
|
+
|
221
|
+
template[:all_values] = EMPTYH
|
222
|
+
@props.each { |prop, kl|
|
223
|
+
if kl.respond_to? :default_template
|
224
|
+
expand_e[prop] = kl.default_template
|
225
|
+
end
|
226
|
+
}
|
227
|
+
template[:expand_e] = expand_e
|
228
|
+
template
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.output_template
|
232
|
+
default_template
|
233
|
+
end
|
93
234
|
def self.type_name
|
94
|
-
"#{@namespace}.#{self.to_s}"
|
235
|
+
@namespace ? "#{@namespace}.#{self.to_s}" : self.to_s
|
95
236
|
end
|
96
237
|
|
97
238
|
def initialize
|
98
239
|
@values = {}
|
99
240
|
end
|
100
|
-
METAK = '__metadata'
|
101
|
-
TYPEK = 'type'
|
241
|
+
METAK = '__metadata'
|
242
|
+
TYPEK = 'type'
|
102
243
|
|
103
244
|
def odata_h
|
104
245
|
ret = { METAK => { TYPEK => self.class.type_name } }
|
246
|
+
|
105
247
|
@values.each { |k, v|
|
106
248
|
ret[k] = if v.respond_to? :odata_h
|
107
249
|
v.odata_h
|
@@ -113,11 +255,11 @@ module Safrano
|
|
113
255
|
end
|
114
256
|
|
115
257
|
def self.return_as_collection_descriptor
|
116
|
-
FunctionImport::
|
258
|
+
FunctionImport::ResultDefinition.asComplexTypeColl(self)
|
117
259
|
end
|
118
260
|
|
119
261
|
def self.return_as_instance_descriptor
|
120
|
-
FunctionImport::
|
262
|
+
FunctionImport::ResultDefinition.asComplexType(self)
|
121
263
|
end
|
122
264
|
|
123
265
|
# add metadata xml to the passed REXML schema object
|