safrano 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/multipart.rb +92 -91
- data/lib/odata/attribute.rb +15 -6
- data/lib/odata/collection.rb +109 -106
- data/lib/odata/collection_filter.rb +14 -485
- data/lib/odata/collection_order.rb +15 -22
- data/lib/odata/entity.rb +31 -41
- data/lib/odata/filter/error.rb +53 -0
- data/lib/odata/filter/parse.rb +171 -0
- data/lib/odata/filter/sequel.rb +208 -0
- data/lib/odata/filter/token.rb +59 -0
- data/lib/odata/filter/tree.rb +368 -0
- data/lib/odata/relations.rb +36 -0
- data/lib/odata/url_parameters.rb +58 -0
- data/lib/odata/walker.rb +55 -42
- data/lib/request.rb +1 -3
- data/lib/safrano.rb +17 -0
- data/lib/safrano_core.rb +30 -7
- data/lib/sequel/plugins/join_by_paths.rb +239 -0
- data/lib/sequel_join_by_paths.rb +5 -0
- data/lib/service.rb +84 -112
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffa3b9e1f282380dcdd32162bca490faa4749e05a1db11d65dc2b0c25c5ee76c
|
4
|
+
data.tar.gz: eae063a3541b8eec09bea8d98adf04dc4ad6bed16bdfe6dc3c06fd2df7bb0baf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c910a0f9677d820f85ae00926b3e7f4f5b29126aa86f5386868d8681e3b9808d44d3f31a6adddac861d98ed1d6969a955691c75262c7a2f892110a4dc948797
|
7
|
+
data.tar.gz: 54b66c00dc7456739457cc58d7a1a68aff8f9a35dccfda3f04b7b83ce12ac7a7e9525d95b7a620cb0e779467e67b26ae3342675477e5c7a49fdb8063bad12183
|
data/lib/multipart.rb
CHANGED
@@ -24,50 +24,64 @@ module MIME
|
|
24
24
|
@lines << line
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
@
|
29
|
-
|
27
|
+
def parse_first_part(line)
|
28
|
+
if @target.parser.next_part(line)
|
29
|
+
@state = :next_part
|
30
|
+
# this is for when there is only one part
|
31
|
+
# (first part is the last one)
|
32
|
+
elsif @target.parser.last_part(line)
|
33
|
+
@state = :end
|
34
|
+
else
|
35
|
+
@target.parser.addline(line)
|
36
|
+
end
|
37
|
+
end
|
30
38
|
|
31
|
-
|
32
|
-
|
33
|
-
when :h
|
34
|
-
if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
|
35
|
-
@target_hd[hmd[1].downcase] = hmd[2].strip
|
39
|
+
def parse_next_part(line)
|
40
|
+
if @target.parser.next_part(line)
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
42
|
+
elsif @target.parser.last_part(line)
|
43
|
+
@state = :end
|
44
|
+
else
|
45
|
+
@target.parser.addline(line)
|
46
|
+
end
|
47
|
+
end
|
40
48
|
|
41
|
-
|
49
|
+
def parse_head(line)
|
50
|
+
if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
|
51
|
+
@target_hd[hmd[1].downcase] = hmd[2].strip
|
42
52
|
|
43
|
-
|
44
|
-
|
53
|
+
elsif /^#{CRLF}$/ =~ line
|
54
|
+
@target_ct = @target_hd['content-type'] || 'text/plain'
|
55
|
+
@state = new_content
|
45
56
|
|
46
|
-
|
57
|
+
end
|
58
|
+
end
|
47
59
|
|
48
|
-
|
60
|
+
def parse_line(line)
|
61
|
+
case @state
|
62
|
+
when :h
|
63
|
+
parse_head(line)
|
49
64
|
|
50
|
-
|
51
|
-
|
52
|
-
@state = :next_part
|
53
|
-
# this is for when there is only one part
|
54
|
-
# (first part is the last one)
|
55
|
-
elsif @target.parser.last_part(line)
|
56
|
-
@state = :end
|
57
|
-
else
|
58
|
-
@target.parser.addline(line)
|
59
|
-
end
|
65
|
+
when :b
|
66
|
+
@target.parser.addline(line)
|
60
67
|
|
61
|
-
|
68
|
+
when :bmp
|
69
|
+
@state = :first_part if @target.parser.first_part(line)
|
62
70
|
|
63
|
-
|
71
|
+
when :first_part
|
72
|
+
parse_first_part(line)
|
64
73
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
74
|
+
when :next_part
|
75
|
+
parse_next_part(line)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse(level: 0)
|
80
|
+
@level = level
|
81
|
+
return unless @lines
|
82
|
+
|
83
|
+
@lines.each do |line|
|
84
|
+
parse_line(line)
|
71
85
|
end
|
72
86
|
# Warning: recursive here
|
73
87
|
@target.parser.parse(level: level)
|
@@ -183,6 +197,17 @@ module MIME
|
|
183
197
|
@lines << line
|
184
198
|
end
|
185
199
|
|
200
|
+
def parse_head(line)
|
201
|
+
if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
|
202
|
+
@target.hd[hmd[1].downcase] = hmd[2].strip
|
203
|
+
elsif /^#{CRLF}$/ =~ line
|
204
|
+
@state = :b
|
205
|
+
else
|
206
|
+
@target.content << line
|
207
|
+
@state = :b
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
186
211
|
def parse(level: 0)
|
187
212
|
return unless @lines
|
188
213
|
|
@@ -190,14 +215,7 @@ module MIME
|
|
190
215
|
@lines.each do |line|
|
191
216
|
case @state
|
192
217
|
when :h
|
193
|
-
|
194
|
-
@target.hd[hmd[1].downcase] = hmd[2].strip
|
195
|
-
elsif /^#{CRLF}$/ =~ line
|
196
|
-
@state = :b
|
197
|
-
else
|
198
|
-
@target.content << line
|
199
|
-
@state = :b
|
200
|
-
end
|
218
|
+
parse_head(line)
|
201
219
|
when :b
|
202
220
|
@target.content << line
|
203
221
|
end
|
@@ -361,9 +379,7 @@ module MIME
|
|
361
379
|
end
|
362
380
|
end
|
363
381
|
else
|
364
|
-
@response.content = @content.map { |
|
365
|
-
part.get_response(batcha)
|
366
|
-
}
|
382
|
+
@response.content = @content.map { |prt| prt.get_response(batcha) }
|
367
383
|
end
|
368
384
|
@response
|
369
385
|
end
|
@@ -448,7 +464,8 @@ module MIME
|
|
448
464
|
|
449
465
|
# For application/http . Content is either a Request or a Response
|
450
466
|
class Http < Media
|
451
|
-
|
467
|
+
HTTP_MTHS_RGX = 'POST|GET|PUT|MERGE|PATCH'.freeze
|
468
|
+
HTTP_R_RGX = %r{^(#{HTTP_MTHS_RGX})\s+(\S*)\s?(HTTP/1\.1)\s*$}.freeze
|
452
469
|
HEADER_RGX = %r{^([a-zA-Z\-]+):\s*([0-9a-zA-Z\-\/,\s]+;?\S*)\s*$}.freeze
|
453
470
|
HTTP_RESP_RGX = %r{^HTTP/1\.1\s(\d+)\s}.freeze
|
454
471
|
|
@@ -465,34 +482,42 @@ module MIME
|
|
465
482
|
@lines << line
|
466
483
|
end
|
467
484
|
|
485
|
+
def parse_http(line)
|
486
|
+
if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
|
487
|
+
@target.hd[hmd[1].downcase] = hmd[2].strip
|
488
|
+
elsif (mdht = HTTP_R_RGX.match(line))
|
489
|
+
@state = :hd
|
490
|
+
@target.content = MIME::Content::Application::HttpReq.new
|
491
|
+
@target.content.http_method = mdht[1]
|
492
|
+
@target.content.uri = mdht[2]
|
493
|
+
# HTTP 1.1 status line --> HttpResp.new
|
494
|
+
elsif (mdht = HTTP_RESP_RGX.match(line))
|
495
|
+
@state = :hd
|
496
|
+
@target.content = MIME::Content::Application::HttpResp.new
|
497
|
+
@target.content.status = mdht[1]
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
def parse_head(line)
|
502
|
+
if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
|
503
|
+
@target.content.hd[hmd[1].downcase] = hmd[2].strip
|
504
|
+
elsif /^#{CRLF}$/ =~ line
|
505
|
+
@state = :b
|
506
|
+
else
|
507
|
+
@body_lines << line
|
508
|
+
@state = :b
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
468
512
|
def parse(level: 0)
|
469
513
|
return unless @lines
|
470
514
|
|
471
515
|
@lines.each do |line|
|
472
516
|
case @state
|
473
517
|
when :http
|
474
|
-
|
475
|
-
@target.hd[hmd[1].downcase] = hmd[2].strip
|
476
|
-
elsif (mdht = HTTP_R_RGX.match(line))
|
477
|
-
@state = :hd
|
478
|
-
@target.content = MIME::Content::Application::HttpReq.new
|
479
|
-
@target.content.http_method = mdht[1]
|
480
|
-
@target.content.uri = mdht[2]
|
481
|
-
# HTTP 1.1 status line --> HttpResp.new
|
482
|
-
elsif (mdht = HTTP_RESP_RGX.match(line))
|
483
|
-
@state = :hd
|
484
|
-
@target.content = MIME::Content::Application::HttpResp.new
|
485
|
-
@target.content.status = mdht[1]
|
486
|
-
end
|
518
|
+
parse_http(line)
|
487
519
|
when :hd
|
488
|
-
|
489
|
-
@target.content.hd[hmd[1].downcase] = hmd[2].strip
|
490
|
-
elsif /^#{CRLF}$/ =~ line
|
491
|
-
@state = :b
|
492
|
-
else
|
493
|
-
@body_lines << line
|
494
|
-
@state = :b
|
495
|
-
end
|
520
|
+
parse_head(line)
|
496
521
|
when :b
|
497
522
|
@body_lines << line
|
498
523
|
end
|
@@ -538,27 +563,3 @@ module MIME
|
|
538
563
|
end
|
539
564
|
end
|
540
565
|
|
541
|
-
# @mimep = MIME::Media::Parser.new
|
542
|
-
|
543
|
-
# @inpstr = File.open('../test/multipart/odata_changeset_1_body.txt','r')
|
544
|
-
# @boundary = 'batch_48f8-3aea-2f04'
|
545
|
-
|
546
|
-
# @inpstr = File.open('../test/multipart/odata_changeset_body.txt','r')
|
547
|
-
# @boundary = 'batch_36522ad7-fc75-4b56-8c71-56071383e77b'
|
548
|
-
|
549
|
-
# @mime = @mimep.hook_multipart('multipart/mixed', @boundary)
|
550
|
-
# @mime = @mimep.parse_str(@inpstr)
|
551
|
-
|
552
|
-
# require 'pry'
|
553
|
-
# binding.pry
|
554
|
-
|
555
|
-
# @inpstr.close
|
556
|
-
|
557
|
-
# inp = File.open(ARGV[0], 'r') do |f|
|
558
|
-
# f.readlines(CRLF)
|
559
|
-
# end
|
560
|
-
# x = MIME::Media::Parser.new
|
561
|
-
# y = x.parsein(inp)
|
562
|
-
# pp y
|
563
|
-
# puts '-----------------------------------------------'
|
564
|
-
# puts y.unparse
|
data/lib/odata/attribute.rb
CHANGED
@@ -13,16 +13,25 @@ module OData
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def value
|
16
|
-
|
16
|
+
# WARNING; this code is duplicated in entity.rb
|
17
|
+
# (and the inverted transformation is in test/client.rb)
|
18
|
+
# will require a more systematic solution some day
|
19
|
+
# WARNING 2... this require more work to handle the timezones topci
|
20
|
+
# currently it is just set to make some minimal testcase work
|
21
|
+
case (v = @entity.values[@name.to_sym])
|
22
|
+
when Time
|
23
|
+
# try to get back the database time zone and value
|
24
|
+
(v + v.gmt_offset).utc.to_datetime
|
25
|
+
else
|
26
|
+
v
|
27
|
+
end
|
17
28
|
end
|
18
29
|
|
19
30
|
def odata_get(req)
|
20
31
|
if req.walker.raw_value
|
21
|
-
[200,
|
22
|
-
|
23
|
-
|
24
|
-
[200, { 'Content-Type' => 'application/json;charset=utf-8' },
|
25
|
-
to_odata_json(service: req.service)]
|
32
|
+
[200, CT_TEXT, value.to_s]
|
33
|
+
elsif req.accept?(APPJSON)
|
34
|
+
[200, CT_JSON, to_odata_json(service: req.service)]
|
26
35
|
else # TODO: other formats
|
27
36
|
406
|
28
37
|
end
|
data/lib/odata/collection.rb
CHANGED
@@ -8,6 +8,7 @@ require 'safrano_core.rb'
|
|
8
8
|
require 'odata/error.rb'
|
9
9
|
require 'odata/collection_filter.rb'
|
10
10
|
require 'odata/collection_order.rb'
|
11
|
+
require 'odata/url_parameters.rb'
|
11
12
|
|
12
13
|
# small helper method
|
13
14
|
# http://stackoverflow.com/
|
@@ -40,6 +41,8 @@ module OData
|
|
40
41
|
# class methods. They Make heavy use of Sequel::Model functionality
|
41
42
|
# we will add this to our Model classes with "extend" --> self is the Class
|
42
43
|
module EntityClassBase
|
44
|
+
SINGLE_PK_URL_REGEXP = /\A\('?([\w\s]+)'?\)(.*)/.freeze
|
45
|
+
|
43
46
|
attr_reader :nav_collection_url_regexp
|
44
47
|
attr_reader :nav_entity_url_regexp
|
45
48
|
attr_reader :entity_id_url_regexp
|
@@ -60,6 +63,10 @@ module OData
|
|
60
63
|
# url params
|
61
64
|
attr_reader :params
|
62
65
|
|
66
|
+
# url parameters processing object (mostly covert to sequel exprs).
|
67
|
+
# exposed for testing only
|
68
|
+
attr_reader :uparms
|
69
|
+
|
63
70
|
# initialising block of code to be executed at end of
|
64
71
|
# ServerApp.publish_service after all model classes have been registered
|
65
72
|
# (without the associations/relationships)
|
@@ -94,42 +101,6 @@ module OData
|
|
94
101
|
enty
|
95
102
|
end
|
96
103
|
|
97
|
-
def odata_get_apply_filter_w_sequel
|
98
|
-
return unless @params['$filter']
|
99
|
-
|
100
|
-
# Sequel requires a dataset to build some sql expressions, so we
|
101
|
-
# need to pass one todo: use a dummy one ?
|
102
|
-
# @fi = Filter.new_by_parse(@params['$filter'], @cx)
|
103
|
-
return if @fi.parse_error?
|
104
|
-
|
105
|
-
@cx = @fi.apply_to_dataset(@cx)
|
106
|
-
@right_assocs.merge @fi.assocs
|
107
|
-
end
|
108
|
-
|
109
|
-
def odata_get_apply_order_w_sequel
|
110
|
-
return unless @params['$orderby']
|
111
|
-
|
112
|
-
fo = Order.new_by_parse(@params['$orderby'], @cx)
|
113
|
-
@cx = fo.apply_to_dataset(@cx)
|
114
|
-
@left_assocs.merge fo.assocs
|
115
|
-
end
|
116
|
-
|
117
|
-
def odata_get_do_assoc_joins_w_sequel
|
118
|
-
return if @right_assocs.empty? && @left_assocs.empty?
|
119
|
-
|
120
|
-
# Preparation: ensure that the left and right assocs sets are disjoint
|
121
|
-
# by keeping the duplicates in the left assoc set and removing
|
122
|
-
# them from the right assocs set. We can use Set difference...
|
123
|
-
@right_assocs -= @left_assocs
|
124
|
-
|
125
|
-
# this is for the filtering. Normally we can use inner join
|
126
|
-
@right_assocs.each { |aj| @cx = @cx.association_join(aj) }
|
127
|
-
# this is for the ordering.
|
128
|
-
# we need left join otherwise we could miss records sometimes
|
129
|
-
@left_assocs.each { |aj| @cx = @cx.association_left_join(aj) }
|
130
|
-
@cx = @cx.select_all(entity_set_name.to_sym)
|
131
|
-
end
|
132
|
-
|
133
104
|
def odata_get_inlinecount_w_sequel
|
134
105
|
return unless (icp = @params['$inlinecount'])
|
135
106
|
|
@@ -142,25 +113,29 @@ module OData
|
|
142
113
|
end
|
143
114
|
end
|
144
115
|
|
145
|
-
def odata_get_apply_params_w_sequel
|
146
|
-
@left_assocs = Set.new
|
147
|
-
@right_assocs = Set.new
|
148
|
-
odata_get_apply_filter_w_sequel
|
149
|
-
odata_get_apply_order_w_sequel
|
150
|
-
odata_get_do_assoc_joins_w_sequel
|
151
|
-
odata_get_inlinecount_w_sequel
|
152
|
-
|
153
|
-
@cx = @cx.offset(@params['$skip']) if @params['$skip']
|
154
|
-
@cx = @cx.limit(@params['$top']) if @params['$top']
|
155
|
-
@cx
|
156
|
-
end
|
157
|
-
|
158
116
|
def navigated_coll
|
159
117
|
false
|
160
118
|
end
|
161
119
|
|
120
|
+
def attrib_path_valid?(path)
|
121
|
+
@attribute_path_list.include? path
|
122
|
+
end
|
123
|
+
|
162
124
|
def odata_get_apply_params
|
163
|
-
|
125
|
+
begin
|
126
|
+
@cx = @uparms.apply_to_dataset(@cx)
|
127
|
+
rescue OData::Filter::Parser::ErrorWrongColumnName
|
128
|
+
@error = BadRequestFilterParseError
|
129
|
+
return
|
130
|
+
rescue OData::Filter::Parser::ErrorFunctionArgumentType
|
131
|
+
@error = BadRequestFilterParseError
|
132
|
+
return
|
133
|
+
end
|
134
|
+
odata_get_inlinecount_w_sequel
|
135
|
+
|
136
|
+
@cx = @cx.offset(@params['$skip']) if @params['$skip']
|
137
|
+
@cx = @cx.limit(@params['$top']) if @params['$top']
|
138
|
+
@cx
|
164
139
|
end
|
165
140
|
|
166
141
|
# url params validation methods.
|
@@ -192,33 +167,11 @@ module OData
|
|
192
167
|
end
|
193
168
|
|
194
169
|
def check_u_p_filter
|
195
|
-
|
196
|
-
|
197
|
-
# Sequel requires a dataset to build some sql expressions, so we
|
198
|
-
# need to pass one todo: use a dummy one ?
|
199
|
-
@fi = Filter.new_by_parse(@params['$filter'], @cx)
|
200
|
-
return BadRequestFilterParseError if @fi.parse_error?
|
201
|
-
|
202
|
-
# nil is the expected return for no errors
|
203
|
-
nil
|
170
|
+
@uparms.check_filter
|
204
171
|
end
|
205
172
|
|
206
173
|
def check_u_p_orderby
|
207
|
-
|
208
|
-
# at least partly
|
209
|
-
return unless @params['$orderby']
|
210
|
-
|
211
|
-
pordlist = @params['$orderby'].dup
|
212
|
-
pordlist.split(',').each do |pord|
|
213
|
-
pord.strip!
|
214
|
-
qualfn, dir = pord.split(/\s/)
|
215
|
-
qualfn.strip!
|
216
|
-
dir.strip! if dir
|
217
|
-
return BadRequestError unless @attribute_path_list.include? qualfn
|
218
|
-
return BadRequestError unless [nil, 'asc', 'desc'].include? dir
|
219
|
-
end
|
220
|
-
# nil is the expected return for no errors
|
221
|
-
nil
|
174
|
+
@uparms.check_order
|
222
175
|
end
|
223
176
|
|
224
177
|
def build_attribute_path_list
|
@@ -248,14 +201,38 @@ module OData
|
|
248
201
|
def check_url_params
|
249
202
|
return nil unless @params
|
250
203
|
|
251
|
-
check_u_p_top || check_u_p_skip || check_u_p_orderby ||
|
204
|
+
check_u_p_top || check_u_p_skip || check_u_p_orderby ||
|
205
|
+
check_u_p_filter || check_u_p_inlinecount
|
252
206
|
end
|
253
207
|
|
254
208
|
def initialize_dataset
|
255
|
-
# initialize dataset
|
256
209
|
@cx = self
|
257
210
|
@ax = nil
|
258
211
|
@cx = navigated_dataset if @cx.navigated_coll
|
212
|
+
@model = if @cx.respond_to? :model
|
213
|
+
@cx.model
|
214
|
+
else
|
215
|
+
@cx
|
216
|
+
end
|
217
|
+
@jh = @model.join_by_paths_helper
|
218
|
+
@uparms = UrlParameters.new(@jh, @params)
|
219
|
+
end
|
220
|
+
|
221
|
+
# finally return the requested output according to format, options etc
|
222
|
+
def odata_get_output(req)
|
223
|
+
return @error.odata_get(req) if @error
|
224
|
+
|
225
|
+
if req.walker.do_count
|
226
|
+
[200, CT_TEXT, @cx.count.to_s]
|
227
|
+
elsif req.accept?(APPJSON)
|
228
|
+
if req.walker.do_links
|
229
|
+
[200, CT_JSON, to_odata_links_json(service: req.service)]
|
230
|
+
else
|
231
|
+
[200, CT_JSON, to_odata_json(service: req.service)]
|
232
|
+
end
|
233
|
+
else # TODO: other formats
|
234
|
+
406
|
235
|
+
end
|
259
236
|
end
|
260
237
|
|
261
238
|
# on model class level we return the collection
|
@@ -264,24 +241,50 @@ module OData
|
|
264
241
|
@uribase = req.uribase
|
265
242
|
initialize_dataset
|
266
243
|
|
267
|
-
if (
|
268
|
-
|
244
|
+
if (@error = check_url_params)
|
245
|
+
@error.odata_get(req)
|
269
246
|
else
|
270
247
|
odata_get_apply_params
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
406
|
248
|
+
odata_get_output(req)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# add metadata xml to the passed REXML schema object
|
253
|
+
def add_metadata_rexml(schema)
|
254
|
+
enty = schema.add_element('EntityType', 'Name' => to_s)
|
255
|
+
# with their properties
|
256
|
+
db_schema.each do |pnam, prop|
|
257
|
+
if prop[:primary_key] == true
|
258
|
+
enty.add_element('Key').add_element('PropertyRef',
|
259
|
+
'Name' => pnam.to_s)
|
284
260
|
end
|
261
|
+
attrs = { 'Name' => pnam.to_s,
|
262
|
+
'Type' => OData.get_edm_type(db_type: prop[:db_type]) }
|
263
|
+
attrs['Nullable'] = 'false' if prop[:allow_null] == false
|
264
|
+
enty.add_element('Property', attrs)
|
265
|
+
end
|
266
|
+
enty
|
267
|
+
end
|
268
|
+
|
269
|
+
# metadata REXML data for a single Nav attribute
|
270
|
+
def metadata_nav_rexml_attribs(assoc, cmap, relman, xnamespace)
|
271
|
+
from = type_name
|
272
|
+
to = cmap[assoc.to_s].type_name
|
273
|
+
relman.get_metadata_xml_attribs(from,
|
274
|
+
to,
|
275
|
+
association_reflection(assoc)[:type],
|
276
|
+
xnamespace)
|
277
|
+
end
|
278
|
+
|
279
|
+
# and their Nav attributes == Sequel Model association
|
280
|
+
def add_metadata_navs_rexml(schema_enty, cmap, relman, xnamespace)
|
281
|
+
associations.each do |assoc|
|
282
|
+
# associated objects need to be in the map...
|
283
|
+
next unless cmap[assoc.to_s]
|
284
|
+
|
285
|
+
nattrs = metadata_nav_rexml_attribs(assoc, cmap, relman, xnamespace)
|
286
|
+
|
287
|
+
schema_enty.add_element('NavigationProperty', nattrs)
|
285
288
|
end
|
286
289
|
end
|
287
290
|
|
@@ -313,15 +316,13 @@ module OData
|
|
313
316
|
return [422, {}, ['Invalid attribute name: ', invalid.to_s]]
|
314
317
|
end
|
315
318
|
|
316
|
-
if req.accept?(
|
319
|
+
if req.accept?(APPJSON)
|
317
320
|
|
318
321
|
new_entity = new_from_hson_h(data, in_changeset: req.in_changeset)
|
319
322
|
req.register_content_id_ref(new_entity)
|
320
323
|
new_entity.copy_request_infos(req)
|
321
324
|
|
322
|
-
[201,
|
323
|
-
{ 'Content-Type' => 'application/json;charset=utf-8' },
|
324
|
-
new_entity.to_odata_post_json(service: req.service)]
|
325
|
+
[201, CT_JSON, new_entity.to_odata_post_json(service: req.service)]
|
325
326
|
else # TODO: other formats
|
326
327
|
415
|
327
328
|
end
|
@@ -382,7 +383,7 @@ module OData
|
|
382
383
|
@entity_id_url_regexp = /\A\(\s*(#{iuk.join(',\s*')})\s*\)(.*)/
|
383
384
|
else
|
384
385
|
@pk_names = [primary_key.to_s]
|
385
|
-
@entity_id_url_regexp =
|
386
|
+
@entity_id_url_regexp = SINGLE_PK_URL_REGEXP
|
386
387
|
end
|
387
388
|
end
|
388
389
|
|
@@ -396,12 +397,12 @@ module OData
|
|
396
397
|
data.keys.map(&:to_sym).find { |ksym| !(@columns.include? ksym) }
|
397
398
|
end
|
398
399
|
|
399
|
-
|
400
|
-
|
401
|
-
def
|
400
|
+
## A regexp matching all allowed attributes of the Entity
|
401
|
+
## (eg ID|name|size etc... ) at start position and returning the rest
|
402
|
+
def transition_attribute_regexp
|
402
403
|
# db_schema.map { |sch| sch[0] }.join('|')
|
403
404
|
# @columns is from Sequel Model
|
404
|
-
@columns.join('|')
|
405
|
+
%r{\A/(#{@columns.join('|')})(.*)\z}
|
405
406
|
end
|
406
407
|
|
407
408
|
# methods related to transitions to next state (cf. walker)
|
@@ -416,7 +417,7 @@ module OData
|
|
416
417
|
|
417
418
|
def transition_id(match_result)
|
418
419
|
if (id = match_result[1])
|
419
|
-
if (y =
|
420
|
+
if (y = find_by_odata_key(id))
|
420
421
|
[y, :run]
|
421
422
|
else
|
422
423
|
[nil, :error, ErrorNotFound]
|
@@ -439,12 +440,14 @@ module OData
|
|
439
440
|
module EntityClassMultiPK
|
440
441
|
include EntityClassBase
|
441
442
|
# id is for composite key, something like fx='aas',fy_w='0001'
|
442
|
-
def
|
443
|
-
#
|
443
|
+
def find_by_odata_key(mid)
|
444
|
+
# @iuk_rgx is (needs to be) built on start with
|
444
445
|
# collklass.prepare_pk
|
445
446
|
md = @iuk_rgx.match(mid).to_a
|
446
447
|
md.shift # remove first element which is the whole match
|
447
|
-
|
448
|
+
# amazingly this works as expected from an Entity.get_related(...) anonymous class
|
449
|
+
# without need to redefine primary_key_lookup (returns nil for valid but unrelated keys)
|
450
|
+
primary_key_lookup(md)
|
448
451
|
end
|
449
452
|
end
|
450
453
|
|
@@ -452,8 +455,8 @@ module OData
|
|
452
455
|
module EntityClassSinglePK
|
453
456
|
include EntityClassBase
|
454
457
|
# id is really just the value of single pk
|
455
|
-
def
|
456
|
-
|
458
|
+
def find_by_odata_key(id)
|
459
|
+
primary_key_lookup(id)
|
457
460
|
end
|
458
461
|
end
|
459
462
|
end
|