safrano 0.4.1 → 0.4.2

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.
@@ -37,25 +37,56 @@ end
37
37
 
38
38
  # filter base class and subclass in our OData namespace
39
39
  module OData
40
+ class FilterBase
41
+ # re-useable empty filtering (idempotent)
42
+ EmptyFilter = new
43
+
44
+ def self.factory(filterstr)
45
+ filterstr.nil? ? EmptyFilter : FilterByParse.new(filterstr)
46
+ end
47
+
48
+ def apply_to_dataset(dtcx)
49
+ dtcx
50
+ end
51
+
52
+ # finalize
53
+ def finalize(jh) end
54
+
55
+ def empty?
56
+ true
57
+ end
58
+
59
+ def parse_error?
60
+ false
61
+ end
62
+ end
40
63
  # should handle everything by parsing
41
- class FilterByParse
42
- def initialize(filterstr, jh)
64
+ class FilterByParse < FilterBase
65
+ attr_reader :filterstr
66
+ def initialize(filterstr)
43
67
  @filterstr = filterstr.dup
44
68
  @ast = OData::Filter::Parser.new(@filterstr).build
45
- @jh = jh
46
69
  end
47
70
 
48
- def apply_to_dataset(dtcx)
49
- filtexpr = @ast.sequel_expr(@jh)
50
- dtcx = @jh.dataset(dtcx).where(filtexpr).select_all(@jh.start_model.table_name)
71
+ # this build's up the Sequel Filter Expression, and as a side effect,
72
+ # it also finalizes the join helper that we need for the start dataset join
73
+ # the join-helper is shared by the order-by object and was potentially already
74
+ # partly built on order-by object creation.
75
+ def finalize(jh)
76
+ @filtexpr = @ast.sequel_expr(jh)
51
77
  end
52
78
 
53
- def sequel_expr
54
- @ast.sequel_expr(@jh)
79
+ def apply_to_dataset(dtcx)
80
+ # normally finalize is called before, and thus @filtexpr is set
81
+ dtcx.where(@filtexpr)
55
82
  end
56
83
 
57
84
  def parse_error?
58
- @ast.kind_of? StandardError
85
+ @ast.is_a? StandardError
86
+ end
87
+
88
+ def empty?
89
+ false
59
90
  end
60
91
  end
61
92
  end
@@ -12,18 +12,17 @@ module OData
12
12
  # similar to Rack::Static
13
13
  # with a flat directory structure
14
14
  class Static < Handler
15
-
16
15
  def initialize(root: nil)
17
16
  @root = File.absolute_path(root || Dir.pwd)
18
17
  @file_server = ::Rack::File.new(@root)
19
18
  end
20
19
 
21
- # TODO testcase and better abs_klass_dir design
20
+ # TODO: testcase and better abs_klass_dir design
22
21
  def register(klass)
23
22
  abs_klass_dir = File.absolute_path(klass.type_name, @root)
24
- FileUtils.makedirs abs_klass_dir unless Dir.exists?(abs_klass_dir)
23
+ FileUtils.makedirs abs_klass_dir unless Dir.exist?(abs_klass_dir)
25
24
  end
26
-
25
+
27
26
  # minimal working implementation...
28
27
  # Note: @file_server works relative to @root directory
29
28
  def odata_get(request:, entity:)
@@ -37,7 +36,7 @@ module OData
37
36
  fsret
38
37
  end
39
38
 
40
- # TODO perf: this can be precalculated and cached on MediaModelKlass level
39
+ # TODO: [perf] this can be precalculated and cached on MediaModelKlass level
41
40
  # and passed as argument to save_file
42
41
  # eg. /@root/Photo
43
42
  def abs_klass_dir(entity)
@@ -49,7 +48,7 @@ module OData
49
48
  def media_path(entity)
50
49
  File.join(entity.klass_dir, media_directory(entity))
51
50
  end
52
-
51
+
53
52
  # relative to @root
54
53
  # eg Photo/1/pommes-topaz.jpg
55
54
  def filename(entity)
@@ -60,7 +59,7 @@ module OData
60
59
  File.join(media_path(entity), Dir.glob('*').first)
61
60
  end
62
61
  end
63
-
62
+
64
63
  # /@root/Photo/1
65
64
  def abs_path(entity)
66
65
  File.absolute_path(media_path(entity), @root)
@@ -75,18 +74,18 @@ module OData
75
74
 
76
75
  def in_media_directory(entity)
77
76
  mpi = media_directory(entity)
78
- Dir.mkdir mpi unless Dir.exists?(mpi)
77
+ Dir.mkdir mpi unless Dir.exist?(mpi)
79
78
  Dir.chdir mpi do
80
79
  yield
81
80
  end
82
81
  end
83
82
 
84
- def odata_delete(request:, entity:)
83
+ def odata_delete(entity:)
85
84
  Dir.chdir(abs_klass_dir(entity)) do
86
85
  in_media_directory(entity) do
87
86
  Dir.glob('*').each { |oldf| File.delete(oldf) }
88
87
  end
89
- end
88
+ end
90
89
  end
91
90
 
92
91
  # Here as well, MVP implementation
@@ -105,17 +104,20 @@ module OData
105
104
  def ressource_version(entity)
106
105
  Dir.chdir(abs_klass_dir(entity)) do
107
106
  in_media_directory(entity) do
108
- Dir.glob('*').last
107
+ Dir.glob('*').last
109
108
  end
110
- end
109
+ end
111
110
  end
112
-
111
+
113
112
  # Here as well, MVP implementation
114
113
  def replace_file(data:, filename:, entity:)
115
114
  Dir.chdir(abs_klass_dir(entity)) do
116
115
  in_media_directory(entity) do
117
116
  version = nil
118
- Dir.glob('*').each { |oldf| version = oldf; File.delete(oldf) }
117
+ Dir.glob('*').each do |oldf|
118
+ version = oldf
119
+ File.delete(oldf)
120
+ end
119
121
  filename = (version.to_i + 1).to_s
120
122
  File.open(filename, 'wb') { |f| IO.copy_stream(data, f) }
121
123
  end
@@ -125,15 +127,15 @@ module OData
125
127
  # Simple static File/Directory based media store handler
126
128
  # similar to Rack::Static
127
129
  # with directory Tree structure
128
-
130
+
129
131
  class StaticTree < Static
130
-
131
- SEP = '/00/'
132
-
133
- def StaticTree.path_builder(ids)
134
- ids.map{|id| id.to_s.chars.join('/')}.join(SEP) + '/v'
132
+ SEP = '/00/'.freeze
133
+ VERS = '/v'.freeze
134
+
135
+ def self.path_builder(ids)
136
+ ids.map { |id| id.to_s.chars.join('/') }.join(SEP) << VERS
135
137
  end
136
-
138
+
137
139
  # this is relative to abs_klass_dir(entity) eg to /@root/Photo
138
140
  # tree-structure
139
141
  # media_path_ids = 1 --> 1
@@ -144,55 +146,54 @@ module OData
144
146
  # media_path_ids = 5,xyz,5 --> 5/00/x/y/z/00/5
145
147
  def media_directory(entity)
146
148
  StaticTree.path_builder(entity.media_path_ids)
147
- # entity.media_path_ids.map{|id| id.to_s.chars.join('/')}.join(@sep)
149
+ # entity.media_path_ids.map{|id| id.to_s.chars.join('/')}.join(@sep)
148
150
  end
149
151
 
150
152
  def in_media_directory(entity)
151
153
  mpi = media_directory(entity)
152
- FileUtils.makedirs mpi unless Dir.exists?(mpi)
153
- Dir.chdir mpi do
154
- yield
155
- end
154
+ FileUtils.makedirs mpi unless Dir.exist?(mpi)
155
+ Dir.chdir(mpi) { yield }
156
156
  end
157
157
 
158
- def odata_delete(request:, entity:)
158
+ def odata_delete(entity:)
159
159
  Dir.chdir(abs_klass_dir(entity)) do
160
160
  in_media_directory(entity) do
161
161
  Dir.glob('*').each { |oldf| File.delete(oldf) if File.file?(oldf) }
162
162
  end
163
- end
163
+ end
164
164
  end
165
-
165
+
166
+ # Here as well, MVP implementation
167
+ # def replace_file(data:, filename:, entity:)
168
+ # Dir.chdir(abs_klass_dir(entity)) do
169
+ # in_media_directory(entity) do
170
+ # Dir.glob('*').each { |oldf| File.delete(oldf) if File.file?(oldf) }
171
+ # File.open(filename, 'wb') { |f| IO.copy_stream(data, f) }
172
+ # end
173
+ # end
174
+ # end
166
175
  # Here as well, MVP implementation
167
- # def replace_file(data:, filename:, entity:)
168
- # Dir.chdir(abs_klass_dir(entity)) do
169
- # in_media_directory(entity) do
170
- # Dir.glob('*').each { |oldf| File.delete(oldf) if File.file?(oldf) }
171
- # File.open(filename, 'wb') { |f| IO.copy_stream(data, f) }
172
- # end
173
- # end
174
- # end
175
- # Here as well, MVP implementation
176
176
  def replace_file(data:, filename:, entity:)
177
177
  Dir.chdir(abs_klass_dir(entity)) do
178
178
  in_media_directory(entity) do
179
179
  version = nil
180
- Dir.glob('*').each { |oldf| version = oldf; File.delete(oldf) }
180
+ Dir.glob('*').each do |oldf|
181
+ version = oldf
182
+ File.delete(oldf)
183
+ end
181
184
  filename = (version.to_i + 1).to_s
182
185
  File.open(filename, 'wb') { |f| IO.copy_stream(data, f) }
183
186
  end
184
187
  end
185
188
  end
186
-
187
189
  end
188
-
189
190
  end
190
191
 
191
192
  # special handling for media entity
192
193
  module EntityClassMedia
193
194
  attr_reader :media_handler
194
195
  attr_reader :slug_field
195
-
196
+
196
197
  # API method for defining the media handler
197
198
  # eg.
198
199
  # publish_media_model photos do
@@ -209,23 +210,21 @@ module OData
209
210
 
210
211
  # API method for setting the model field mapped to SLUG on upload
211
212
  def slug(inp)
212
- @slug_field = inp
213
+ @slug_field = inp
213
214
  end
214
-
215
+
215
216
  def api_check_media_fields
216
- unless self.db_schema.has_key?(:content_type)
217
- raise OData::API::MediaModelError, self
218
- end
217
+ raise(OData::API::MediaModelError, self) unless db_schema.key?(:content_type)
218
+
219
219
  # unless self.db_schema.has_key?(:media_src)
220
220
  # raise OData::API::MediaModelError, self
221
221
  # end
222
222
  end
223
223
 
224
- # END API methods
224
+ # END API methods
225
225
 
226
226
  def new_media_entity(mimetype:)
227
- nh = {}
228
- nh['content_type'] = mimetype
227
+ nh = { 'content_type' => mimetype }
229
228
  new_from_hson_h(nh)
230
229
  end
231
230
 
@@ -252,11 +251,11 @@ module OData
252
251
 
253
252
  if slug_field
254
253
 
255
- new_entity.set_fields({ slug_field => filename},
256
- data_fields,
257
- missing: :skip)
254
+ new_entity.set_fields({ slug_field => filename },
255
+ data_fields,
256
+ missing: :skip)
258
257
  end
259
-
258
+
260
259
  # to_one rels are create with FK data set on the parent entity
261
260
  if parent
262
261
  odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
@@ -1,18 +1,32 @@
1
1
  require 'odata/error.rb'
2
2
 
3
- # Ordering with ruby expression
4
- module OrderWithRuby
5
- # this module requires the @fn attribute to exist where it is used
6
- def fn=(fnam)
7
- @fn = fnam
8
- @fn_tab = fnam.split('/').map(&:to_sym)
9
- end
10
- end
11
-
12
3
  # all ordering related classes in our OData module
13
4
  module OData
14
5
  # base class for ordering
15
- class Order
6
+ class OrderBase
7
+ # re-useable empty ordering (idempotent)
8
+ EmptyOrder = new
9
+
10
+ # input : the OData order string
11
+ # returns a Order object that should have a apply_to(cx) method
12
+ def self.factory(orderstr, jh)
13
+ orderstr.nil? ? EmptyOrder : MultiOrder.new(orderstr, jh)
14
+ end
15
+
16
+ def empty?
17
+ true
18
+ end
19
+
20
+ def parse_error?
21
+ false
22
+ end
23
+
24
+ def apply_to_dataset(dtcx)
25
+ dtcx
26
+ end
27
+ end
28
+
29
+ class Order < OrderBase
16
30
  attr_reader :oarg
17
31
  def initialize(ostr, jh)
18
32
  ostr.strip!
@@ -21,23 +35,14 @@ module OData
21
35
  build_oarg if @orderp
22
36
  end
23
37
 
24
- class << self
25
- attr_reader :regexp
26
- end
27
-
28
- # input : the filter string
29
- # returns a filter object that should have a apply_to(cx) method
30
- def self.new_by_parse(orderstr, jh)
31
- Order.new_full_match_complexpr(orderstr, jh)
32
- end
33
-
34
- # handle with Sequel
35
- def self.new_full_match_complexpr(orderstr, jh)
36
- ComplexOrder.new(orderstr, jh)
38
+ def empty?
39
+ false
37
40
  end
38
41
 
39
42
  def apply_to_dataset(dtcx)
40
- dtcx
43
+ # Warning, we need order_append, simply order(oarg) overwrites
44
+ # previous one !
45
+ dtcx.order_append(@oarg)
41
46
  end
42
47
 
43
48
  def build_oarg
@@ -60,26 +65,31 @@ module OData
60
65
  end
61
66
 
62
67
  # complex ordering logic
63
- class ComplexOrder < Order
68
+ class MultiOrder < Order
64
69
  def initialize(orderstr, jh)
65
70
  super
66
71
  @olist = []
67
72
  @jh = jh
68
- return unless orderstr
69
-
70
- @olist = orderstr.split(',').map do |ostr|
71
- oo = Order.new(ostr, @jh)
72
- oo.oarg
73
- end
73
+ @orderstr = orderstr.dup
74
+ @olist = orderstr.split(',').map { |ostr| Order.new(ostr, @jh) }
74
75
  end
75
76
 
76
77
  def apply_to_dataset(dtcx)
77
- @olist.each { |oarg|
78
- # Warning, we need order_append, simply order(oarg) overwrites
79
- # previous one !
80
- dtcx = dtcx.order_append(oarg)
81
- }
78
+ @olist.each { |osingl| dtcx = osingl.apply_to_dataset(dtcx) }
82
79
  dtcx
83
80
  end
81
+
82
+ def parse_error?
83
+ @orderstr.split(',').each do |pord|
84
+ pord.strip!
85
+ qualfn, dir = pord.split(/\s/)
86
+ qualfn.strip!
87
+ dir.strip! if dir
88
+ return true unless @jh.start_model.attrib_path_valid? qualfn
89
+ return true unless [nil, 'asc', 'desc'].include? dir
90
+ end
91
+
92
+ false
93
+ end
84
94
  end
85
95
  end
@@ -5,41 +5,41 @@ module Rack
5
5
  super
6
6
  end
7
7
 
8
- # Handle https://github.com/rack/rack/pull/1526
8
+ # Handle https://github.com/rack/rack/pull/1526
9
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
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
43
 
44
44
  def batch_log(env, status, header, began_at)
45
45
  length = extract_content_length(header)