safrano 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/odata/attribute.rb +1 -1
- data/lib/odata/batch.rb +3 -3
- data/lib/odata/collection.rb +55 -20
- data/lib/odata/collection_media.rb +123 -20
- data/lib/odata/common_logger.rb +37 -12
- data/lib/odata/entity.rb +85 -14
- data/lib/odata/error.rb +29 -10
- data/lib/odata/filter/error.rb +6 -0
- data/lib/odata/navigation_attribute.rb +49 -14
- data/lib/odata/relations.rb +2 -2
- data/lib/safrano.rb +22 -12
- data/lib/{safrano_core.rb → safrano/core.rb} +3 -2
- data/lib/{multipart.rb → safrano/multipart.rb} +0 -0
- data/lib/{odata_rack_builder.rb → safrano/odata_rack_builder.rb} +0 -0
- data/lib/{rack_app.rb → safrano/rack_app.rb} +1 -1
- data/lib/{request.rb → safrano/request.rb} +5 -4
- data/lib/{response.rb → safrano/response.rb} +0 -1
- data/lib/{sequel_join_by_paths.rb → safrano/sequel_join_by_paths.rb} +1 -1
- data/lib/{service.rb → safrano/service.rb} +32 -8
- data/lib/safrano/version.rb +3 -0
- metadata +26 -12
- data/lib/version.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a16c6b649ec630fbada504c11039b017b4055c7038c2aa126e7c6b55dd768ff
|
4
|
+
data.tar.gz: 03b387f824a5f2e4d53c3e16dd9b013ca8d3c308bc06e13cc0bf09bf54f4f3eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d40563e3e2ad2db813954318aaf5d0f3097f71d9210af9434524cc7e8e25ba81b37694e0fe5fb7d7f9c662679a3b5c8425d11e771d8ba0fe42162598d4f81f65
|
7
|
+
data.tar.gz: 8dc6cfea884a95c2841e0646d0051dcf4926c3fad85734978e8ee2bc98ea7dbae5fd3718cf6a3a8b49f66c2a3566c33c70c64fd8babde8c4c66a1cbe2eb46929
|
data/lib/odata/attribute.rb
CHANGED
data/lib/odata/batch.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative '../safrano/rack_app.rb'
|
2
|
+
require_relative '../safrano/core.rb'
|
3
3
|
require 'rack/body_proxy'
|
4
4
|
require_relative './common_logger.rb'
|
5
5
|
|
@@ -59,7 +59,7 @@ module OData
|
|
59
59
|
# TODO: test ?
|
60
60
|
if (logga = @full_req.env['safrano.logger_mw'])
|
61
61
|
logga.batch_log(env, status, header, began_at)
|
62
|
-
# TODO check why/if we need Rack::Utils::HeaderHash.new(header)
|
62
|
+
# TODO check why/if we need Rack::Utils::HeaderHash.new(header)
|
63
63
|
# and Rack::BodyProxy.new(body) ?
|
64
64
|
end
|
65
65
|
[status, header, body]
|
data/lib/odata/collection.rb
CHANGED
@@ -4,12 +4,12 @@
|
|
4
4
|
|
5
5
|
require 'json'
|
6
6
|
require 'rexml/document'
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
require_relative '../safrano/core.rb'
|
8
|
+
require_relative 'error.rb'
|
9
|
+
require_relative 'collection_filter.rb'
|
10
|
+
require_relative 'collection_order.rb'
|
11
|
+
require_relative 'url_parameters.rb'
|
12
|
+
require_relative 'collection_media.rb'
|
13
13
|
|
14
14
|
# small helper method
|
15
15
|
# http://stackoverflow.com/
|
@@ -53,6 +53,13 @@ module OData
|
|
53
53
|
attr_reader :nav_entity_attribs
|
54
54
|
attr_reader :data_fields
|
55
55
|
attr_reader :inlinecount
|
56
|
+
|
57
|
+
# Sequel associations pointing to this model. Sequel provides association
|
58
|
+
# reflection information on the "from" side. But in some cases
|
59
|
+
# we will need the reverted way
|
60
|
+
# finally not needed and not used yet
|
61
|
+
# attr_accessor :assocs_to
|
62
|
+
|
56
63
|
# set to parent entity in case the collection is a nav.collection
|
57
64
|
# nil otherwise
|
58
65
|
attr_reader :nav_parent
|
@@ -288,7 +295,11 @@ module OData
|
|
288
295
|
|
289
296
|
# add metadata xml to the passed REXML schema object
|
290
297
|
def add_metadata_rexml(schema)
|
291
|
-
enty =
|
298
|
+
enty = if @media_handler
|
299
|
+
schema.add_element('EntityType', 'Name' => to_s, 'HasStream' => 'true' )
|
300
|
+
else
|
301
|
+
schema.add_element('EntityType', 'Name' => to_s)
|
302
|
+
end
|
292
303
|
# with their properties
|
293
304
|
db_schema.each do |pnam, prop|
|
294
305
|
if prop[:primary_key] == true
|
@@ -304,25 +315,37 @@ module OData
|
|
304
315
|
end
|
305
316
|
|
306
317
|
# metadata REXML data for a single Nav attribute
|
307
|
-
def metadata_nav_rexml_attribs(assoc,
|
318
|
+
def metadata_nav_rexml_attribs(assoc, to_klass, relman, xnamespace)
|
308
319
|
from = type_name
|
309
|
-
to =
|
320
|
+
to = to_klass.type_name
|
310
321
|
relman.get_metadata_xml_attribs(from,
|
311
322
|
to,
|
312
|
-
association_reflection(assoc)[:type],
|
313
|
-
xnamespace
|
323
|
+
association_reflection(assoc.to_sym)[:type],
|
324
|
+
xnamespace,
|
325
|
+
assoc)
|
326
|
+
|
314
327
|
end
|
315
328
|
|
316
329
|
# and their Nav attributes == Sequel Model association
|
317
|
-
def add_metadata_navs_rexml(schema_enty,
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
schema_enty.add_element('NavigationProperty',
|
325
|
-
|
330
|
+
def add_metadata_navs_rexml(schema_enty, relman, xnamespace)
|
331
|
+
|
332
|
+
@nav_entity_attribs.each{|ne,klass|
|
333
|
+
nattr = metadata_nav_rexml_attribs(ne,
|
334
|
+
klass,
|
335
|
+
relman,
|
336
|
+
xnamespace)
|
337
|
+
schema_enty.add_element('NavigationProperty', nattr)
|
338
|
+
} if @nav_entity_attribs
|
339
|
+
|
340
|
+
|
341
|
+
@nav_collection_attribs.each{|nc,klass|
|
342
|
+
nattr = metadata_nav_rexml_attribs(nc,
|
343
|
+
klass,
|
344
|
+
relman,
|
345
|
+
xnamespace)
|
346
|
+
schema_enty.add_element('NavigationProperty', nattr)
|
347
|
+
} if @nav_collection_attribs
|
348
|
+
|
326
349
|
end
|
327
350
|
|
328
351
|
D = 'd'.freeze
|
@@ -351,6 +374,7 @@ module OData
|
|
351
374
|
# We need to base this on the Sequel rels, or extend them
|
352
375
|
def add_nav_prop_collection(assoc_symb, attr_name_str = nil)
|
353
376
|
@nav_collection_attribs = (@nav_collection_attribs || {})
|
377
|
+
# @assocs_to = ( @assocs_to || [] )
|
354
378
|
# DONE: Error handling. This requires that associations
|
355
379
|
# have been properly defined with Sequel before
|
356
380
|
assoc = all_association_reflections.find do |a|
|
@@ -368,6 +392,7 @@ module OData
|
|
368
392
|
|
369
393
|
def add_nav_prop_single(assoc_symb, attr_name_str = nil)
|
370
394
|
@nav_entity_attribs = (@nav_entity_attribs || {})
|
395
|
+
# @assocs_to = ( @assocs_to || [])
|
371
396
|
# DONE: Error handling. This requires that associations
|
372
397
|
# have been properly defined with Sequel before
|
373
398
|
assoc = all_association_reflections.find do |a|
|
@@ -381,12 +406,22 @@ module OData
|
|
381
406
|
lattr_name_str = (attr_name_str || assoc_symb.to_s)
|
382
407
|
@nav_entity_attribs[lattr_name_str] = attr_class
|
383
408
|
@nav_entity_url_regexp = @nav_entity_attribs.keys.join('|')
|
409
|
+
# attr_class.assocs_to = ( attr_class.assocs_to || [] )
|
410
|
+
# attr_class.assocs_to << assoc
|
384
411
|
end
|
385
412
|
|
386
413
|
# old names...
|
387
414
|
# alias_method :add_nav_prop_collection, :addNavCollectionAttrib
|
388
415
|
# alias_method :add_nav_prop_single, :addNavEntityAttrib
|
389
416
|
|
417
|
+
def finalize_publishing
|
418
|
+
# finalize media handler
|
419
|
+
@media_handler.register(self) if @media_handler
|
420
|
+
|
421
|
+
# and finally build the path list
|
422
|
+
build_attribute_path_list
|
423
|
+
end
|
424
|
+
|
390
425
|
def prepare_pk
|
391
426
|
if primary_key.is_a? Array
|
392
427
|
@pk_names = []
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rack'
|
2
|
+
require 'fileutils'
|
2
3
|
require_relative './navigation_attribute.rb'
|
3
4
|
|
4
5
|
module OData
|
@@ -9,49 +10,84 @@ module OData
|
|
9
10
|
|
10
11
|
# Simple static File/Directory based media store handler
|
11
12
|
# similar to Rack::Static
|
13
|
+
# with a flat directory structure
|
12
14
|
class Static < Handler
|
15
|
+
|
13
16
|
def initialize(root: nil)
|
14
17
|
@root = File.absolute_path(root || Dir.pwd)
|
15
18
|
@file_server = ::Rack::File.new(@root)
|
16
19
|
end
|
17
20
|
|
21
|
+
# TODO testcase and better abs_klass_dir design
|
22
|
+
def register(klass)
|
23
|
+
abs_klass_dir = File.absolute_path(klass.type_name, @root)
|
24
|
+
FileUtils.makedirs abs_klass_dir unless Dir.exists?(abs_klass_dir)
|
25
|
+
end
|
26
|
+
|
18
27
|
# minimal working implementation...
|
19
28
|
# Note: @file_server works relative to @root directory
|
20
29
|
def odata_get(request:, entity:)
|
21
30
|
media_env = request.env.dup
|
22
|
-
|
23
|
-
# simple design: one file per directory, and the directory
|
24
|
-
# contains the media entity-id --> implicit link between the media
|
25
|
-
# entity
|
26
|
-
filename = Dir.glob('*').first
|
27
|
-
|
28
|
-
File.join(path(entity), filename)
|
29
|
-
end
|
30
|
-
media_env['PATH_INFO'] = relpath
|
31
|
+
media_env['PATH_INFO'] = filename(entity)
|
31
32
|
@file_server.call(media_env)
|
32
33
|
end
|
33
34
|
|
34
35
|
# TODO perf: this can be precalculated and cached on MediaModelKlass level
|
35
36
|
# and passed as argument to save_file
|
37
|
+
# eg. /@root/Photo
|
36
38
|
def abs_klass_dir(entity)
|
37
39
|
File.absolute_path(entity.klass_dir, @root)
|
38
40
|
end
|
39
41
|
|
42
|
+
# this is relative to @root
|
43
|
+
# eg. Photo/1
|
44
|
+
def media_path(entity)
|
45
|
+
File.join(entity.klass_dir, media_directory(entity))
|
46
|
+
end
|
47
|
+
|
48
|
+
# relative to @root
|
49
|
+
# eg Photo/1/pommes-topaz.jpg
|
50
|
+
def filename(entity)
|
51
|
+
Dir.chdir(abs_path(entity)) do
|
52
|
+
# simple design: one file per directory, and the directory
|
53
|
+
# contains the media entity-id --> implicit link between the media
|
54
|
+
# entity
|
55
|
+
File.join(media_path(entity), Dir.glob('*').first)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# /@root/Photo/1
|
40
60
|
def abs_path(entity)
|
41
|
-
File.absolute_path(
|
61
|
+
File.absolute_path(media_path(entity), @root)
|
42
62
|
end
|
43
63
|
|
44
|
-
# this is relative to
|
45
|
-
|
46
|
-
|
64
|
+
# this is relative to abs_klass_dir(entity) eg to /@root/Photo
|
65
|
+
# simplest implementation is media_directory = entity.media_path_id
|
66
|
+
# --> we get a 1 level depth flat directory structure
|
67
|
+
def media_directory(entity)
|
68
|
+
entity.media_path_id
|
69
|
+
end
|
70
|
+
|
71
|
+
def in_media_directory(entity)
|
72
|
+
mpi = media_directory(entity)
|
73
|
+
Dir.mkdir mpi unless Dir.exists?(mpi)
|
74
|
+
Dir.chdir mpi do
|
75
|
+
yield
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def odata_delete(request:, entity:)
|
80
|
+
Dir.chdir(abs_klass_dir(entity)) do
|
81
|
+
in_media_directory(entity) do
|
82
|
+
Dir.glob('*').each { |oldf| File.delete(oldf) }
|
83
|
+
end
|
84
|
+
end
|
47
85
|
end
|
48
86
|
|
49
87
|
# Here as well, MVP implementation
|
50
88
|
def save_file(data:, filename:, entity:)
|
51
|
-
mpi = entity.media_path_id
|
52
89
|
Dir.chdir(abs_klass_dir(entity)) do
|
53
|
-
|
54
|
-
Dir.chdir mpi do
|
90
|
+
in_media_directory(entity) do
|
55
91
|
File.open(filename, 'wb') { |f| IO.copy_stream(data, f) }
|
56
92
|
end
|
57
93
|
end
|
@@ -59,22 +95,75 @@ module OData
|
|
59
95
|
|
60
96
|
# Here as well, MVP implementation
|
61
97
|
def replace_file(data:, filename:, entity:)
|
62
|
-
mpi = entity.media_path_id
|
63
98
|
Dir.chdir(abs_klass_dir(entity)) do
|
64
|
-
|
65
|
-
Dir.chdir mpi do
|
99
|
+
in_media_directory(entity) do
|
66
100
|
Dir.glob('*').each { |oldf| File.delete(oldf) }
|
67
101
|
File.open(filename, 'wb') { |f| IO.copy_stream(data, f) }
|
68
102
|
end
|
69
103
|
end
|
70
104
|
end
|
71
105
|
end
|
106
|
+
# Simple static File/Directory based media store handler
|
107
|
+
# similar to Rack::Static
|
108
|
+
# with directory Tree structure
|
109
|
+
|
110
|
+
class StaticTree < Static
|
111
|
+
|
112
|
+
SEP = '/00/'
|
113
|
+
|
114
|
+
def StaticTree.path_builder(ids)
|
115
|
+
ids.map{|id| id.to_s.chars.join('/')}.join(SEP)
|
116
|
+
end
|
117
|
+
|
118
|
+
# this is relative to abs_klass_dir(entity) eg to /@root/Photo
|
119
|
+
# tree-structure
|
120
|
+
# media_path_ids = 1 --> 1
|
121
|
+
# media_path_ids = 15 --> 1/5
|
122
|
+
# media_path_ids = 555 --> 5/5/5
|
123
|
+
# media_path_ids = 5,5,5 --> 5/00/5/00/5
|
124
|
+
# media_path_ids = 5,00,5 --> 5/00/0/0/00/5
|
125
|
+
# media_path_ids = 5,xyz,5 --> 5/00/x/y/z/00/5
|
126
|
+
def media_directory(entity)
|
127
|
+
StaticTree.path_builder(entity.media_path_ids)
|
128
|
+
# entity.media_path_ids.map{|id| id.to_s.chars.join('/')}.join(@sep)
|
129
|
+
end
|
130
|
+
|
131
|
+
def in_media_directory(entity)
|
132
|
+
mpi = media_directory(entity)
|
133
|
+
FileUtils.makedirs mpi unless Dir.exists?(mpi)
|
134
|
+
Dir.chdir mpi do
|
135
|
+
yield
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def odata_delete(request:, entity:)
|
140
|
+
Dir.chdir(abs_klass_dir(entity)) do
|
141
|
+
in_media_directory(entity) do
|
142
|
+
Dir.glob('*').each { |oldf| File.delete(oldf) if File.file?(oldf) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Here as well, MVP implementation
|
148
|
+
def replace_file(data:, filename:, entity:)
|
149
|
+
Dir.chdir(abs_klass_dir(entity)) do
|
150
|
+
in_media_directory(entity) do
|
151
|
+
Dir.glob('*').each { |oldf| File.delete(oldf) if File.file?(oldf) }
|
152
|
+
File.open(filename, 'wb') { |f| IO.copy_stream(data, f) }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
end
|
159
|
+
|
72
160
|
end
|
73
161
|
|
74
162
|
# special handling for media entity
|
75
163
|
module EntityClassMedia
|
76
164
|
attr_reader :media_handler
|
77
|
-
|
165
|
+
attr_reader :slug_field
|
166
|
+
|
78
167
|
# API method for defining the media handler
|
79
168
|
# eg.
|
80
169
|
# publish_media_model photos do
|
@@ -89,6 +178,11 @@ module OData
|
|
89
178
|
@media_handler = klass.new(*args)
|
90
179
|
end
|
91
180
|
|
181
|
+
# API method for setting the model field mapped to SLUG on upload
|
182
|
+
def slug(inp)
|
183
|
+
@slug_field = inp
|
184
|
+
end
|
185
|
+
|
92
186
|
def api_check_media_fields
|
93
187
|
unless self.db_schema.has_key?(:content_type)
|
94
188
|
raise OData::API::MediaModelError, self
|
@@ -98,6 +192,8 @@ module OData
|
|
98
192
|
# end
|
99
193
|
end
|
100
194
|
|
195
|
+
# END API methods
|
196
|
+
|
101
197
|
def new_media_entity(mimetype:)
|
102
198
|
nh = {}
|
103
199
|
nh['content_type'] = mimetype
|
@@ -125,6 +221,13 @@ module OData
|
|
125
221
|
|
126
222
|
new_entity = new_media_entity(mimetype: mimetype)
|
127
223
|
|
224
|
+
if slug_field
|
225
|
+
|
226
|
+
new_entity.set_fields({ slug_field => filename},
|
227
|
+
data_fields,
|
228
|
+
missing: :skip)
|
229
|
+
end
|
230
|
+
|
128
231
|
# to_one rels are create with FK data set on the parent entity
|
129
232
|
if parent
|
130
233
|
odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
|
data/lib/odata/common_logger.rb
CHANGED
@@ -5,21 +5,46 @@ module Rack
|
|
5
5
|
super
|
6
6
|
end
|
7
7
|
|
8
|
+
# Handle https://github.com/rack/rack/pull/1526
|
9
|
+
# new in Rack 2.2.2 : Format has now 11 placeholders instead of 10
|
10
|
+
|
11
|
+
MSG_FUNC = if (FORMAT.count('%') == 10)
|
12
|
+
lambda {|env,length,status,began_at|
|
13
|
+
FORMAT % [
|
14
|
+
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
15
|
+
env["REMOTE_USER"] || "-",
|
16
|
+
Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
17
|
+
env[REQUEST_METHOD],
|
18
|
+
env[SCRIPT_NAME] + env[PATH_INFO],
|
19
|
+
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
20
|
+
env[SERVER_PROTOCOL],
|
21
|
+
status.to_s[0..3],
|
22
|
+
length,
|
23
|
+
Utils.clock_time - began_at
|
24
|
+
]
|
25
|
+
}
|
26
|
+
elsif (FORMAT.count('%') == 11)
|
27
|
+
lambda {|env,length,status,began_at|
|
28
|
+
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],
|
38
|
+
length,
|
39
|
+
Utils.clock_time - began_at
|
40
|
+
]
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
8
44
|
def batch_log(env, status, header, began_at)
|
9
45
|
length = extract_content_length(header)
|
10
46
|
|
11
|
-
msg =
|
12
|
-
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
13
|
-
env["REMOTE_USER"] || "-",
|
14
|
-
Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
15
|
-
env[REQUEST_METHOD],
|
16
|
-
env[PATH_INFO],
|
17
|
-
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
18
|
-
env[SERVER_PROTOCOL],
|
19
|
-
status.to_s[0..3],
|
20
|
-
length,
|
21
|
-
Utils.clock_time - began_at
|
22
|
-
]
|
47
|
+
msg = MSG_FUNC.call(env, length, status, began_at)
|
23
48
|
|
24
49
|
logger = @logger || env[RACK_ERRORS]
|
25
50
|
# Standard library logger doesn't support write but it supports << which actually
|
data/lib/odata/entity.rb
CHANGED
@@ -2,6 +2,7 @@ require 'json'
|
|
2
2
|
require 'rexml/document'
|
3
3
|
require 'safrano.rb'
|
4
4
|
require 'odata/collection.rb' # required for self.class.entity_type_name ??
|
5
|
+
require_relative 'navigation_attribute'
|
5
6
|
|
6
7
|
module OData
|
7
8
|
# this will be mixed in the Model classes (subclasses of Sequel Model)
|
@@ -9,6 +10,8 @@ module OData
|
|
9
10
|
attr_reader :params
|
10
11
|
attr_reader :uribase
|
11
12
|
|
13
|
+
include EntityBase::NavigationInfo
|
14
|
+
|
12
15
|
# methods related to transitions to next state (cf. walker)
|
13
16
|
module Transitions
|
14
17
|
def allowed_transitions
|
@@ -98,11 +101,18 @@ module OData
|
|
98
101
|
def to_odata_json(service:)
|
99
102
|
innerj = service.get_entity_odata_h(entity: self,
|
100
103
|
expand: @params['$expand'],
|
101
|
-
# links: @do_links,
|
102
104
|
uribase: @uribase).to_json
|
103
105
|
"#{DJopen}#{innerj}#{DJclose}"
|
104
106
|
end
|
105
107
|
|
108
|
+
# Json formatter for a single entity reached by navigation $links
|
109
|
+
def to_odata_onelink_json(service:)
|
110
|
+
innerj = service.get_entity_odata_link_h(entity: self,
|
111
|
+
uribase: @uribase).to_json
|
112
|
+
"#{DJopen}#{innerj}#{DJclose}"
|
113
|
+
end
|
114
|
+
|
115
|
+
|
106
116
|
# needed for proper datetime output
|
107
117
|
# TODO design/performance
|
108
118
|
def casted_values
|
@@ -144,21 +154,38 @@ module OData
|
|
144
154
|
if req.walker.media_value
|
145
155
|
odata_media_value_get(req)
|
146
156
|
elsif req.accept?(APPJSON)
|
147
|
-
|
157
|
+
if req.walker.do_links
|
158
|
+
[200, CT_JSON, [to_odata_onelink_json(service: req.service)]]
|
159
|
+
else
|
160
|
+
[200, CT_JSON, [to_odata_json(service: req.service)]]
|
161
|
+
end
|
148
162
|
else # TODO: other formats
|
149
163
|
415
|
150
164
|
end
|
151
165
|
end
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
166
|
+
|
167
|
+
DELETE_REL_AND_ENTY = lambda do |entity, assoc, parent|
|
168
|
+
OData.remove_nav_relation(entity, assoc, parent)
|
169
|
+
entity.destroy(transaction: false)
|
170
|
+
end
|
171
|
+
|
172
|
+
def odata_delete_relation_and_entity(req, assoc, parent)
|
173
|
+
if parent
|
174
|
+
if req.in_changeset
|
175
|
+
# in-changeset requests get their own transaction
|
176
|
+
DELETE_REL_AND_ENTY.call(self, assoc, parent)
|
177
|
+
else
|
178
|
+
db.transaction do
|
179
|
+
DELETE_REL_AND_ENTY.call(self, assoc, parent)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
else
|
183
|
+
destroy(transaction: false)
|
159
184
|
end
|
185
|
+
rescue StandardError => e
|
186
|
+
raise SequelAdapterError.new(e)
|
160
187
|
end
|
161
|
-
|
188
|
+
|
162
189
|
# TODO: differentiate between POST/PUT/PATCH/MERGE
|
163
190
|
def odata_post(req)
|
164
191
|
data = JSON.parse(req.body.read)
|
@@ -253,6 +280,11 @@ module OData
|
|
253
280
|
y.each { |enty| yield enty }
|
254
281
|
end
|
255
282
|
|
283
|
+
# TODO design... this is not DRY
|
284
|
+
def slug_field
|
285
|
+
superclass.slug_field
|
286
|
+
end
|
287
|
+
|
256
288
|
def type_name
|
257
289
|
superclass.type_name
|
258
290
|
end
|
@@ -288,7 +320,7 @@ module OData
|
|
288
320
|
extend NavigationRedefinitions
|
289
321
|
end
|
290
322
|
end
|
291
|
-
|
323
|
+
|
292
324
|
# GetRelatedEntity that returns an single related Entity
|
293
325
|
# (...to_one relationship )
|
294
326
|
def get_related_entity(childattrib)
|
@@ -301,9 +333,10 @@ module OData
|
|
301
333
|
# then we return a Nil... wrapper object. This object then
|
302
334
|
# allows to receive a POST operation that would actually create the nav attribute entity
|
303
335
|
|
304
|
-
ret = method(childattrib.to_sym).call ||
|
305
|
-
|
306
|
-
|
336
|
+
ret = method(childattrib.to_sym).call || OData::NilNavigationAttribute.new
|
337
|
+
|
338
|
+
ret.set_relation_info(self, childattrib)
|
339
|
+
|
307
340
|
ret
|
308
341
|
end
|
309
342
|
end
|
@@ -323,6 +356,20 @@ module OData
|
|
323
356
|
values.dup
|
324
357
|
end
|
325
358
|
|
359
|
+
def odata_delete(req)
|
360
|
+
if req.accept?(APPJSON)
|
361
|
+
# delete
|
362
|
+
begin
|
363
|
+
odata_delete_relation_and_entity(req, @navattr_reflection, @nav_parent)
|
364
|
+
[200, CT_JSON, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
|
365
|
+
rescue SequelAdapterError => e
|
366
|
+
BadRequestSequelAdapterError.new(e).odata_get(req)
|
367
|
+
end
|
368
|
+
else # TODO: other formats
|
369
|
+
415
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
326
373
|
# in case of a non media entity, we have to return an error on $value request
|
327
374
|
def odata_media_value_get(req)
|
328
375
|
return BadRequestNonMediaValue.odata_get
|
@@ -364,6 +411,23 @@ module OData
|
|
364
411
|
ret
|
365
412
|
end
|
366
413
|
|
414
|
+
def odata_delete(req)
|
415
|
+
if req.accept?(APPJSON)
|
416
|
+
# delete the MR
|
417
|
+
# delegate to the media handler on collection(ie class) level
|
418
|
+
# TODO error handling
|
419
|
+
|
420
|
+
|
421
|
+
self.class.media_handler.odata_delete(request: req, entity: self)
|
422
|
+
# delete the relation(s) to parent(s) (if any) and then entity
|
423
|
+
odata_delete_relation_and_entity(req, @navattr_reflection, @nav_parent)
|
424
|
+
# result
|
425
|
+
[200, CT_JSON, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
|
426
|
+
else # TODO: other formats
|
427
|
+
415
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
367
431
|
# real implementation for returning $value for a media entity
|
368
432
|
def odata_media_value_get(req)
|
369
433
|
# delegate to the media handler on collection(ie class) level
|
@@ -399,6 +463,9 @@ module OData
|
|
399
463
|
def media_path_id
|
400
464
|
pk.to_s
|
401
465
|
end
|
466
|
+
def media_path_ids
|
467
|
+
[pk]
|
468
|
+
end
|
402
469
|
end
|
403
470
|
|
404
471
|
# for multiple key
|
@@ -412,6 +479,10 @@ module OData
|
|
412
479
|
def media_path_id
|
413
480
|
self.pk_hash.values.join('_')
|
414
481
|
end
|
482
|
+
|
483
|
+
def media_path_ids
|
484
|
+
self.pk_hash.values
|
485
|
+
end
|
415
486
|
end
|
416
487
|
end
|
417
488
|
# end of Module OData
|
data/lib/odata/error.rb
CHANGED
@@ -31,8 +31,8 @@ module OData
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
# base module for HTTP errors
|
35
|
-
module
|
34
|
+
# base module for HTTP errors, when used as a Error Class
|
35
|
+
module ErrorClass
|
36
36
|
def odata_get(req)
|
37
37
|
if req.accept?(APPJSON)
|
38
38
|
[const_get(:HTTP_CODE), CT_JSON,
|
@@ -42,9 +42,22 @@ module OData
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
# base module for HTTP errors, when used as an Error instance
|
47
|
+
module ErrorInstance
|
48
|
+
def odata_get(req)
|
49
|
+
if req.accept?(APPJSON)
|
50
|
+
[self.class.const_get(:HTTP_CODE), CT_JSON,
|
51
|
+
{ 'odata.error' => { 'code' => '', 'message' => @msg } }.to_json]
|
52
|
+
else
|
53
|
+
[self.class.const_get(:HTTP_CODE), CT_TEXT, @msg]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
45
58
|
# http Bad Req.
|
46
59
|
class BadRequestError
|
47
|
-
extend
|
60
|
+
extend ErrorClass
|
48
61
|
HTTP_CODE = 400
|
49
62
|
@msg = 'Bad Request Error'
|
50
63
|
end
|
@@ -59,7 +72,13 @@ module OData
|
|
59
72
|
HTTP_CODE = 400
|
60
73
|
@msg = 'Bad Request: $value request for a non-media entity'
|
61
74
|
end
|
62
|
-
|
75
|
+
class BadRequestSequelAdapterError < BadRequestError
|
76
|
+
include ErrorInstance
|
77
|
+
def initialize(err)
|
78
|
+
@msg = err.inner.message
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
63
82
|
# for Syntax error in Filtering
|
64
83
|
class BadRequestFilterParseError < BadRequestError
|
65
84
|
HTTP_CODE = 400
|
@@ -72,36 +91,36 @@ module OData
|
|
72
91
|
end
|
73
92
|
# http not found
|
74
93
|
class ErrorNotFound
|
75
|
-
extend
|
94
|
+
extend ErrorClass
|
76
95
|
HTTP_CODE = 404
|
77
96
|
@msg = 'The requested ressource was not found'
|
78
97
|
end
|
79
98
|
# Transition error (Safrano specific)
|
80
99
|
class ServerTransitionError
|
81
|
-
extend
|
100
|
+
extend ErrorClass
|
82
101
|
HTTP_CODE = 500
|
83
102
|
@msg = 'Server error: Segment could not be parsed'
|
84
103
|
end
|
85
104
|
# generic http 500 server err
|
86
105
|
class ServerError
|
87
|
-
extend
|
106
|
+
extend ErrorClass
|
88
107
|
HTTP_CODE = 500
|
89
108
|
@msg = 'Server error'
|
90
109
|
end
|
91
110
|
# not implemented (Safrano specific)
|
92
111
|
class NotImplementedError
|
93
|
-
extend
|
112
|
+
extend ErrorClass
|
94
113
|
HTTP_CODE = 501
|
95
114
|
end
|
96
115
|
# batch not implemented (Safrano specific)
|
97
116
|
class BatchNotImplementedError
|
98
|
-
extend
|
117
|
+
extend ErrorClass
|
99
118
|
HTTP_CODE = 501
|
100
119
|
@msg = 'Not implemented: OData batch'
|
101
120
|
end
|
102
121
|
# error in filter parsing (Safrano specific)
|
103
122
|
class FilterParseError < BadRequestError
|
104
|
-
extend
|
123
|
+
extend ErrorClass
|
105
124
|
HTTP_CODE = 400
|
106
125
|
end
|
107
126
|
end
|
data/lib/odata/filter/error.rb
CHANGED
@@ -1,8 +1,30 @@
|
|
1
1
|
require 'json'
|
2
|
-
require_relative '../
|
2
|
+
require_relative '../safrano/core.rb'
|
3
3
|
require_relative './entity.rb'
|
4
4
|
|
5
5
|
module OData
|
6
|
+
|
7
|
+
# remove the relation between entity and parent by clearing
|
8
|
+
# the FK field(s) (if allowed)
|
9
|
+
def OData.remove_nav_relation(entity, assoc, parent)
|
10
|
+
return unless assoc
|
11
|
+
|
12
|
+
case assoc[:type]
|
13
|
+
when :one_to_many, :one_to_one
|
14
|
+
when :many_to_one
|
15
|
+
# removes/clear the FK values in parent
|
16
|
+
# thus deleting the "link" between the entity and the parent
|
17
|
+
# Note: This is called if we have to delete the child--> can only be
|
18
|
+
# done after removing the FK in parent (if allowed!)
|
19
|
+
lks = [assoc[:key]].flatten
|
20
|
+
lks.each{|lk|
|
21
|
+
parent.set(lk => nil )
|
22
|
+
parent.save(transaction: false)
|
23
|
+
}
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
6
28
|
# link newly created entities(child) to an existing parent
|
7
29
|
# by following the association_reflection rules
|
8
30
|
def OData.create_nav_relation(child, assoc, parent)
|
@@ -55,18 +77,24 @@ module OData
|
|
55
77
|
end
|
56
78
|
end
|
57
79
|
|
80
|
+
module EntityBase
|
81
|
+
module NavigationInfo
|
82
|
+
attr_reader :nav_parent
|
83
|
+
attr_reader :navattr_reflection
|
84
|
+
attr_reader :nav_name
|
85
|
+
def set_relation_info(parent,name)
|
86
|
+
@nav_parent = parent
|
87
|
+
@nav_name = name
|
88
|
+
@navattr_reflection = parent.class.association_reflections[name.to_sym]
|
89
|
+
@nav_klass = @navattr_reflection[:class_name].constantize
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
58
94
|
# Represents a named but nil-valued navigation-attribute of an Entity
|
59
95
|
# (usually resulting from a NULL FK db value)
|
60
96
|
class NilNavigationAttribute
|
61
|
-
|
62
|
-
attr_reader :parent
|
63
|
-
def initialize(parent, name)
|
64
|
-
@parent = parent
|
65
|
-
@name = name
|
66
|
-
@navattr_reflection = parent.class.association_reflections[name.to_sym]
|
67
|
-
@klass = @navattr_reflection[:class_name].constantize
|
68
|
-
end
|
69
|
-
|
97
|
+
include EntityBase::NavigationInfo
|
70
98
|
def odata_get(req)
|
71
99
|
if req.walker.media_value
|
72
100
|
OData::ErrorNotFound.odata_get
|
@@ -80,13 +108,20 @@ module OData
|
|
80
108
|
# create the nav. entity
|
81
109
|
def odata_post(req)
|
82
110
|
# delegate to the class method
|
83
|
-
@
|
111
|
+
@nav_klass.odata_create_entity_and_relation(req,
|
112
|
+
@navattr_reflection,
|
113
|
+
@nav_parent)
|
84
114
|
end
|
85
115
|
|
86
116
|
# create the nav. entity
|
87
117
|
def odata_put(req)
|
88
|
-
|
89
|
-
|
118
|
+
# if req.walker.raw_value
|
119
|
+
# delegate to the class method
|
120
|
+
@nav_klass.odata_create_entity_and_relation(req,
|
121
|
+
@navattr_reflection,
|
122
|
+
@nav_parent)
|
123
|
+
# else
|
124
|
+
# end
|
90
125
|
end
|
91
126
|
|
92
127
|
# empty output as OData json (v2)
|
@@ -96,7 +131,7 @@ module OData
|
|
96
131
|
|
97
132
|
# for testing purpose (assert_equal ...)
|
98
133
|
def ==(other)
|
99
|
-
(@
|
134
|
+
(@nav_parent == other.nav_parent) && (@nav_name == other.nav_name)
|
100
135
|
end
|
101
136
|
|
102
137
|
# methods related to transitions to next state (cf. walker)
|
data/lib/odata/relations.rb
CHANGED
@@ -85,7 +85,7 @@ module OData
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
def get_metadata_xml_attribs(from, to, assoc_type, xnamespace)
|
88
|
+
def get_metadata_xml_attribs(from, to, assoc_type, xnamespace, attrname)
|
89
89
|
rel = get([from, to])
|
90
90
|
# use Sequel reflection to get multiplicity (will be used later
|
91
91
|
# in 2. Associations below)
|
@@ -107,7 +107,7 @@ module OData
|
|
107
107
|
# <NavigationProperty Name="Supplier"
|
108
108
|
# Relationship="ODataDemo.Product_Supplier_Supplier_Products"
|
109
109
|
# FromRole="Product_Supplier" ToRole="Supplier_Products"/>
|
110
|
-
{ 'Name' =>
|
110
|
+
{ 'Name' => attrname, 'Relationship' => "#{xnamespace}.#{rel.name}",
|
111
111
|
'FromRole' => from, 'ToRole' => to }
|
112
112
|
end
|
113
113
|
end
|
data/lib/safrano.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
|
3
2
|
require 'json'
|
4
3
|
require 'rexml/document'
|
5
|
-
require_relative '
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
require_relative 'safrano/multipart.rb'
|
5
|
+
require_relative 'safrano/core.rb'
|
6
|
+
require_relative 'odata/entity.rb'
|
7
|
+
require_relative 'odata/attribute.rb'
|
8
|
+
require_relative 'odata/navigation_attribute.rb'
|
9
|
+
require_relative 'odata/collection.rb'
|
10
|
+
require_relative 'safrano/service.rb'
|
11
|
+
require_relative 'odata/walker.rb'
|
13
12
|
require 'sequel'
|
14
|
-
require_relative '
|
15
|
-
|
16
|
-
|
13
|
+
require_relative 'safrano/sequel_join_by_paths.rb'
|
14
|
+
require_relative 'safrano/rack_app'
|
15
|
+
require_relative 'safrano/odata_rack_builder'
|
16
|
+
require_relative 'safrano/version'
|
17
17
|
|
18
18
|
# picked from activsupport; needed for ruby < 2.5
|
19
19
|
# Destructively converts all keys using the +block+ operations.
|
@@ -30,3 +30,13 @@ class Hash
|
|
30
30
|
transform_keys! { |key| key.to_sym rescue key }
|
31
31
|
end
|
32
32
|
end
|
33
|
+
|
34
|
+
# needed for ruby < 2.5
|
35
|
+
class Dir
|
36
|
+
def self.each_child(dir)
|
37
|
+
Dir.foreach(dir) {|x|
|
38
|
+
next if ( ( x == '.' ) or ( x == '..' ) )
|
39
|
+
yield x
|
40
|
+
}
|
41
|
+
end unless respond_to? :each_child
|
42
|
+
end
|
@@ -32,14 +32,15 @@ module OData
|
|
32
32
|
# database-specific types.
|
33
33
|
DB_TYPE_STRING_RGX = /\ACHAR\s*\(\d+\)\z/.freeze
|
34
34
|
|
35
|
+
# TODO... complete; used in $metadata
|
35
36
|
def self.get_edm_type(db_type:)
|
36
|
-
case db_type
|
37
|
+
case db_type.upcase
|
37
38
|
when 'INTEGER'
|
38
39
|
'Edm.Int32'
|
39
40
|
when 'TEXT', 'STRING'
|
40
41
|
'Edm.String'
|
41
42
|
else
|
42
|
-
'Edm.String' if DB_TYPE_STRING_RGX =~ db_type
|
43
|
+
'Edm.String' if DB_TYPE_STRING_RGX =~ db_type.upcase
|
43
44
|
end
|
44
45
|
end
|
45
46
|
end
|
File without changes
|
File without changes
|
@@ -1,6 +1,5 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
require 'rack'
|
2
|
+
require 'rfc2047'
|
4
3
|
|
5
4
|
module OData
|
6
5
|
# monkey patch deactivate Rack/multipart because it does not work on simple
|
@@ -135,8 +134,10 @@ module OData
|
|
135
134
|
|
136
135
|
def with_media_data
|
137
136
|
if (filename = @env['HTTP_SLUG'])
|
138
|
-
|
139
|
-
yield @env['rack.input'],
|
137
|
+
|
138
|
+
yield @env['rack.input'],
|
139
|
+
content_type.split(';').first,
|
140
|
+
Rfc2047.decode(filename)
|
140
141
|
|
141
142
|
else
|
142
143
|
ON_CGST_ERROR.call(self)
|
@@ -46,20 +46,40 @@ module OData
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
# for expand 1..n nav attributes
|
50
|
+
# actually same as v1 get_coll_odata_h
|
51
|
+
# def get_expandcoll_odata_h(array:, expand: nil, uribase:, icount: nil)
|
52
|
+
# array.map do |w|
|
53
|
+
# get_entity_odata_h(entity: w,
|
54
|
+
# expand: expand,
|
55
|
+
# uribase: uribase)
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
|
49
59
|
# handle a single expand
|
50
60
|
def handle_entity_expand_one(entity:, exp_one:, nav_values_h:, nav_coll_h:,
|
51
61
|
uribase:)
|
52
62
|
|
63
|
+
|
53
64
|
split_entity_expand_arg(exp_one) do |first, rest_exp|
|
54
|
-
|
55
|
-
|
65
|
+
if ( entity.nav_values.has_key?(first) )
|
66
|
+
if (enval = entity.nav_values[first])
|
67
|
+
nav_values_h[first.to_s] = get_entity_odata_h(entity: enval,
|
56
68
|
expand: rest_exp,
|
57
69
|
uribase: uribase)
|
70
|
+
else
|
71
|
+
# FK is NULL --> nav_value is nil --> return empty json
|
72
|
+
nav_values_h[first.to_s] = {}
|
73
|
+
end
|
58
74
|
elsif (encoll = entity.nav_coll[first])
|
59
|
-
|
60
|
-
|
75
|
+
# nav attributes that are a collection (x..n)
|
76
|
+
nav_coll_h[first.to_s] = get_coll_odata_h(array: encoll,
|
61
77
|
expand: rest_exp,
|
62
78
|
uribase: uribase)
|
79
|
+
# nav_coll_h[first.to_s] = get_expandcoll_odata_h(array: encoll,
|
80
|
+
# expand: rest_exp,
|
81
|
+
# uribase: uribase)
|
82
|
+
|
63
83
|
|
64
84
|
end
|
65
85
|
end
|
@@ -71,7 +91,8 @@ module OData
|
|
71
91
|
explist = expand.split(',')
|
72
92
|
# handle multiple expands
|
73
93
|
explist.each do |exp|
|
74
|
-
handle_entity_expand_one(entity: entity,
|
94
|
+
handle_entity_expand_one(entity: entity,
|
95
|
+
exp_one: exp,
|
75
96
|
nav_values_h: nav_values_h,
|
76
97
|
nav_coll_h: nav_coll_h,
|
77
98
|
uribase: uribase)
|
@@ -335,6 +356,7 @@ module OData
|
|
335
356
|
# to be called at end of publishing block to ensure we get the right names
|
336
357
|
# and additionally build the list of valid attribute path's used
|
337
358
|
# for validation of $orderby or $filter params
|
359
|
+
|
338
360
|
def finalize_publishing
|
339
361
|
# build the cmap
|
340
362
|
@cmap = {}
|
@@ -348,8 +370,10 @@ module OData
|
|
348
370
|
# set default path prefix if path_prefix was not called
|
349
371
|
path_prefix(DEFAULT_PATH_PREFIX) unless @xpath_prefix
|
350
372
|
|
351
|
-
|
352
|
-
|
373
|
+
@collections.each(&:finalize_publishing)
|
374
|
+
|
375
|
+
#finalize the media handlers
|
376
|
+
@collections.each{|klass| }
|
353
377
|
end
|
354
378
|
|
355
379
|
def execute_deferred_iblocks
|
@@ -397,7 +421,7 @@ module OData
|
|
397
421
|
def add_metadata_xml_entity_type(schema)
|
398
422
|
@collections.each do |klass|
|
399
423
|
enty = klass.add_metadata_rexml(schema)
|
400
|
-
klass.add_metadata_navs_rexml(enty, @
|
424
|
+
klass.add_metadata_navs_rexml(enty, @relman, @xnamespace)
|
401
425
|
end
|
402
426
|
end
|
403
427
|
|
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.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- D.M.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.15'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rfc2047
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rake
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,12 +109,11 @@ dependencies:
|
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0.51'
|
97
111
|
description: Safrano is an OData server framework based on Ruby, Rack and Sequel.
|
98
|
-
email:
|
112
|
+
email: dev@aithscel.eu
|
99
113
|
executables: []
|
100
114
|
extensions: []
|
101
115
|
extra_rdoc_files: []
|
102
116
|
files:
|
103
|
-
- lib/multipart.rb
|
104
117
|
- lib/odata/attribute.rb
|
105
118
|
- lib/odata/batch.rb
|
106
119
|
- lib/odata/collection.rb
|
@@ -119,16 +132,17 @@ files:
|
|
119
132
|
- lib/odata/relations.rb
|
120
133
|
- lib/odata/url_parameters.rb
|
121
134
|
- lib/odata/walker.rb
|
122
|
-
- lib/odata_rack_builder.rb
|
123
|
-
- lib/rack_app.rb
|
124
|
-
- lib/request.rb
|
125
|
-
- lib/response.rb
|
126
135
|
- lib/safrano.rb
|
127
|
-
- lib/
|
136
|
+
- lib/safrano/core.rb
|
137
|
+
- lib/safrano/multipart.rb
|
138
|
+
- lib/safrano/odata_rack_builder.rb
|
139
|
+
- lib/safrano/rack_app.rb
|
140
|
+
- lib/safrano/request.rb
|
141
|
+
- lib/safrano/response.rb
|
142
|
+
- lib/safrano/sequel_join_by_paths.rb
|
143
|
+
- lib/safrano/service.rb
|
144
|
+
- lib/safrano/version.rb
|
128
145
|
- lib/sequel/plugins/join_by_paths.rb
|
129
|
-
- lib/sequel_join_by_paths.rb
|
130
|
-
- lib/service.rb
|
131
|
-
- lib/version.rb
|
132
146
|
homepage: https://gitlab.com/dm0da/safrano
|
133
147
|
licenses:
|
134
148
|
- MIT
|
data/lib/version.rb
DELETED