safrano 0.6.8 → 0.7.0

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: 9d1ee63cc31ce50cff784481ce75f5f5581c2b9ff406a18efb516908bbf7c65d
4
- data.tar.gz: 5770021472ccce8efb445114d4b4834b4fb0ff31b58f6444654cfb16b5aeb860
3
+ metadata.gz: dda061616194242041f7308c791bc5689fabaf0627f6f5090df3141fe5a34d8e
4
+ data.tar.gz: bec6791bb225330e1e149bf4166012397f9ffce9df2dbe0be3d7b224c5172212
5
5
  SHA512:
6
- metadata.gz: b1be48927ce7f3b5350ea903fa6ebb514cd6c09a1eafb064b7760c56bb396f02e59cab6e2a2e091ab8c526c77d45e9ff18838728d34d7500b29f611ba98e858b
7
- data.tar.gz: 5877c1e41a0bbbcb76d948914a703a24ea8cdf52215815b7c4051c3cfb3807463adfa0144ccce4861dfd988abf8776a002de6353d6343205f3767af02b9ca090
6
+ metadata.gz: 7aef08bdf67609d7e836b955e478f4b0a8cd67a08fc6caf0c9e9b263bac3d59086a6da59967cdacf5b3bb328060cbb20b9cdbe70e28fcacb5ce464e63eeb7e59
7
+ data.tar.gz: 2f65e93d591fc18ca7836a17c61250bc181c1d9aff14bd875e2d2f4803406ca76d577c6cd4c0b5f7e468240ac6d63e9389f7367a5b6cba6bfa298c4c5efd5fcd
@@ -49,7 +49,7 @@ module Safrano
49
49
  else
50
50
  # same as above with GMT offset in minutes
51
51
  # DateTime offset is Rational ; fraction of hours per Day --> *24*60
52
- min_off_s = (min_off = (offset * 60 * 24).to_i) > 0 ? "+#{min_off}" : min_off.to_s
52
+ min_off_s = (min_off = (offset * 60 * 24).to_i).positive? ? "+#{min_off}" : min_off.to_s
53
53
  "/Date(#{strftime('%Q')}#{min_off_s})/"
54
54
  end
55
55
  end
@@ -57,7 +57,7 @@ module Safrano
57
57
  # the json raw data is like this : "HireDate": "\/Date(704678400000)\/"
58
58
  # --> \/\/
59
59
  def to_edm_json
60
- if utc? || (gmt_offset == 0)
60
+ if utc? || gmt_offset.zero?
61
61
  # no offset
62
62
  # %s : seconds since unix epoch
63
63
  # %L : milliseconds 000-999
@@ -66,7 +66,7 @@ module Safrano
66
66
  else
67
67
  # same as above with GMT offset in minutes
68
68
 
69
- min_off_s = (min_off = gmt_offset / 60) > 0 ? "+#{min_off}" : min_off.to_s
69
+ min_off_s = (min_off = gmt_offset / 60).positive? ? "+#{min_off}" : min_off.to_s
70
70
  "/Date(#{strftime('%s%L')}#{min_off_s})/"
71
71
  end
72
72
  end
@@ -194,9 +194,8 @@ module Safrano
194
194
  @child_dataset_method.call
195
195
  end
196
196
 
197
- def each
198
- y = @child_method.call
199
- y.each { |enty| yield enty }
197
+ def each(&block)
198
+ @child_method.call.each(&block)
200
199
  end
201
200
 
202
201
  def to_a
@@ -10,9 +10,7 @@ module Safrano
10
10
  module Media
11
11
  # base class for Media Handler
12
12
  class Handler
13
- def check_before_create(data:,
14
- entity:,
15
- filename:)
13
+ def check_before_create(data:, entity:, filename:)
16
14
  Contract::OK
17
15
  end
18
16
  end
@@ -21,8 +19,9 @@ module Safrano
21
19
  # with a flat directory structure
22
20
  class Static < Handler
23
21
  def initialize(root: nil, mediaklass:)
22
+ super()
24
23
  @root = Pathname(File.absolute_path(root || Dir.pwd))
25
- @files_class = (::Rack.release[0..2] == '2.0') ? ::Rack::File : ::Rack::Files
24
+ @files_class = ::Rack.release[0..2] == '2.0' ? ::Rack::File : ::Rack::Files
26
25
  @media_class = mediaklass
27
26
  @media_dir_name = Pathname(mediaklass.to_s)
28
27
  @semaphore = Thread::Mutex.new
@@ -32,7 +31,7 @@ module Safrano
32
31
 
33
32
  def register
34
33
  @abs_klass_dir = @root + @media_dir_name
35
- @abs_temp_dir = @abs_klass_dir + 'tmp'
34
+ @abs_temp_dir = @abs_klass_dir.join('tmp')
36
35
  end
37
36
 
38
37
  def create_abs_class_dir
@@ -129,7 +128,7 @@ module Safrano
129
128
  # Here as well, MVP implementation
130
129
  def save_file(data:, filename:, entity:)
131
130
  with_media_directory(entity) do |d|
132
- filename = d + '1'
131
+ filename = d.join('1')
133
132
  atomic_write(filename) { |f| IO.copy_stream(data, f) }
134
133
  end
135
134
  end
@@ -138,7 +137,7 @@ module Safrano
138
137
  # after each upload, so that clients get informed about new versions
139
138
  # of the same media ressource
140
139
  def ressource_version(entity)
141
- abs_media_directory(entity).children(with_directory = false).max.to_s
140
+ abs_media_directory(entity).children(false).max.to_s
142
141
  end
143
142
 
144
143
  # Note: add a new Version and remove the previous one
@@ -13,11 +13,28 @@ module Rack
13
13
  MSG_FUNC = case FORMAT.count('%')
14
14
  when 10
15
15
  lambda { |env, length, status, began_at|
16
- format(FORMAT, env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-', env['REMOTE_USER'] || '-', Time.now.strftime('%d/%b/%Y:%H:%M:%S %z'), env[REQUEST_METHOD], env[SCRIPT_NAME] + env[PATH_INFO], env[QUERY_STRING].empty? ? '' : "?#{env[QUERY_STRING]}", env[SERVER_PROTOCOL], status.to_s[0..3], length, Utils.clock_time - began_at)
16
+ format(FORMAT,
17
+ env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-',
18
+ env['REMOTE_USER'] || '-',
19
+ Time.now.strftime('%d/%b/%Y:%H:%M:%S %z'),
20
+ env[REQUEST_METHOD],
21
+ env[SCRIPT_NAME] + env[PATH_INFO],
22
+ env[QUERY_STRING].empty? ? '' : "?#{env[QUERY_STRING]}",
23
+ env[SERVER_PROTOCOL],
24
+ status.to_s[0..3], length, Utils.clock_time - began_at)
17
25
  }
18
26
  when 11
19
27
  lambda { |env, length, status, began_at|
20
- format(FORMAT, env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-', env['REMOTE_USER'] || '-', Time.now.strftime('%d/%b/%Y:%H:%M:%S %z'), env[REQUEST_METHOD], env[SCRIPT_NAME], env[PATH_INFO], env[QUERY_STRING].empty? ? '' : "?#{env[QUERY_STRING]}", env[SERVER_PROTOCOL], status.to_s[0..3], length, Utils.clock_time - began_at)
28
+ format(FORMAT,
29
+ env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-',
30
+ env['REMOTE_USER'] || '-',
31
+ Time.now.strftime('%d/%b/%Y:%H:%M:%S %z'),
32
+ env[REQUEST_METHOD],
33
+ env[SCRIPT_NAME],
34
+ env[PATH_INFO],
35
+ env[QUERY_STRING].empty? ? '' : "?#{env[QUERY_STRING]}",
36
+ env[SERVER_PROTOCOL],
37
+ status.to_s[0..3], length, Utils.clock_time - began_at)
21
38
  }
22
39
  end
23
40
 
@@ -92,7 +92,7 @@ module Safrano
92
92
  # wrapper
93
93
  # for OData Entity and Collections, return them directly
94
94
  # for others, ie ComplexType, Prims etc, return the ResultDefinition-subclass wrapped result
95
- def self.do_execute_func_result(result, _req, _apply_query_params = false)
95
+ def self.do_execute_func_result(result, _req, apply_query_params: false)
96
96
  new(result)
97
97
  end
98
98
  end
@@ -129,7 +129,7 @@ module Safrano
129
129
 
130
130
  # wrapper
131
131
  # for OData Entity return them directly
132
- def self.do_execute_func_result(result, _req, apply_query_params = false)
132
+ def self.do_execute_func_result(result, _req, apply_query_params: false)
133
133
  # note: Sequel entities instances seem to be thread safe, so we can
134
134
  # safely add request-dependant data (eg. req.params) there
135
135
  apply_query_params ? result : result.inactive_query_params
@@ -143,7 +143,7 @@ module Safrano
143
143
 
144
144
  # wrapper
145
145
  # for OData Entity Collection return them directly
146
- def self.do_execute_func_result(result, req, apply_query_params = false)
146
+ def self.do_execute_func_result(result, req, apply_query_params: false)
147
147
  coll = Safrano::OData::Collection.new(@klassmod)
148
148
  # instance_exec has other instance variables; @values would be nil in the block below
149
149
  # need to pass a local copy
@@ -241,12 +241,8 @@ module Safrano
241
241
  def odata_h
242
242
  ret = { METAK => { TYPEK => self.class.type_name } }
243
243
 
244
- @values.each do |k, v|
245
- ret[k] = if v.respond_to? :odata_h
246
- v.odata_h
247
- else
248
- v
249
- end
244
+ @values.each do |key, val|
245
+ ret[key] = val.respond_to?(:odata_h) ? val.odata_h : val
250
246
  end
251
247
  ret
252
248
  end
@@ -38,6 +38,28 @@ module Safrano
38
38
 
39
39
  # type mappings are hard, especially between "Standards" like SQL and OData V2 (might be a bit better in V4 ?)
40
40
  # this is all best effort/try to make it work logic
41
+
42
+ RUBY_TY_EDM_TY_MAP = { integer: 'Edm.Int32',
43
+ string: 'Edm.String',
44
+ date: 'Edm.DateTime',
45
+ datetime: 'Edm.DateTime',
46
+ time: 'Edm.Time',
47
+ boolean: 'Edm.Boolean',
48
+ float: 'Edm.Double',
49
+ decimal: 'Edm.Decimal',
50
+ blob: 'Edm.Binary' }.freeze
51
+ DB_TY_EDM_TY_MAP = { 'smallint' => 'Edm.Int16',
52
+ 'int2' => 'Edm.Int16',
53
+ 'smallserial' => 'Edm.Int16',
54
+ 'int' => 'Edm.Int32',
55
+ 'integer' => 'Edm.Int32',
56
+ 'serial' => 'Edm.Int32',
57
+ 'mediumint' => 'Edm.Int32',
58
+ 'int4' => 'Edm.Int32',
59
+ 'bigint' => 'Edm.Int64',
60
+ 'bigserial' => 'Edm.Int64',
61
+ 'int8' => 'Edm.Int64',
62
+ 'tinyint' => 'Edm.Byte' }.freeze
41
63
  def self.add_edm_types(metadata, props)
42
64
  # try num/dec with db_type:
43
65
  metadata[:edm_type] = if (md = DB_TYPE_NUMDEC_RGX.match(props[:db_type]))
@@ -80,51 +102,24 @@ module Safrano
80
102
 
81
103
  # try int-like with db_type:
82
104
  # smallint|int|integer|bigint|serial|bigserial
83
- metadata[:edm_type] = if (md = DB_TYPE_INTLIKE_RGX.match(props[:db_type]))
84
105
 
106
+ metadata[:edm_type] = if (md = DB_TYPE_INTLIKE_RGX.match(props[:db_type]))
85
107
  if (itype = md[1])
86
- case itype.downcase
87
- when 'smallint', 'int2', 'smallserial'
88
- 'Edm.Int16'
89
- when 'int', 'integer', 'serial', 'mediumint', 'int4'
90
- 'Edm.Int32'
91
- when 'bigint', 'bigserial', 'int8'
92
- 'Edm.Int64'
93
- when 'tinyint'
94
- 'Edm.Byte'
95
- end
108
+ DB_TY_EDM_TY_MAP[itype.downcase]
96
109
  end
97
110
  end
98
111
  return if metadata[:edm_type]
99
112
 
100
113
  # try Guid with db_type:
101
114
 
102
- metadata[:edm_type] = if (DB_TYPE_GUID_RGX.match(props[:db_type]))
115
+ metadata[:edm_type] = if DB_TYPE_GUID_RGX.match(props[:db_type])
103
116
  'Edm.Guid'
104
117
  end
118
+
105
119
  return if metadata[:edm_type]
106
120
 
107
121
  # try with Sequel(ruby) type
108
- metadata[:edm_type] = case props[:type]
109
- when :integer
110
- 'Edm.Int32'
111
- when :string
112
- 'Edm.String'
113
- when :date
114
- 'Edm.DateTime'
115
- when :datetime
116
- 'Edm.DateTime'
117
- when :time
118
- 'Edm.Time'
119
- when :boolean
120
- 'Edm.Boolean'
121
- when :float
122
- 'Edm.Double'
123
- when :decimal
124
- 'Edm.Decimal'
125
- when :blob
126
- 'Edm.Binary'
127
- end
122
+ metadata[:edm_type] = RUBY_TY_EDM_TY_MAP[props[:type]]
128
123
  end
129
124
 
130
125
  # use Edm twice so that we can do include Safrano::Edm and then
@@ -153,8 +148,8 @@ module Safrano
153
148
  nil
154
149
  end
155
150
 
156
- def self.convert_from_urlparam(v)
157
- return Contract::NOK unless v == 'null'
151
+ def self.convert_from_urlparam(val)
152
+ return Contract::NOK unless val == 'null'
158
153
 
159
154
  Contract.valid(nil)
160
155
  end
@@ -164,9 +159,9 @@ module Safrano
164
159
  class Binary < String
165
160
  extend OutputClassMethods
166
161
 
167
- def self.convert_from_urlparam(v)
168
- # TODO this should use base64
169
- Contract.valid(v.dup.force_encoding('BINARY'))
162
+ def self.convert_from_urlparam(val)
163
+ # TODO: this should use base64
164
+ Contract.valid(val.dup.force_encoding('BINARY'))
170
165
  end
171
166
  end
172
167
 
@@ -180,13 +175,13 @@ module Safrano
180
175
  end
181
176
 
182
177
  def self.odata_collection(array)
183
- array.map { |v| odata_value(v) }
178
+ array.map { |val| odata_value(val) }
184
179
  end
185
180
 
186
- def self.convert_from_urlparam(v)
187
- return Contract::NOK unless %w[true false].include?(v)
181
+ def self.convert_from_urlparam(val)
182
+ return Contract::NOK unless %w[true false].include?(val)
188
183
 
189
- Contract.valid(v == 'true')
184
+ Contract.valid(val == 'true')
190
185
  end
191
186
  end
192
187
 
@@ -195,8 +190,8 @@ module Safrano
195
190
  class Byte < Integer
196
191
  extend OutputClassMethods
197
192
 
198
- def self.convert_from_urlparam(v)
199
- return Contract::NOK unless (bytev = v.to_i) < 256
193
+ def self.convert_from_urlparam(val)
194
+ return Contract::NOK unless (bytev = val.to_i) < 256
200
195
 
201
196
  Contract.valid(bytev)
202
197
  end
@@ -209,29 +204,29 @@ module Safrano
209
204
  end
210
205
 
211
206
  def self.odata_collection(array)
212
- array.map { |v| odata_value(v) }
207
+ array.map { |val| odata_value(val) }
213
208
  end
214
209
 
215
- def self.convert_from_urlparam(v)
216
- Contract.valid(DateTime.parse(v))
210
+ def self.convert_from_urlparam(val)
211
+ Contract.valid(DateTime.parse(val))
217
212
  rescue StandardError
218
- convertion_error(v)
213
+ convertion_error(val)
219
214
  end
220
215
  end
221
216
 
222
217
  class String < ::String
223
218
  extend OutputClassMethods
224
219
 
225
- def self.convert_from_urlparam(v)
226
- Contract.valid(v)
220
+ def self.convert_from_urlparam(val)
221
+ Contract.valid(val)
227
222
  end
228
223
  end
229
224
 
230
225
  class Int32 < Integer
231
226
  extend OutputClassMethods
232
227
 
233
- def self.convert_from_urlparam(v)
234
- return Contract::NOK unless (ret = number_or_nil(v))
228
+ def self.convert_from_urlparam(val)
229
+ return Contract::NOK unless (ret = number_or_nil(val))
235
230
 
236
231
  Contract.valid(ret)
237
232
  end
@@ -240,8 +235,8 @@ module Safrano
240
235
  class Int64 < Integer
241
236
  extend OutputClassMethods
242
237
 
243
- def self.convert_from_urlparam(v)
244
- return Contract::NOK unless (ret = number_or_nil(v))
238
+ def self.convert_from_urlparam(val)
239
+ return Contract::NOK unless (ret = number_or_nil(val))
245
240
 
246
241
  Contract.valid(ret)
247
242
  end
@@ -250,8 +245,8 @@ module Safrano
250
245
  class Double < Float
251
246
  extend OutputClassMethods
252
247
 
253
- def self.convert_from_urlparam(v)
254
- Contract.valid(v.to_f)
248
+ def self.convert_from_urlparam(val)
249
+ Contract.valid(val.to_f)
255
250
  rescue StandardError
256
251
  Contract::NOK
257
252
  end
@@ -260,10 +255,10 @@ module Safrano
260
255
  class Guid < UUIDTools::UUID
261
256
  extend OutputClassMethods
262
257
 
263
- def self.convert_from_urlparam(v)
264
- Contract::NOK unless m = Filter::Parser::Token::GUIDRGX.match(v)
258
+ def self.convert_from_urlparam(val)
259
+ Contract::NOK unless (m = Filter::Parser::Token::GUIDRGX.match(val))
265
260
 
266
- Contract.valid(UUIDTools::UUID.parse m[1])
261
+ Contract.valid(UUIDTools::UUID.parse(m[1]))
267
262
  rescue StandardError
268
263
  Contract::NOK
269
264
  end
@@ -271,9 +266,3 @@ module Safrano
271
266
  end
272
267
  end
273
268
  end
274
-
275
- # include Safrano
276
-
277
- # x = Edm::String.new('xxx')
278
-
279
- # pp x
data/lib/odata/error.rb CHANGED
@@ -50,9 +50,9 @@ module Safrano
50
50
 
51
51
  # used in function import error handling. cf. func import / do_execute_func
52
52
  def self.with_error(result)
53
- if result.respond_to?(:error) && (err = result.error)
54
- yield err
55
- end
53
+ return unless result.respond_to?(:error) && (err = result.error)
54
+
55
+ yield err
56
56
  end
57
57
 
58
58
  # base module for HTTP errors, when used as a Error Class
data/lib/odata/expand.rb CHANGED
@@ -14,9 +14,6 @@ module Safrano
14
14
  expandstr.nil? ? EmptyExpand : MultiExpand.new(expandstr, model)
15
15
  end
16
16
 
17
- # output template
18
- attr_reader :template
19
-
20
17
  def apply_to_dataset(dtcx)
21
18
  Contract.valid(dtcx)
22
19
  end
@@ -196,7 +196,7 @@ module Safrano
196
196
  unless (@error = check_url_func_params)
197
197
  begin
198
198
  return yield
199
- rescue LocalJumpError => e
199
+ rescue LocalJumpError
200
200
  @error = Safrano::ServiceOperationReturnError.new
201
201
  end
202
202
  end
@@ -212,13 +212,15 @@ module Safrano
212
212
 
213
213
  # Note: Sequel::Error exceptions are already
214
214
  # handled on rack app level (cf. the call methode )
215
- Safrano::with_error(result) do |error|
215
+ Safrano.with_error(result) do |error|
216
216
  @error = error
217
217
  return [nil, :error, @error] # this is return from do_execute_func !
218
218
  end
219
219
 
220
220
  # non error case
221
- [@returning.do_execute_func_result(result, req, @auto_query_params), :run]
221
+ [@returning.do_execute_func_result(result, req,
222
+ apply_query_params: @auto_query_params),
223
+ :run]
222
224
  end
223
225
  end
224
226
 
@@ -142,11 +142,11 @@ module Safrano
142
142
  @attribute_path_list = attribute_path_list
143
143
  end
144
144
 
145
- MAX_DEPTH = 6
145
+ MAX_DEPTH = 4
146
146
  def attribute_path_list(depth = 0)
147
147
  ret = @columns_str.dup
148
148
  # break circles
149
- return ret if depth > MAX_DEPTH
149
+ return ret if depth >= MAX_DEPTH
150
150
 
151
151
  depth += 1
152
152
 
@@ -166,7 +166,7 @@ module Safrano
166
166
  ret.concat(@nav_collection_attribs_keys) if @nav_collection_attribs
167
167
 
168
168
  # break circles
169
- return ret if depth > MAX_DEPTH
169
+ return ret if depth >= MAX_DEPTH
170
170
 
171
171
  depth += 1
172
172
 
@@ -387,7 +387,7 @@ module Safrano
387
387
  end
388
388
 
389
389
  # attribute specific type mapping
390
- if colmap = @type_mappings[col]
390
+ if (colmap = @type_mappings[col])
391
391
  metadata[:edm_type] = colmap.edm_type
392
392
  if colmap.castfunc
393
393
  @casted_cols[col] = colmap.castfunc
@@ -430,7 +430,7 @@ module Safrano
430
430
  if metadata[:edm_type] == 'Edm.Guid'
431
431
 
432
432
  if props[:type] == :blob # Edm.Guid but as 16 byte binary Blob on DB level, eg in Sqlite
433
- @casted_cols[col] = ->(x) {
433
+ @casted_cols[col] = lambda { |x|
434
434
  UUIDTools::UUID.parse_raw(x).to_s unless x.nil?
435
435
  } # Base64
436
436
  next
@@ -522,7 +522,7 @@ module Safrano
522
522
 
523
523
  kvpredicate = case db_schema[primary_key][:type]
524
524
  when :integer
525
- # TODO harmonize this with primitive_types.rb convert_from_url
525
+ # TODO: Harmonize this with primitive_types.rb convert_from_url
526
526
  @pk_cast_from_string = ->(str) { Integer(str) }
527
527
  /(\d+)/.freeze
528
528
  else
@@ -530,9 +530,7 @@ module Safrano
530
530
  case metadata[:edm_type]
531
531
  when 'Edm.Guid'
532
532
  if db_schema[primary_key][:type] == :blob # Edm.Guid but as 16byte binary Blob on DB
533
- @pk_cast_from_string = ->(str) {
534
- # TODO harmonize this with primitive_types.rb convert_from_url
535
- # Sequel::SQL::Blob.new([ str.gsub('-', '') ].pack('H*')) }
533
+ @pk_cast_from_string = lambda { |str|
536
534
  Sequel::SQL::Blob.new(UUIDTools::UUID.parse(str).raw)
537
535
  }
538
536
  end
@@ -544,7 +542,7 @@ module Safrano
544
542
  @iuk_rgx = /\A\s*#{kvpredicate}\s*\z/
545
543
  end
546
544
  # @entity_id_url_regexp = /\A\(\s*#{kvpredicate}\s*\)(.*)/.freeze
547
- @entity_id_url_regexp = KEYPRED_URL_REGEXP
545
+ @entity_id_url_regexp = KEYPRED_URL_REGEXP
548
546
  end
549
547
 
550
548
  def prepare_fields
@@ -677,16 +675,14 @@ module Safrano
677
675
  mval = md[1]
678
676
  end
679
677
  end
680
- if mpk && mval
681
- mdch[mpk] = if (pk_cast = @pk_cast_from_string[mpk])
682
- pk_cast.call(mval)
683
- else
684
- mval # no cast needed, eg for string
685
- end
686
- scan_rgx_parts.delete(mpk)
687
- else
688
- return Contract::NOK
689
- end
678
+ return Contract::NOK unless mpk && mval
679
+
680
+ mdch[mpk] = if (pk_cast = @pk_cast_from_string[mpk])
681
+ pk_cast.call(mval)
682
+ else
683
+ mval # no cast needed, eg for string
684
+ end
685
+ scan_rgx_parts.delete(mpk)
690
686
  end
691
687
  # normally arriving here we have mdch filled with key values pairs,
692
688
  # but not in the model key ordering. lets just re-order the values
data/lib/odata/walker.rb CHANGED
@@ -66,34 +66,27 @@ module Safrano
66
66
  path
67
67
  else
68
68
  # path.sub!(/\A#{prefix}/, '')
69
- # TODO check
69
+ # TODO: check
70
70
  path.sub(/\A#{prefix}/, EMPTYSTR)
71
71
  end
72
72
  end
73
73
 
74
74
  def get_next_transition
75
- # this does not work if there are multiple valid transitions
75
+ # handle multiple valid transitions
76
76
  # like when we have attributes that are substring of each other
77
77
  # --> instead of using detect (ie take first transition)
78
78
  # we need to use select and then find the longest match
79
- # tr_next = @context.allowed_transitions.detect do |t|
80
- # t.do_match(@path_remain)
81
- # end
82
79
 
83
- valid_tr = @context.allowed_transitions.select do |t|
80
+ valid_tr = @context.allowed_transitions.map(&:dup).select do |t|
84
81
  t.do_match(@path_remain)
85
82
  end
86
83
 
87
- # this is a very fragile and obscure but required hack (wanted: a
88
- # better one) to make attributes that are substrings of each other
84
+ # HACK: (wanted: a better one) to make attributes that are substrings of each other
89
85
  # work well
90
- @tr_next = if valid_tr
91
- if valid_tr.size == 1
92
- valid_tr.first
93
- elsif valid_tr.size > 1
94
- valid_tr.max_by { |t| t.match_result[1].size }
95
- end
96
- end
86
+ return unless valid_tr
87
+ return (@tr_next = nil) if valid_tr.empty?
88
+
89
+ @tr_next = valid_tr.size == 1 ? valid_tr.first : valid_tr.max_by { |t| t.match_result[1].size }
97
90
  end
98
91
 
99
92
  # perform a content-id ($batch changeset ref) transition
@@ -45,9 +45,9 @@ module MIME
45
45
  end
46
46
 
47
47
  def parse_next_part(line)
48
- if @target.parser.next_part(line)
48
+ return if @target.parser.next_part(line)
49
49
 
50
- elsif @target.parser.last_part(line)
50
+ if @target.parser.last_part(line)
51
51
  @state = :end
52
52
  else
53
53
  @target.parser.addline(line)
@@ -138,18 +138,17 @@ module MIME
138
138
 
139
139
  def parse_str(inpstr, level: 0)
140
140
  # we need to keep the line separators --> use io.readlines
141
- if inpstr.respond_to?(:readlines)
142
- @lines = inpstr.readlines(@sep)
143
- else
144
- # rack input wrapper only has gets but not readlines
145
- # BUT the rack SPEC says it only supports gets without argument!
146
- # --> finally we end up using read and split into lines...
147
- # normally should be ok for $batch POST payloads
148
-
149
- # inpstr.read should be a String
150
- @lines = inpstr.read.lines(@sep)
151
-
152
- end
141
+ @lines = if inpstr.respond_to?(:readlines)
142
+ inpstr.readlines(@sep)
143
+ else
144
+ # rack input wrapper only has gets but not readlines
145
+ # BUT the rack SPEC says it only supports gets without argument!
146
+ # --> finally we end up using read and split into lines...
147
+ # normally should be ok for $batch POST payloads
148
+
149
+ # inpstr.read should be a String
150
+ inpstr.read.lines(@sep)
151
+ end
153
152
  # tmp hack for test-tools that convert CRLF in payload to LF :-(
154
153
  if @lines.size == 1
155
154
  @sep = LF
@@ -371,7 +370,7 @@ module MIME
371
370
 
372
371
  def get_mult_resp(full_req)
373
372
  get_response(full_req)
374
- [202, @response.hd, @response.unparse(true)]
373
+ [202, @response.hd, @response.unparse_bodyonly]
375
374
  end
376
375
 
377
376
  def get_response(full_req)
@@ -410,10 +409,20 @@ module MIME
410
409
  @response
411
410
  end
412
411
 
413
- def unparse(bodyonly = false)
412
+ def unparse
414
413
  b = +String.new
415
- b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}" unless bodyonly
414
+ b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
416
415
 
416
+ # warning: duplicated code with below
417
+ b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
418
+ b << @content.map(&:unparse).join(crbdcr)
419
+ b << "#{CRLF}--#{@boundary}--#{CRLF}"
420
+ b
421
+ end
422
+
423
+ def unparse_bodyonly
424
+ b = +String.new
425
+ # warning: duplicated code with above
417
426
  b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
418
427
  b << @content.map(&:unparse).join(crbdcr)
419
428
  b << "#{CRLF}--#{@boundary}--#{CRLF}"
@@ -61,6 +61,7 @@ module Safrano
61
61
  'Expires' => '-1',
62
62
  'Pragma' => 'no-cache' }.freeze
63
63
  DATASERVICEVERSION = 'DataServiceVersion'
64
+
64
65
  # borowed from Sinatra
65
66
  class AcceptEntry
66
67
  attr_accessor :params
@@ -79,10 +80,6 @@ module Safrano
79
80
  @q = @params.delete('q') { 1.0 }.to_f
80
81
  end
81
82
 
82
- def method_missing(*args, &block)
83
- to_str.send(*args, &block)
84
- end
85
-
86
83
  def <=>(other)
87
84
  other.priority <=> priority
88
85
  end
@@ -96,10 +93,6 @@ module Safrano
96
93
  super || to_str.respond_to?(*args)
97
94
  end
98
95
 
99
- def to_s(full = false)
100
- full ? entry : to_str
101
- end
102
-
103
96
  def to_str
104
97
  @type
105
98
  end
@@ -257,7 +257,7 @@ module Safrano
257
257
  # * Single/Multi PK
258
258
  # * Media/Non-Media entity
259
259
  # Putting this logic here in modules loaded once on start shall result in less runtime overhead
260
- def register_model(modelklass, entity_set_name = nil, is_media = false)
260
+ def register_model(modelklass, entity_set_name = nil, is_media: false)
261
261
  # check that the provided klass is a Sequel Model
262
262
 
263
263
  raise(Safrano::API::ModelNameError, modelklass) unless modelklass.is_a? Sequel::Model::ClassMethods
@@ -314,7 +314,7 @@ module Safrano
314
314
  end
315
315
 
316
316
  def publish_media_model(modelklass, entity_set_name = nil, &block)
317
- register_model(modelklass, entity_set_name, true)
317
+ register_model(modelklass, entity_set_name, is_media: true)
318
318
  # we need to execute the passed block in a deferred step
319
319
  # after all models have been registered (due to rel. dependancies)
320
320
  # modelklass.instance_eval(&block) if block_given?
@@ -450,7 +450,7 @@ module Safrano
450
450
  end
451
451
 
452
452
  # check function import definition
453
- function_imports.each_value { |func| func.check_definition }
453
+ function_imports.each_value(&:check_definition)
454
454
  end
455
455
 
456
456
  def execute_deferred_iblocks
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Safrano
4
- VERSION = '0.6.8'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -99,6 +99,8 @@ class JoinByPathsHelper < Set
99
99
  @result << iset
100
100
  end
101
101
 
102
+ many_to_many_count = 0 # counter for differing multiple
103
+ # many_to_many through alias
102
104
  @result.map! do |jseg|
103
105
  jseg.map do |seg|
104
106
  leftm = seg.first.model_class
@@ -122,29 +124,65 @@ class JoinByPathsHelper < Set
122
124
  lks = [leftm.primary_key].flatten
123
125
  rks = [assoc[:key]].flatten
124
126
 
125
- # TODO
126
- # when # :many_to_many :: A join table is used that has a foreign key that points
127
+ when :many_to_many, :one_through_one
128
+ # :many_to_many :: A join table is used that has a foreign key that points
127
129
  # to this model's primary key and a foreign key that points to the
128
130
  # associated model's primary key. Each current model object can be
129
131
  # associated with many associated model objects, and each associated
130
132
  # model object can be associated with many current model objects.
133
+ # TODO: testcase for :one_through_one
131
134
  # when # :one_through_one :: Similar to many_to_many in terms of foreign keys, but only one object
132
135
  # is associated to the current object through the association.
133
136
  # Provides only getter methods, no setter or modification methods.
134
137
 
138
+ result_ = []
139
+
140
+ # in case of multiple many_to_many rels, we need differing through aliases
141
+ many_to_many_count = many_to_many_count + 1
142
+ through_alias = "t#{many_to_many_count}".to_sym
143
+
144
+ # For many_to_many first we add the join with assigment table
145
+ lks_ = [assoc[:left_primary_key]].flatten
146
+ rks_ = [assoc[:left_key]].flatten
147
+
148
+ lks_.map! { |k| Sequel[seg.first.alias_sym][k] } unless seg.first.empty?
149
+ rks_.map! { |k| Sequel[through_alias][k] }
150
+
151
+ result_ << {
152
+ type: assoc[:type],
153
+ left: leftm.table_name,
154
+ right: assoc[:join_table],
155
+ alias: through_alias,
156
+ cond: rks_.zip(lks_).to_h
157
+ }
158
+
159
+ # then we add the join with with target table (right)
160
+ lks = [assoc[:right_key]].flatten
161
+ rks = [assoc.right_primary_key].flatten
162
+
163
+ lks.map! { |k| Sequel[through_alias][k] }
164
+ rks.map! { |k| Sequel[seg.last.alias_sym][k] }
165
+
166
+ result_ << {
167
+ type: assoc[:type],
168
+ left: assoc[:join_table],
169
+ right: rightm.table_name,
170
+ alias: seg.last.alias_sym,
171
+ cond: rks.zip(lks).to_h
172
+ }
173
+
174
+ next result_
175
+
176
+
135
177
  end
136
178
 
137
179
  lks.map! { |k| Sequel[seg.first.alias_sym][k] } unless seg.first.empty?
138
-
139
180
  rks.map! { |k| Sequel[seg.last.alias_sym][k] }
140
181
 
141
182
  { type: assoc[:type],
142
- segment: seg,
143
183
  left: leftm.table_name,
144
184
  right: rightm.table_name,
145
185
  alias: seg.last.alias_sym,
146
- left_keys: lks,
147
- right_keys: rks,
148
186
  cond: rks.zip(lks).to_h }
149
187
  end
150
188
  end
@@ -159,11 +197,14 @@ class JoinByPathsHelper < Set
159
197
  return start_dataset if empty?
160
198
 
161
199
  build_unique_join_segments
162
- @result.flatten.inject(start_dataset) do |dt, jo|
200
+ need_distinct = false
201
+ ret = @result.flatten.inject(start_dataset) do |dt, jo|
202
+ need_distinct = true if jo[:type] == :many_to_many
163
203
  dt.left_join(Sequel[jo[:right]].as(jo[:alias]),
164
204
  jo[:cond],
165
205
  implicit_qualifier: jo[:left])
166
206
  end
207
+ need_distinct ? ret.distinct : ret
167
208
  end
168
209
 
169
210
  def join_by_paths_helper(*pathlist)
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.6.8
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - oz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-13 00:00:00.000000000 Z
11
+ date: 2023-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack