safrano 0.3.4 → 0.4.0
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 +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