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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88f62ce611a5030382ea19c2e7c22b47f454b85c51261e5bad060d82e4680dff
4
- data.tar.gz: '088e66ed4a107a3bef8dd6db68c8ca626f7d3acef6ed2e58eb53d871e6349613'
3
+ metadata.gz: 00bf6cd3a561928000b8bf53bbc71f139b162f92adc229e625f010dc6f1ce3c7
4
+ data.tar.gz: bf154e1b55e4d32e4640d6c993566a45eae8a2bce44b23dd979a9946d3633dc7
5
5
  SHA512:
6
- metadata.gz: 2d719e317178a4baebc2e40cc50b95559921fe2936a6a03437f7f92714d173ef6160b10620ce5476ea54a7433b3dd1366b49ec37a48b121b8f808ad030e2c364
7
- data.tar.gz: 535ee0f1d4fed30a0c94918c31149a2e95fce90051548a2ccca6bffb71930c0767543255695496c3623249070b5efb1b6a203a1dae6f868c1e4da9a126a85346
6
+ metadata.gz: 9b2fad0321b45bae50f3435b688b31f99d320cfa4f81f14867f64659faa0c4e8ece7bc4f1f7ecc2db2c7710c08f7726bb4d758bf5d09d487a54199afea50119a
7
+ data.tar.gz: b9a70b4a08fb7a2cc31df7e7637f635a42a85433660f5061f6897e1fb463496252c8d611fb1023e5f27202bbc478b58569d2ad42b1c3359db14e34c92f4ec35c
@@ -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
- [nil, :end]
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
@@ -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'.freeze
81
- DJ_OPEN = '{"d":'.freeze
82
- DJ_CLOSE = '}'.freeze
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.rb'
3
+ require 'odata/error'
4
4
 
5
- require_relative 'filter/parse.rb'
6
- require_relative 'filter/sequel.rb'
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 do |ast| ast.sequel_expr(jh) end
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! do |f| dtcx.where(f) end
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.rb'
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/pommes-topaz.jpg
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('*').sort.first)
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('*').sort.last
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
- # to_one rels are create with FK data set on the parent entity
254
- if parent
255
- odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
256
- else
257
- # in-changeset requests get their own transaction
258
- new_entity.save(transaction: !req.in_changeset)
259
- end
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
- req.register_content_id_ref(new_entity)
262
- new_entity.copy_request_infos(req)
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
@@ -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'.freeze
7
- DJ_OPEN = '{"d":'.freeze
8
- DJ_CLOSE = '}'.freeze
9
- METAK = '__metadata'.freeze
10
- TYPEK = 'type'.freeze
11
- VALUEK = 'value'.freeze
12
- RESULTSK = 'results'.freeze
13
- COLLECTION = 'Collection'.freeze
14
-
15
- def initialize(klassmod)
16
- @klassmod = klassmod
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 to_odata_json(result, _req)
20
- "#{DJ_OPEN}#{result.odata_h.to_json}#{DJ_CLOSE}"
19
+
20
+ def transition_end(_match_result)
21
+ Safrano::Transition::RESULT_END
21
22
  end
22
-
23
- def type_metadata
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(coll, _req)
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
- def to_odata_json(result_entity, req)
40
- result_entity.instance_exec do
41
- copy_request_infos(req)
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
- def type_metadata
139
+
140
+ def self.type_metadata
48
141
  "Collection(#{@klassmod.type_name})"
49
142
  end
50
-
51
- def to_odata_json(result_dataset, req)
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
- @params = req.params
55
- initialize_dataset(result_dataset)
152
+
153
+ @params = apply_query_params ? req.params : EMPTY_HASH
154
+ initialize_dataset(dtset)
155
+ initialize_uparms
56
156
  end
57
- coll.to_odata_json(request: req)
157
+ coll
58
158
  end
159
+
59
160
  end
161
+
60
162
  class ResultAsPrimitiveType < ResultDefinition
61
- def to_odata_json(result, _req)
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 => @klassmod.odata_value(result) } }.to_json
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(result, _req)
72
- { D => { METAK => { TYPEK => type_metadata },
73
- RESULTSK => @klassmod.odata_collection(result) } }.to_json
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'.freeze
101
- TYPEK = 'type'.freeze
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::ResultAsComplexTypeColl.new(self)
258
+ FunctionImport::ResultDefinition.asComplexTypeColl(self)
117
259
  end
118
260
 
119
261
  def self.return_as_instance_descriptor
120
- FunctionImport::ResultAsComplexType.new(self)
262
+ FunctionImport::ResultDefinition.asComplexType(self)
121
263
  end
122
264
 
123
265
  # add metadata xml to the passed REXML schema object