safrano 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)