safrano 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
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