safrano 0.0.4 → 0.0.6

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: 302464268b9b4475861347fca0cf7c330bed4dc35a0357b1ad4dc866b020134c
4
- data.tar.gz: 968c331ca598e6eb84682ac8f2ab94aa71ac1cdfa83744b310a2a75e22dc3008
3
+ metadata.gz: a69035d5b1015e36842490919163234c4ae55591657fb7d3b4b730b871c0035b
4
+ data.tar.gz: 5c0024539a67f37cbf397975315066b1df803dd826b242b873147bbd1da60062
5
5
  SHA512:
6
- metadata.gz: 0c374f75c178787452357907e8e81ba315ac4f855ff4f658e40bbc7b5e9ae1d0d2a9849973c80a94e1101731dd79fbc597c51e4a804721da999db28535fd7c78
7
- data.tar.gz: 3528b0d9150fd2b0bfc825894a03d438a77cf9d0b9bb11de2c3681938927d83187ac7ff21e16fb8240821f0f2931041ef0e75611928d8fa2948a8e377040ad24
6
+ metadata.gz: 7538b83433db7381e77a34415e9cd4887cd2db20bfe8ba05db66e6d3703a0358e9f3024f137bddfca3b9b490caf3afbc2262b6b26514a36efeae792c8983a670
7
+ data.tar.gz: b12e002bdbdeb826678d9cd1f8c1c9dce8bd11791adf8205464d12b115551bebbc59ddf4635d4406e93c4f5cee955f5356fea3d373857bac662ddbe5dd098ef7
@@ -0,0 +1,58 @@
1
+ require 'json'
2
+ require_relative '../safrano_core.rb'
3
+ require_relative './entity.rb'
4
+
5
+ module OData
6
+ # Represents a named and valued attribute of an Entity
7
+ class Attribute
8
+ attr_reader :name
9
+ attr_reader :entity
10
+ def initialize(entity, name)
11
+ @entity = entity
12
+ @name = name
13
+ end
14
+
15
+ def value
16
+ @entity.values[@name.to_sym]
17
+ end
18
+
19
+ def odata_get(req)
20
+ if req.walker.raw_value
21
+ [200, { 'Content-Type' => 'text/plain;charset=utf-8' },
22
+ value.to_s]
23
+ elsif req.accept?('application/json')
24
+ [200, { 'Content-Type' => 'application/json;charset=utf-8' },
25
+ to_odata_json(service: req.service)]
26
+ else # TODO: other formats
27
+ 406
28
+ end
29
+ end
30
+
31
+ # output as OData json (v2)
32
+ def to_odata_json(*)
33
+ { 'd' => { @name => value } }.to_json
34
+ end
35
+
36
+ # for testing purpose (assert_equal ...)
37
+ def ==(other)
38
+ (@entity == other.entity) && (@name == other.name)
39
+ end
40
+
41
+ # methods related to transitions to next state (cf. walker)
42
+ module Transitions
43
+ def transition_end(_match_result)
44
+ [nil, :end]
45
+ end
46
+
47
+ def transition_value(_match_result)
48
+ [self, :end_with_value]
49
+ end
50
+
51
+ def allowed_transitions
52
+ [Safrano::TransitionEnd,
53
+ Safrano::TransitionValue]
54
+ end
55
+ end
56
+ include Transitions
57
+ end
58
+ end
@@ -87,9 +87,9 @@ module OData
87
87
  enty = new
88
88
  hash.delete('__metadata')
89
89
  # DONE: move this somewhere else where it's evaluated only once at setup
90
- #data_fields = db_schema.map do |col, cattr|
90
+ # data_fields = db_schema.map do |col, cattr|
91
91
  # cattr[:primary_key] ? nil : col
92
- #end.select { |col| col }
92
+ # end.select { |col| col }
93
93
  enty.set_fields(hash, @data_fields, missing: :skip)
94
94
  enty.save
95
95
  enty
@@ -88,8 +88,8 @@ module OData
88
88
  class Filter
89
89
  attr_reader :assoc
90
90
  attr_reader :assocs
91
- QUOTED_RGX = /'((?:[^']|(?:\'{2}))*)'/.freeze
92
- NOTQ_RGX = /((?:[^'\s\)])*)/.freeze
91
+ QUO = /'((?:[^']|(?:\'{2}))*)'/.freeze
92
+ NOTQ = /((?:[^'\s\)])*)/.freeze
93
93
 
94
94
  def initialize(matx)
95
95
  @matx = matx
@@ -139,11 +139,10 @@ module OData
139
139
  class EqualFilter < Filter
140
140
  include FilterWithRuby
141
141
  include FilterWithAssoc
142
- EQUAL_KW_RGX = '[eE][qQ]|[LlgGNn][eETt]'.freeze
143
- RIGHT_RGX = %q{'[^']+'|[^'\)\s]+}.freeze
142
+ EQL = '[eE][qQ]|[LlgGNn][eETt]'.freeze
144
143
 
145
144
  def self.qualified_regexp(paths_rx)
146
- /\s*(#{paths_rx})\s+(#{EQUAL_KW_RGX})\s+(?:#{QUOTED_RGX}|#{NOTQ_RGX})\s*/
145
+ /\s*(#{paths_rx})\s+(#{EQL})\s+(?:#{QUO}|#{NOTQ})\s*/
147
146
  end
148
147
 
149
148
  def initialize(matx)
@@ -208,6 +207,82 @@ module OData
208
207
  end
209
208
  end
210
209
 
210
+ # equality expressions with functions ... length(name) eq 2
211
+ class FuncEqualFilter < EqualFilter
212
+ FUNC = 'length|trim|tolower|toupper'.freeze
213
+
214
+ attr_reader :funcname
215
+
216
+ def self.qualified_regexp(pathsrx)
217
+ /\s*(#{FUNC})\((#{pathsrx})\)\s+(#{EQL})\s+(?:#{QUO}|#{NOTQ})\s*/
218
+ end
219
+
220
+ def initialize(matx)
221
+ super(matx)
222
+ @funcname = matx[1]
223
+ self.fn = matx[2]
224
+ get_assoc if @fn
225
+
226
+ @val = matx[4] ? matx[4].gsub("''", "'") : matx[5]
227
+
228
+ @op = matx[3].downcase
229
+ end
230
+
231
+ # ugly hack... but working
232
+ def gen_sql(dtcx)
233
+ # handle ambiguous column names (joins) by qualifiying the names
234
+ # seqfn = @assoc ? Sequel[@assoc.to_sym][@fn.to_sym] : @fn.to_sym
235
+ # handle ambiguous column names (joins) by qualifiying the names
236
+ # for the main table as well
237
+ # TODO: find a better way to differentiate between the two types
238
+ # of dtcx : Model or Dataset
239
+
240
+ # DONE: same handling for order and substring and everywhere where
241
+ # qualified fieldnames could be needed (uses attrib path regexp)
242
+ x = apply_to_dataset(dtcx)
243
+ # ugly ugly hack :-(
244
+ y = x.unordered.sql.sub(x.unfiltered.unordered.sql + ' WHERE', '')
245
+ y
246
+ end
247
+
248
+ def apply_to_dataset(dtcx)
249
+ seqfn = get_qualified_fn(dtcx)
250
+ # using @val in Procs below does not work reliably. Using a local var
251
+ # seems to work better
252
+ argval = @val
253
+ seqfunc = case @funcname
254
+ when 'length'
255
+ argval = Sequel.lit(@val)
256
+ Sequel.char_length(seqfn)
257
+ when 'trim'
258
+ Sequel.trim(seqfn)
259
+ when 'toupper'
260
+ Sequel.function(:upper, seqfn)
261
+ when 'tolower'
262
+ Sequel.function(:lower, seqfn)
263
+ else
264
+ raise OData::FilterParseError
265
+ end
266
+
267
+ case @op
268
+ when 'eq'
269
+ dtcx.where(seqfunc => argval)
270
+ when 'ne'
271
+ dtcx.exclude(seqfunc => argval)
272
+ when 'le'
273
+ dtcx.where { seqfunc <= argval }
274
+ when 'ge'
275
+ dtcx.where { seqfunc >= argval }
276
+ when 'lt'
277
+ dtcx.where { seqfunc < argval }
278
+ when 'gt'
279
+ dtcx.where { seqfunc > argval }
280
+ else
281
+ raise OData::FilterParseError
282
+ end
283
+ end
284
+ end
285
+
211
286
  # keyword based
212
287
  class FilterWithKeyword < Filter
213
288
  attr_reader :keyword
@@ -221,8 +296,9 @@ module OData
221
296
  def self.qualified_regexp(paths_rx)
222
297
  # Note: QUOTED_RGX captures the string content without
223
298
  # start and end quote (no need to un-quote)
224
- # NOTQ_RGX captures an not-quoted value argument (like for numbers)
225
- /(substringof)\((?:#{QUOTED_RGX}|#{NOTQ_RGX}),\s*(#{paths_rx})\)/
299
+ # NOTQ_RGX captures an not-quoted value argument
300
+ # (like for numbers)
301
+ /(substringof)\((?:#{QUO}|#{NOTQ}),\s*(#{paths_rx})\)/
226
302
  end
227
303
 
228
304
  def initialize(matx)
@@ -237,7 +313,7 @@ module OData
237
313
 
238
314
  def whcl(dtcx)
239
315
  # handle ambiguous column names (joins) by qualifiying the names
240
- # seqfn = @assoc ? Sequel[@assoc.to_sym][@fn.to_sym] : @fn.to_sym
316
+ # seqfn = @assoc ? Sequel[@assoc.to_sym][@fn.to_sym] : @fn.to_sym
241
317
  seqfn = get_qualified_fn(dtcx)
242
318
  Sequel.like(seqfn, "%#{@val}%")
243
319
  end
@@ -289,7 +365,7 @@ module OData
289
365
  # example : startswith(year, 190')
290
366
 
291
367
  def self.qualified_regexp(paths)
292
- /(endswith|startswith)\((#{paths}),\s*(?:#{QUOTED_RGX}|#{NOTQ_RGX})\s*\)/
368
+ /(endswith|startswith)\((#{paths}),\s*(?:#{QUO}|#{NOTQ})\s*\)/
293
369
  end
294
370
 
295
371
  def initialize(matx)
@@ -358,6 +434,7 @@ module OData
358
434
  class ComplexFilter < Filter
359
435
  # list of active Subfilter classes. The ordering is important
360
436
  SUBFILTERS = [EqualFilter,
437
+ FuncEqualFilter,
361
438
  SubstringOfFilterSig1,
362
439
  StartOrEndsWithFilter].freeze
363
440
  # for counting number of AND OR's
@@ -375,6 +452,7 @@ module OData
375
452
  else
376
453
  dtset.model.attrib_paths_url_regexp.dup
377
454
  end
455
+
378
456
  @active_subfs = []
379
457
  @osql = filterstr.dup
380
458
  @dt = dtset
data/lib/odata/entity.rb CHANGED
@@ -1,6 +1,3 @@
1
- #!/usr/bin/env ruby
2
- # !/usr/bin/env ruby
3
-
4
1
  require 'json'
5
2
  require 'pp'
6
3
  require 'rexml/document'
@@ -47,7 +44,8 @@ module OData
47
44
 
48
45
  def transition_attribute(match_result)
49
46
  attrib = match_result[1]
50
- [values[attrib.to_sym], :run]
47
+ # [values[attrib.to_sym], :run]
48
+ [OData::Attribute.new(self, attrib), :run]
51
49
  end
52
50
 
53
51
  def transition_nav_collection(match_result)
@@ -156,7 +154,6 @@ module OData
156
154
  end
157
155
 
158
156
  def odata_patch(req)
159
-
160
157
  data = JSON.parse(req.body.read)
161
158
  @uribase = req.uribase
162
159
 
data/lib/odata/walker.rb CHANGED
@@ -22,6 +22,9 @@ module OData
22
22
  # is $count requested?
23
23
  attr_accessor :do_count
24
24
 
25
+ # is $value requested?
26
+ attr_reader :raw_value
27
+
25
28
  def initialize(service, path)
26
29
  path = URI.decode_www_form_component(path)
27
30
  @context = service
@@ -78,11 +81,15 @@ module OData
78
81
  @contexts << @context
79
82
  @path_remain = @tr_next.path_remain
80
83
  @path_done << @tr_next.path_done
81
- # little hack
84
+ # little hack's
82
85
  if @status == :end_with_count
83
86
  @do_count = true
84
87
  @status == :end
85
88
  end
89
+ if @status == :end_with_value
90
+ @raw_value = true
91
+ @status == :end
92
+ end
86
93
  else
87
94
  @context = nil
88
95
  @status = :error
data/lib/safrano.rb CHANGED
@@ -4,6 +4,7 @@ require 'json'
4
4
  require 'rexml/document'
5
5
  require 'safrano_core.rb'
6
6
  require 'odata/entity.rb'
7
+ require 'odata/attribute.rb'
7
8
  require 'odata/collection.rb'
8
9
  require 'service.rb'
9
10
  require 'odata/walker.rb'
data/lib/safrano_core.rb CHANGED
@@ -83,31 +83,7 @@ module Safrano
83
83
  trans: 'transition_batch')
84
84
  TransitionCount = Transition.new('(\A\/\$count)(.*)\z',
85
85
  trans: 'transition_count')
86
+ TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
87
+ trans: 'transition_value')
86
88
  attr_accessor :allowed_transitions
87
89
  end
88
-
89
- # for $count and number type attributes
90
- # class Fixnum deprecated as of ruby 2.4+
91
- class Integer
92
- def odata_get(_req)
93
- [200, { 'Content-Type' => 'text/plain;charset=utf-8' }, to_s]
94
- end
95
- end
96
-
97
- # for string type attributes
98
- class String
99
- def odata_get(_req)
100
- [200, { 'Content-Type' => 'text/plain;charset=utf-8' }, to_s]
101
- end
102
- end
103
-
104
- # Final attribute value....
105
- class Object
106
- def allowed_transitions
107
- [Safrano::TransitionEnd]
108
- end
109
-
110
- def transition_end(_match_result)
111
- [nil, :end]
112
- end
113
- 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.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - D.M.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-15 00:00:00.000000000 Z
11
+ date: 2019-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -73,6 +73,7 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - lib/odata/attribute.rb
76
77
  - lib/odata/batch.rb
77
78
  - lib/odata/collection.rb
78
79
  - lib/odata/collection_filter.rb
@@ -110,5 +111,5 @@ requirements: []
110
111
  rubygems_version: 3.0.3
111
112
  signing_key:
112
113
  specification_version: 4
113
- summary: Safrano
114
+ summary: Safrano is a ruby (Sequel + Rack) OData provider
114
115
  test_files: []