safrano 0.0.4 → 0.0.6

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: 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: []