safrano 0.5.3 → 0.5.4

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: 64699787371f5e9f696461faac80382cf2d53fe78d387742d8650e5a4b2c54df
4
- data.tar.gz: 530e7619b4b27a1a50a63f832d4f3a8894850c6cea36e638769cdffbc6b7d942
3
+ metadata.gz: 89b3584ee45b115a0a463faea56586c06271c36ea080c4f73b64fb4a4a641802
4
+ data.tar.gz: 977386e3bff89f18c6b596f1f4244623e85b2960ab9cb9eed6ebf7c9c1c0498f
5
5
  SHA512:
6
- metadata.gz: e5d118f15a3a3ba78f5385867c82370e36612585b7f83329343babb2e3a8a7220d1059ad766fbe7c9e1f7f74a57945c086e60624d0301259c1427f0053b59052
7
- data.tar.gz: 52b283be3e8902226f14864a74eae30bc62c65908be748ea37c5a150f574f3130c8957f26251e1958e298eba221eeada9f0155fcc73ed2591034e97466f1e629
6
+ metadata.gz: e45fb7cd3af3aa3b0d918229f84760b9c779a01a55d5f46f593e93dd2f18dbefe5e3713141cf3d72523b3e2069b426130760531c991549993f861c26c9b8a2f0
7
+ data.tar.gz: 943e0cb99f039eb41c69204dd348347965b188a0e334d13c075ffe06188b01a44e8bc757b86509de24ca843b9253f131ed3c30bbbc4eaaa2d176cc6b5e71249d
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'bigdecimal/util'
4
+
5
+ module Safrano
6
+ module CoreIncl
7
+ module Numeric
8
+ module Convert
9
+ def toDecimalString
10
+ BigDecimal(to_s).to_s('F')
11
+ end
12
+
13
+ def toDecimalPrecisionString(precision, scale = nil)
14
+ p = Integer(precision)
15
+
16
+ scale.nil? ? BigDecimal(self, p).to_s('F') : sprintf("%#{p + 2}.#{scale}f", self).strip
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'Numeric/convert'
2
+
3
+ Numeric.include Safrano::CoreIncl::Numeric::Convert
@@ -60,13 +60,13 @@ module Safrano
60
60
  end
61
61
 
62
62
  def initialize_dataset(dtset = nil)
63
- @cx = @cx || dtset || @modelk
63
+ @cx = @cx || dtset || @modelk
64
64
  end
65
-
65
+
66
66
  def initialize_uparms
67
67
  @uparms = UrlParameters4Coll.new(@cx, @params)
68
68
  end
69
-
69
+
70
70
  def odata_get_apply_params
71
71
  @uparms.apply_to_dataset(@cx).map_result! do |dataset|
72
72
  @cx = dataset
@@ -135,7 +135,7 @@ module Safrano
135
135
  # on model class level we return the collection
136
136
  def odata_get(req)
137
137
  @params = @params || req.params
138
- initialize_dataset
138
+ initialize_dataset
139
139
  initialize_uparms
140
140
  @uparms.check_all.if_valid { |_ret|
141
141
  odata_get_apply_params.if_valid { |_ret|
@@ -12,153 +12,148 @@ module Safrano
12
12
  VALUEK = 'value'
13
13
  RESULTSK = 'results'
14
14
  COLLECTION = 'Collection'
15
-
15
+
16
16
  def allowed_transitions
17
17
  [Safrano::TransitionEnd]
18
18
  end
19
-
19
+
20
20
  def transition_end(_match_result)
21
21
  Safrano::Transition::RESULT_END
22
22
  end
23
-
23
+
24
24
  # we will have this on class and instance level for making things simpler first
25
25
  def self.klassmod
26
26
  @klassmod
27
27
  end
28
-
28
+
29
29
  # return a subclass of ResultAsComplexType
30
30
  def self.asComplexType(klassmod)
31
31
  Class.new(ResultAsComplexType) do
32
32
  @klassmod = klassmod
33
33
  end
34
34
  end
35
-
35
+
36
36
  # return a subclass of ResultAsComplexType
37
37
  def self.asComplexTypeColl(klassmod)
38
38
  Class.new(ResultAsComplexTypeColl) do
39
39
  @klassmod = klassmod
40
40
  end
41
41
  end
42
-
42
+
43
43
  def self.asPrimitiveType(klassmod)
44
44
  Class.new(ResultAsPrimitiveType) do
45
45
  @klassmod = klassmod
46
46
  end
47
47
  end
48
-
48
+
49
49
  def self.asPrimitiveTypeColl(klassmod)
50
50
  Class.new(ResultAsPrimitiveTypeColl) do
51
51
  @klassmod = klassmod
52
52
  end
53
- end
54
-
53
+ end
54
+
55
55
  def self.asEntity(klassmod)
56
56
  Class.new(ResultAsEntity) do
57
57
  @klassmod = klassmod
58
58
  end
59
59
  end
60
-
60
+
61
61
  def self.asEntityColl(klassmod)
62
62
  Class.new(ResultAsEntityColl) do
63
63
  @klassmod = klassmod
64
64
  end
65
65
  end
66
-
66
+
67
67
  def initialize(value)
68
68
  @value = value
69
69
  end
70
-
70
+
71
71
  def odata_get(req)
72
72
  [200, EMPTY_HASH, [to_odata_json(req)]]
73
73
  end
74
+
74
75
  def self.type_metadata
75
76
  @klassmod.type_name
76
77
  end
78
+
77
79
  def type_metadata
78
80
  self.class.type_metadata
79
81
  end
80
-
82
+
81
83
  # needed for ComplexType result
82
84
  def to_odata_json(_req)
83
85
  "#{DJ_OPEN}#{@value.odata_h.to_json}#{DJ_CLOSE}"
84
- end
85
-
86
- # wrapper
86
+ end
87
+
88
+ # wrapper
87
89
  # for OData Entity and Collections, return them directly
88
90
  # 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)
91
+ def self.do_execute_func_result(result, _req, apply_query_params = false)
90
92
  self.new(result)
91
93
  end
92
-
93
94
  end
94
-
95
+
95
96
  class ResultAsComplexType < ResultDefinition
96
97
  def self.type_metadata
97
98
  @klassmod.type_name
98
99
  end
99
100
  end
100
-
101
+
101
102
  class ResultAsComplexTypeColl < ResultDefinition
102
103
  def self.type_metadata
103
104
  "Collection(#{@klassmod.type_name})"
104
105
  end
105
106
 
106
107
  def to_odata_json(req)
107
- # "#{DJ_OPEN}#{{ RESULTSK => coll.map { |c| c.odata_h } }.to_json}#{DJ_CLOSE}"
108
+ # "#{DJ_OPEN}#{{ RESULTSK => coll.map { |c| c.odata_h } }.to_json}#{DJ_CLOSE}"
108
109
  template = self.class.klassmod.output_template
109
110
  # TODO: Error handling if database contains binary BLOB data that cant be
110
111
  # interpreted as UTF-8 then JSON will fail here
111
112
 
112
113
  innerh = req.service.get_coll_odata_h(array: @value,
113
114
  template: template)
114
-
115
+
115
116
  innerj = innerh.to_json
116
117
 
117
118
  "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
118
119
  end
119
120
  end
120
-
121
+
121
122
  class ResultAsEntity < ResultDefinition
122
-
123
123
  def self.type_metadata
124
124
  @klassmod.type_name
125
125
  end
126
126
 
127
-
128
- # wrapper
127
+ # wrapper
129
128
  # 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
129
+ def self.do_execute_func_result(result, _req, apply_query_params = false)
130
+ # note: Sequel entities instances seem to be thread safe, so we can
132
131
  # safely add request-dependant data (eg. req.params) there
133
132
  apply_query_params ? result : result.inactive_query_params
134
133
  end
135
-
136
134
  end
137
-
135
+
138
136
  class ResultAsEntityColl < ResultDefinition
139
-
140
137
  def self.type_metadata
141
138
  "Collection(#{@klassmod.type_name})"
142
139
  end
143
-
144
- # wrapper
140
+
141
+ # wrapper
145
142
  # for OData Entity Collection return them directly
146
- def self.do_execute_func_result(result, req, apply_query_params=false)
143
+ def self.do_execute_func_result(result, req, apply_query_params = false)
147
144
  coll = Safrano::OData::Collection.new(@klassmod)
148
145
  # instance_exec has other instance variables; @values would be nil in the block below
149
146
  # need to pass a local copy
150
147
  dtset = result
151
148
  coll.instance_exec do
152
-
153
- @params = apply_query_params ? req.params : EMPTY_HASH
149
+ @params = apply_query_params ? req.params : EMPTY_HASH
154
150
  initialize_dataset(dtset)
155
151
  initialize_uparms
156
152
  end
157
153
  coll
158
154
  end
159
-
160
155
  end
161
-
156
+
162
157
  class ResultAsPrimitiveType < ResultDefinition
163
158
  def self.type_metadata
164
159
  @klassmod.type_name
@@ -169,7 +164,7 @@ module Safrano
169
164
  VALUEK => self.class.klassmod.odata_value(@value) } }.to_json
170
165
  end
171
166
  end
172
-
167
+
173
168
  class ResultAsPrimitiveTypeColl < ResultDefinition
174
169
  def self.type_metadata
175
170
  "Collection(#{@klassmod.type_name})"
@@ -179,7 +174,6 @@ module Safrano
179
174
  { D => { METAK => { TYPEK => self.class.type_metadata },
180
175
  RESULTSK => self.class.klassmod.odata_collection(@value) } }.to_json
181
176
  end
182
-
183
177
  end
184
178
  end
185
179
 
@@ -188,8 +182,9 @@ module Safrano
188
182
  # with added OData functionality
189
183
  class ComplexType
190
184
  attr_reader :values
185
+
191
186
  EMPTYH = {}.freeze
192
-
187
+
193
188
  @namespace = nil
194
189
  def self.namespace
195
190
  @namespace
@@ -198,28 +193,28 @@ module Safrano
198
193
  def self.props
199
194
  @props
200
195
  end
201
-
196
+
202
197
  def type_name
203
198
  self.class.type_name
204
199
  end
205
-
200
+
206
201
  def metadata_h
207
202
  { type: type_name }
208
203
  end
209
-
204
+
210
205
  def casted_values
211
206
  # MVP... TODO: handle time mappings like in Entity models
212
207
  values
213
208
  end
214
-
209
+
215
210
  # needed for nested json output
216
211
  # this is a simpler version of model_ext#output_template
217
212
  def self.default_template
218
213
  template = {}
219
214
  expand_e = {}
220
-
215
+
221
216
  template[:all_values] = EMPTYH
222
- @props.each { |prop, kl|
217
+ @props.each { |prop, kl|
223
218
  if kl.respond_to? :default_template
224
219
  expand_e[prop] = kl.default_template
225
220
  end
@@ -227,10 +222,11 @@ module Safrano
227
222
  template[:expand_e] = expand_e
228
223
  template
229
224
  end
230
-
225
+
231
226
  def self.output_template
232
227
  default_template
233
228
  end
229
+
234
230
  def self.type_name
235
231
  @namespace ? "#{@namespace}.#{self.to_s}" : self.to_s
236
232
  end
@@ -15,35 +15,107 @@ module Safrano
15
15
  # Classes specifying generic types that Sequel will convert to
16
16
  # database-specific types.
17
17
  DB_TYPE_STRING_RGX = /\ACHAR\s*\(\d+\)\z/.freeze
18
-
18
+ DB_TYPE_NUMDEC_RGX = /\A(NUMERIC|DECIMAL)\s*(\(\s*((\d+)\s*(,\s*(\d+))?)\s*\))?\s*\z/i.freeze
19
+ # thank you rubular
20
+ # Test String: DECIMAL (55,2 )
21
+ # Match groups
22
+ # 1 DECIMAL
23
+ # 2 (55,2 )
24
+ # 3 55,2
25
+ # 4 55
26
+ # 5 ,2
27
+ # 6 2
28
+
29
+ DB_TYPE_FLOATP_RGX = /\A\s*(FLOAT)\s*(\(\s*(\d+)\s*\))?\s*\z/i.freeze
30
+
31
+ # Note: "char" (quoted!) is postgresql's byte type
32
+ DB_TYPE_INTLIKE_RGX = /\A\s*(smallserial|smallint|integer|int2|int4|int8|int|mediumint|bigint|serial|bigserial|tinyint)\s*/i.freeze
19
33
  # used in $metadata
20
34
  # cf. Sequel Database column_schema_default_to_ruby_value
21
35
  # schema_column_type
22
36
  # https://www.odata.org/documentation/odata-version-2-0/overview/
23
- def self.default_edm_type(ruby_type:, db_type: )
24
- case ruby_type
25
- when :integer
26
- 'Edm.Int32'
27
- when :string
28
- 'Edm.String'
29
- when :date
30
- 'Edm.DateTime'
31
- when :datetime
32
- 'Edm.DateTime'
33
- when :time
34
- 'Edm.Time'
35
- when :boolean
36
- 'Edm.Boolean'
37
- when :float
38
- 'Edm.Double'
39
- when :decimal
40
- 'Edm.Decimal'
41
- when :blob
42
- 'Edm.Binary'
43
- else # try with db_type:
44
- if ( db_type =~ /\ANUMERIC/ )
45
- 'Edm.Decimal'
46
- end
37
+
38
+ # type mappings are hard, especially between "Standards" like SQL and OData V2 (might be a bit better in V4 ?)
39
+ # this is all best effort/try to make it work logic
40
+ def self.add_edm_types(props)
41
+ # try num/dec with db_type:
42
+ props[:edm_type] = if (md = DB_TYPE_NUMDEC_RGX.match(props[:db_type]))
43
+ prec = md[4]
44
+ scale = md[6]
45
+ if (scale && prec)
46
+ if (scale == '0') # dont force default scale to 0 like SQL standard
47
+ props[:edm_precision] = prec
48
+ "Edm.Decimal(#{prec})"
49
+ else
50
+ # we have precision and scale
51
+ props[:edm_scale] = scale
52
+ props[:edm_precision] = prec
53
+ "Edm.Decimal(#{prec},#{scale})"
54
+ end
55
+ elsif prec
56
+ # we have precision only
57
+ props[:edm_precision] = prec
58
+ "Edm.Decimal(#{prec})"
59
+ else
60
+ 'Edm.Decimal'
61
+ end
62
+ end
63
+ return if props[:edm_type]
64
+
65
+ # try float(prec) with db_type:
66
+ props[:edm_type] = if (md = DB_TYPE_FLOATP_RGX.match(props[:db_type]))
67
+ # FLOAT( 22) match groups
68
+ # 1 FLOAT
69
+ # 2 (22 )
70
+ # 3 22
71
+
72
+ if (prec = md[3])
73
+ # we have precision only
74
+ props[:edm_precision] = prec
75
+ 'Edm.Double'
76
+ end
77
+ end
78
+ return if props[:edm_type]
79
+
80
+ # try int-like with db_type:
81
+ # smallint|int|integer|bigint|serial|bigserial
82
+ props[:edm_type] = if (md = DB_TYPE_INTLIKE_RGX.match(props[:db_type]))
83
+
84
+ if (itype = md[1])
85
+ case itype.downcase
86
+ when 'smallint', 'int2', 'smallserial'
87
+ 'Edm.Int16'
88
+ when 'int', 'integer', 'serial', 'mediumint', 'int4'
89
+ 'Edm.Int32'
90
+ when 'bigint', 'bigserial', 'int8'
91
+ 'Edm.Int64'
92
+ when 'tinyint'
93
+ 'Edm.Byte'
94
+ end
95
+ end
96
+ end
97
+ return if props[:edm_type]
98
+
99
+ props[:edm_type] = case props[:type]
100
+ when :integer
101
+ 'Edm.Int32'
102
+ when :string
103
+ 'Edm.String'
104
+ when :date
105
+ 'Edm.DateTime'
106
+ when :datetime
107
+ 'Edm.DateTime'
108
+ when :time
109
+ 'Edm.Time'
110
+ when :boolean
111
+ 'Edm.Boolean'
112
+ when :float
113
+ 'Edm.Double'
114
+ when :decimal
115
+ 'Edm.Decimal'
116
+ when :blob
117
+ 'Edm.Binary'
118
+ else
47
119
  end
48
120
  end
49
121
 
data/lib/odata/entity.rb CHANGED
@@ -151,11 +151,12 @@ module Safrano
151
151
  @uparms.check_all.tap_valid { return odata_get_output(req) }
152
152
  .tap_error { |e| return e.odata_get(req) }
153
153
  end
154
+
154
155
  def inactive_query_params
155
156
  @inactive_query_params = true
156
157
  self # chaining
157
158
  end
158
-
159
+
159
160
  DELETE_REL_AND_ENTY = lambda do |entity, assoc, parent|
160
161
  Safrano.remove_nav_relation(assoc, parent)
161
162
  entity.destroy(transaction: false)
@@ -310,40 +311,6 @@ module Safrano
310
311
  end
311
312
  end
312
313
 
313
- module MappingBeforeOutput
314
- # needed for proper datetime or Decimal output
315
- def casted_values(cols = nil)
316
- vals = case cols
317
- when nil
318
- # we need to dup the model values as we need to change it before passing to_json,
319
- # but we dont want to interfere with Sequel's owned data
320
- # (eg because then in worst case it could happen that we write back changed values to DB)
321
- values_for_odata.dup
322
- else
323
- selected_values_for_odata(cols)
324
- end
325
- # TODO better design (perf/ do more during startup and less during request runtime )
326
- # TODO replace the quick and dirty BigDecimal hack with something better
327
- self.class.decimal_cols.each { |dc| vals[dc] = BigDecimal(vals[dc].to_s).to_s('F') if vals.key?(dc) }
328
-
329
- self.class.time_cols.each { |tc| vals[tc] = vals[tc]&.iso8601 if vals.key?(tc) }
330
- vals
331
- end
332
- end
333
- module NoMappingBeforeOutput
334
- # current model does not have eg. Time fields--> no special mapping, just to_json is fine
335
- # --> we can use directly the model.values (values_for_odata) withoud dup'ing it as we dont
336
- # need to change it, just output as is
337
- def casted_values(cols = nil)
338
- case cols
339
- when nil
340
- values_for_odata
341
- else
342
- selected_values_for_odata(cols)
343
- end
344
- end
345
- end
346
-
347
314
  module MediaEntity
348
315
  # media entity metadata for json h
349
316
  def metadata_h
@@ -42,13 +42,13 @@ module Safrano
42
42
  end
43
43
  self
44
44
  end
45
-
45
+
46
46
  def auto_query_parameters
47
47
  @auto_query_params = true
48
- self # chaining
48
+ self # chaining
49
49
  end
50
- alias auto_query_params auto_query_parameters
51
-
50
+ alias auto_query_params auto_query_parameters
51
+
52
52
  def return(klassmod, &proc)
53
53
  raise('Please provide a code block') unless block_given?
54
54
 
@@ -71,7 +71,7 @@ module Safrano
71
71
  else
72
72
  # if it's neither a ComplexType nor a Modle-Entity
73
73
  # --> assume it is a Primitive
74
- # ResultAsPrimitiveTypeColl.new(klassmod)
74
+ # ResultAsPrimitiveTypeColl.new(klassmod)
75
75
  ResultDefinition.asPrimitiveTypeColl(klassmod)
76
76
  end
77
77
  @proc = proc
@@ -142,7 +142,7 @@ module Safrano
142
142
  end if @input
143
143
  funky
144
144
  end
145
-
145
+
146
146
  def with_transition_validated(req)
147
147
  # initialize_params
148
148
  @params = req.params
@@ -150,15 +150,14 @@ module Safrano
150
150
 
151
151
  [nil, :error, @error] if @error
152
152
  end
153
-
154
-
153
+
155
154
  def do_execute_func(req)
156
155
  with_transition_validated(req) do
157
156
  result = @proc.call(**@funcparams)
158
157
  [@returning.do_execute_func_result(result, req, @auto_query_params), :run]
159
158
  end
160
159
  end
161
-
160
+
162
161
  def transition_execute_func(_match_result)
163
162
  [self, :run_with_execute_func]
164
163
  end
@@ -5,6 +5,7 @@
5
5
  # Thus Below we have called that "EntityClass". It's meant as "Collection"
6
6
 
7
7
  require 'json'
8
+ require 'base64'
8
9
  require 'rexml/document'
9
10
  require 'bigdecimal'
10
11
  require_relative '../safrano/core'
@@ -32,8 +33,7 @@ module Safrano
32
33
  attr_reader :default_template
33
34
  attr_reader :uri
34
35
  attr_reader :odata_upk_parts
35
- attr_reader :time_cols
36
- attr_reader :decimal_cols
36
+ attr_reader :casted_cols
37
37
  attr_reader :namespace
38
38
 
39
39
  # initialising block of code to be executed at end of
@@ -75,7 +75,6 @@ module Safrano
75
75
  @uparms = nil
76
76
  @params = nil
77
77
  @cx = nil
78
- # @@time_cols = nil
79
78
  end
80
79
 
81
80
  def build_uri(uribase)
@@ -96,10 +95,10 @@ module Safrano
96
95
 
97
96
  # Factory json-> Model Object instance
98
97
  def new_from_hson_h(hash)
99
- #enty = new
100
- #enty.set_fields(hash, data_fields, missing: :skip)
98
+ # enty = new
99
+ # enty.set_fields(hash, data_fields, missing: :skip)
101
100
  enty = create(hash)
102
- #enty.set(hash)
101
+ # enty.set(hash)
103
102
  enty
104
103
  end
105
104
 
@@ -184,7 +183,7 @@ module Safrano
184
183
  end
185
184
  attrs = { 'Name' => pnam.to_s,
186
185
  # 'Type' => Safrano.get_edm_type(db_type: prop[:db_type]) }
187
- 'Type' => prop[:odata_edm_type] }
186
+ 'Type' => prop[:edm_type] }
188
187
  attrs['Nullable'] = 'false' if prop[:allow_null] == false
189
188
  enty.add_element('Property', attrs)
190
189
  end
@@ -357,19 +356,41 @@ module Safrano
357
356
  # build default output template structure
358
357
  build_default_template
359
358
 
360
- # Time columns
361
- @time_cols = db_schema.select { |_c, v| v[:type] == :datetime }.map { |c, _v| c }
362
-
363
-
364
-
365
359
  # add edm_types into schema
366
360
  db_schema.each do |_col, props|
367
- props[:odata_edm_type] = Safrano.default_edm_type(ruby_type: props[:type],
368
- db_type: props[:db_type])
361
+ Safrano.add_edm_types(props)
369
362
  end
370
- # Edm.Decimal cols
371
- @decimal_cols = db_schema.select { |_c, v| v[:odata_edm_type] == 'Edm.Decimal' }.map { |c, _v| c }
372
-
363
+
364
+ # cols needed catsting before final json output
365
+ @casted_cols = {}
366
+ db_schema.each { |col, props|
367
+ if (props[:edm_precision] && (props[:edm_type] =~ /\AEdm.Decimal\(/i))
368
+ # we save the precision and scale in the lambda (binding!)
369
+ @casted_cols[col] = ->(x) {
370
+ # not sure if these copies are really needed, but feels better that way
371
+ x&.toDecimalPrecisionString(props[:edm_precision], props[:edm_scale])
372
+ }
373
+
374
+ next
375
+ end
376
+ if props[:edm_type] == 'Edm.Decimal'
377
+ @casted_cols[col] = ->(x) { x&.toDecimalString }
378
+ next
379
+ end
380
+ # Odata V2 Spec:
381
+ # Edm.Binary Base64 encoded value of an EDM.Binary value represented as a JSON string
382
+ # See for example https://services.odata.org/V2/Northwind/Northwind.svc/Categories(1)?$format=json
383
+ if props[:edm_type] == 'Edm.Binary'
384
+ @casted_cols[col] = ->(x) { Base64.encode64(x) unless x.nil? } # Base64
385
+ next
386
+ end
387
+ # TODO check this more in details
388
+ if props[:type] == :datetime
389
+ @casted_cols[col] = ->(x) { x&.iso8601 }
390
+
391
+ end
392
+ }
393
+
373
394
  # and finally build the path lists and allowed tr's
374
395
  build_attribute_path_list
375
396
  build_expand_path_list
@@ -6,7 +6,7 @@ require_relative 'error'
6
6
  module Safrano
7
7
  # represents a state transition when navigating/parsing the url path
8
8
  # from left to right
9
- class Transition
9
+ class Transition
10
10
  attr_accessor :trans
11
11
  attr_accessor :match_result
12
12
  attr_accessor :rgx
@@ -52,26 +52,30 @@ module Safrano
52
52
  ctx.method(@trans).call(@match_result)
53
53
  end
54
54
  end
55
-
56
- #Transition that does not move/change the input
57
- class InplaceTransition < Transition
58
- def initialize(trans: )
55
+
56
+ # Transition that does not move/change the input
57
+ class InplaceTransition < Transition
58
+ def initialize(trans:)
59
59
  @trans = trans
60
60
  end
61
+
61
62
  def do_match(str)
62
63
  @str = str
63
64
  end
65
+
64
66
  def path_remain
65
- @str
67
+ @str
66
68
  end
69
+
67
70
  def path_done
68
71
  EMPTYSTR
69
72
  end
73
+
70
74
  def do_transition(ctx)
71
75
  ctx.method(@trans).call(@str)
72
76
  end
73
77
  end
74
-
78
+
75
79
  TransitionEnd = Transition.new('\A(\/?)\z', trans: 'transition_end')
76
80
  TransitionExecuteFunc = InplaceTransition.new(trans: 'transition_execute_func')
77
81
  TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
data/lib/odata/walker.rb CHANGED
@@ -29,24 +29,24 @@ module Safrano
29
29
 
30
30
  # are $links requested ?
31
31
  attr_reader :do_links
32
-
33
- attr_reader :request
32
+
33
+ attr_reader :request
34
34
 
35
35
  NIL_SERVICE_FATAL = 'Walker is called with a nil service'
36
36
  EMPTYSTR = ''
37
37
  SLASH = '/'
38
38
 
39
- def initialize(service, path, request, content_id_refs = nil )
39
+ def initialize(service, path, request, content_id_refs = nil)
40
40
  raise NIL_SERVICE_FATAL unless service
41
41
 
42
42
  path = URI.decode_www_form_component(path)
43
43
  @context = service
44
44
  @content_id_refs = content_id_refs
45
-
45
+
46
46
  # needed because for function import we need access to the url parameters (req.params)
47
47
  # who contains the functions params
48
48
  @request = request
49
-
49
+
50
50
  @contexts = [@context]
51
51
 
52
52
  @path_start = @path_remain = if service
@@ -115,16 +115,16 @@ module Safrano
115
115
  end
116
116
  end
117
117
 
118
- # execute function import with request parameters
118
+ # execute function import with request parameters
119
119
  # input: @context containt the function to exectute,
120
120
  # @request.params should normally contain the params
121
- # result: validate the params for the given function, execute the function and
121
+ # result: validate the params for the given function, execute the function and
122
122
  # return it's result back into @context,
123
123
  # and finaly set status :end (or error if anyting went wrong )
124
124
  def do_run_with_execute_func
125
125
  @context, @status, @error = @context.do_execute_func(@request)
126
126
  end
127
-
127
+
128
128
  # little hacks... depending on returned state, set some attributes
129
129
  def state_mappings
130
130
  case @status
@@ -343,10 +343,10 @@ module Safrano
343
343
  @collections.sort_by! { |klass| klass.entity_set_name.size }.reverse! if @collections
344
344
  @collections
345
345
  end
346
-
346
+
347
347
  # need to be sorted by size too
348
348
  def set_funcimports_sorted
349
- @function_import_keys.sort_by!{|k| k.size}.reverse!
349
+ @function_import_keys.sort_by! { |k| k.size }.reverse!
350
350
  end
351
351
  # to be called at end of publishing block to ensure we get the right names
352
352
  # and additionally build the list of valid attribute path's used
@@ -375,11 +375,25 @@ module Safrano
375
375
  # finalize the uri's and include NoMappingBeforeOutput or MappingBeforeOutput as needed
376
376
  @collections.each do |klass|
377
377
  klass.build_uri(@uribase)
378
- # TODO perf
379
- klass.include( (klass.time_cols.empty? && klass.decimal_cols.empty?) ? Safrano::NoMappingBeforeOutput : Safrano::MappingBeforeOutput)
380
378
 
381
379
  # Output create (POST) as single entity (Standard) or as array (non-standard buggy)
382
380
  klass.include ( @bugfix_create_response ? Safrano::EntityCreateStandardOutput : Safrano::EntityCreateArrayOutput)
381
+
382
+ # define the most optimal casted_values method for the given model(klass)
383
+ if (klass.casted_cols.empty?)
384
+ klass.send(:define_method, :casted_values) do |cols = nil|
385
+ cols ? selected_values_for_odata(cols) : values_for_odata
386
+ end
387
+ else
388
+ klass.send(:define_method, :casted_values) do |cols = nil|
389
+ # we need to dup the model values as we need to change it before passing to_json,
390
+ # but we dont want to interfere with Sequel's owned data
391
+ # (eg because then in worst case it could happen that we write back changed values to DB)
392
+ vals = cols ? selected_values_for_odata(cols) : values_for_odata.dup
393
+ self.class.casted_cols.each { |cc, lambda| vals[cc] = lambda.call(vals[cc]) if vals.key?(cc) }
394
+ vals
395
+ end
396
+ end
383
397
  end
384
398
 
385
399
  # build allowed transitions (requires that @collections are filled and sorted for having a
@@ -505,8 +519,8 @@ module Safrano
505
519
  doc.add_element('edmx:Edmx', 'Version' => '1.0')
506
520
  doc.root.add_namespace('xmlns:edmx', XMLNS::MSFT_ADO_2007_EDMX)
507
521
  serv = doc.root.add_element('edmx:DataServices',
508
- # TODO: export the real version (result from version negotions)
509
- # but currently we support only v1 and v2, and most users will use v2
522
+ # TODO: export the real version (result from version negotions)
523
+ # but currently we support only v1 and v2, and most users will use v2
510
524
  'm:DataServiceVersion' => '2.0')
511
525
  # 'm:DataServiceVersion' => "#{self.dataServiceVersion}" )
512
526
  # DataServiceVersion: This attribute MUST be in the data service
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Safrano
4
- VERSION = '0.5.3'
4
+ VERSION = '0.5.4'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safrano
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - oz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-10 00:00:00.000000000 Z
11
+ date: 2021-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -117,12 +117,14 @@ files:
117
117
  - lib/core_ext/Dir/iter.rb
118
118
  - lib/core_ext/Hash/transform.rb
119
119
  - lib/core_ext/Integer/edm.rb
120
+ - lib/core_ext/Numeric/convert.rb
120
121
  - lib/core_ext/REXML/Document/output.rb
121
122
  - lib/core_ext/String/convert.rb
122
123
  - lib/core_ext/String/edm.rb
123
124
  - lib/core_ext/dir.rb
124
125
  - lib/core_ext/hash.rb
125
126
  - lib/core_ext/integer.rb
127
+ - lib/core_ext/numeric.rb
126
128
  - lib/core_ext/rexml.rb
127
129
  - lib/core_ext/string.rb
128
130
  - lib/odata/attribute.rb