isomorfeus-data 2.5.5 → 22.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/lucid_file.rb ADDED
@@ -0,0 +1,137 @@
1
+ class LucidFile < LucidDataGeneric
2
+ include LucidI18nMixin
3
+
4
+ VALID_METHODS = %i[key data data_uri content_type]
5
+
6
+ class << self
7
+ def api_path(key:)
8
+ "#{Isomorfeus.api_files_path}#{self.name}/#{key}"
9
+ end
10
+
11
+ def format_data_uri(c, d)
12
+ "data:#{c};base64,#{Base64.strict_encode64(d)}"
13
+ end
14
+
15
+ def destroy(key:)
16
+ Isomorfeus.store.deferred_dispatch(type: 'FILE_DESTROY', class_name: self.name, key: key)
17
+ true
18
+ end
19
+
20
+ def load!(key:, instance: nil)
21
+ instance = self.new(key: key, _loading: true) unless instance
22
+ Isomorfeus.something_loading!
23
+ Isomorfeus.store.deferred_dispatch(type: 'FILE_LOAD', class_name: self.name, key: key)
24
+ instance
25
+ end
26
+ end
27
+
28
+ def initialize(key: nil, content_type: nil, data: nil, data_uri: nil, _loading: false)
29
+ super(key: key)
30
+ _update_paths
31
+ loaded = loaded?
32
+ @changed_uri_string = nil
33
+ if data_uri
34
+ @changed_uri_string = data_uri
35
+ elsif data || content_type
36
+ data = data.to_s unless data.is_a?(String)
37
+ if RUBY_ENGINE != 'opal'
38
+ content_type = Marcel::MimeType.for(data) unless content_type
39
+ end
40
+ @changed_uri_string = self.class.format_data_uri(content_type, data)
41
+ end
42
+ end
43
+
44
+ def create
45
+ raise 'Already created!' if revision > 0
46
+ @last_revision = 0
47
+ d = data_uri
48
+ unchange!
49
+ Isomorfeus.something_loading!
50
+ Isomorfeus.store.deferred_dispatch(type: "FILE_CREATE", class_name: @class_name, key: @key, data_uri: d, instance_uuid: instance_uuid, revision: 0)
51
+ self
52
+ end
53
+
54
+ def save
55
+ @last_revision = revision
56
+ return create if @last_revision == 0
57
+ d = data_uri
58
+ unchange!
59
+ Isomorfeus.something_loading!
60
+ Isomorfeus.store.deferred_dispatch(type: "FILE_SAVE", class_name: @class_name, key: @key, data_uri: d, instance_uuid: instance_uuid, revision: @last_revision)
61
+ self
62
+ end
63
+
64
+ def [](name)
65
+ self.send(name) if VALID_METHODS.include?(name)
66
+ end
67
+
68
+ def []=(name, value)
69
+ self.send("#{name}=".to_sym, value) if VALID_METHODS.include?(name)
70
+ end
71
+
72
+ def unchange!
73
+ super
74
+ @changed_uri_string = nil
75
+ end
76
+
77
+ def _update_paths
78
+ @store_path = [:data_state, @class_name, @key, :data_uri]
79
+ end
80
+
81
+ def content_type
82
+ data_uri_object&.content_type
83
+ end
84
+
85
+ def content_type=(c)
86
+ changed!
87
+ uri_string = @changed_uri_string ? @changed_uri_string : Isomorfeus.store.dig(*@store_path)
88
+ @changed_uri_string = self.class.format_data_uri(c, URI::Data.new(uri_string).data)
89
+ c
90
+ end
91
+
92
+ def data_uri
93
+ @changed_uri_string ? @changed_uri_string : Isomorfeus.store.dig(*@store_path)
94
+ end
95
+
96
+ def data_uri=(d)
97
+ changed!
98
+ @changed_uri_string = d
99
+ end
100
+
101
+ def data_uri_object
102
+ du = data_uri
103
+ URI::Data.new(du) if du
104
+ end
105
+
106
+ def data
107
+ data_uri_object&.data
108
+ end
109
+
110
+ def data=(d)
111
+ changed!
112
+ d = d.to_s unless d.is_a?(String)
113
+ if RUBY_ENGINE == 'opal'
114
+ ct = content_type
115
+ else
116
+ ct = Marcel::MimeType.for(d)
117
+ end
118
+ @changed_uri_string = self.class.format_data_uri(ct, d)
119
+ end
120
+
121
+ if RUBY_ENGINE != 'opal'
122
+ class << self
123
+ def inherited(base)
124
+ Isomorfeus.add_valid_data_class(base) unless 'LucidFile' == base.name
125
+ end
126
+
127
+ def file_accelerator
128
+ @file_accelerator ||= Isomorfeus::Data::FileAccelerator.new(self)
129
+ end
130
+
131
+ def exist?(key:)
132
+ raise "Not authorized!" unless current_user.authorized?(self, :load, key: key)
133
+ file_accelerator.exist?(sid: SID.new(self.name, key))
134
+ end
135
+ end
136
+ end # RUBY_ENGINE
137
+ end
@@ -0,0 +1,431 @@
1
+ class LucidObject < LucidDataGeneric
2
+ include LucidI18nMixin
3
+
4
+ class SearchResultProxy
5
+ attr_reader :query_json
6
+
7
+ def initialize(class_name, query, params)
8
+ @store_path = [:data_state, :queries, class_name, query, JSON.dump(params)]
9
+ end
10
+
11
+ def method_missing(method, *args, &block)
12
+ all.send(method, *args, &block)
13
+ end
14
+
15
+ def loaded?
16
+ !!as_sid_s
17
+ end
18
+
19
+ def as_sid_s
20
+ Isomorfeus.store.dig(*@store_path)
21
+ end
22
+
23
+ def as_keys
24
+ a = as_sid_s
25
+ return [] unless a
26
+ a.map do |sid_s|
27
+ SID.from_s(sid_s).key
28
+ end
29
+ end
30
+
31
+ def [](idx)
32
+ sid_s = as_sid_s&.fetch(idx)
33
+ LucidObject.instance_from_sid_s(sid_s) if sid_s
34
+ end
35
+
36
+ def all
37
+ a = as_sid_s
38
+ return [] unless a
39
+ a.map do |sid_s|
40
+ LucidObject.instance_from_sid_s(sid_s)
41
+ end
42
+ end
43
+
44
+ def dig(*identifiers)
45
+ all.dig(*identifiers)
46
+ end
47
+
48
+ def length
49
+ as_sid_s&.size
50
+ end
51
+ alias size length
52
+ end
53
+
54
+ OBJECT_VALIDATION_PROC = proc do |v|
55
+ fail = false
56
+ if v.is_a?(SID)
57
+ # yes, a sid
58
+ elsif v.is_a?(Array)
59
+ v.each do |e|
60
+ fail = true unless (e.nil? || e.is_a?(LucidDataGeneric) || e.is_a?(SID) || (e.is_a?(String) && SID.is?(e)))
61
+ end
62
+ elsif v.is_a?(String)
63
+ fail = true unless SID.is?(v)
64
+ elsif !v.nil? && !v.is_a?(LucidObject)
65
+ fail = true
66
+ end
67
+ raise "must be nil, a LucidObject or a Array of LucidObjects, a SID or a Array of SIDs" if fail
68
+ end
69
+
70
+ ATTRIBUTE_VALIDATION_PROC = proc do |v|
71
+ fail = false
72
+ if v.is_a?(SID) || (v.is_a?(String) && SID.is?(v)) || v.is_a?(LucidDataGeneric)
73
+ fail = true
74
+ elsif v.is_a?(Array)
75
+ v.each do |e|
76
+ fail = true if e.is_a?(SID) || (e.is_a?(String) && SID.is?(e)) || e.is_a?(LucidDataGeneric)
77
+ end
78
+ end
79
+ raise "LucidObjects or LucidFiles are not allowed as attributes!" if fail
80
+ end
81
+
82
+ class << self
83
+ def search(query, params = nil)
84
+ proxy_instance = LucidObject::SearchResultProxy.new(self.name, query, params)
85
+ Isomorfeus.something_loading!
86
+ Isomorfeus.store.deferred_dispatch(type: 'DATA_SEARCH', class_name: self.name, query: query, params: params)
87
+ proxy_instance
88
+ end
89
+
90
+ def load!(key:, instance: nil)
91
+ instance = self.new(key: key, _loading: true) unless instance
92
+ Isomorfeus.something_loading!
93
+ Isomorfeus.store.deferred_dispatch(type: 'DATA_LOAD', class_name: self.name, key: key)
94
+ instance
95
+ end
96
+
97
+ def destroy(key:)
98
+ Isomorfeus.store.deferred_dispatch(type: 'DATA_DESTROY', class_name: self.name, key: key)
99
+ true
100
+ end
101
+
102
+ def escape_string(s)
103
+ s.gsub(/([\\\&\:\(\)\[\]\{\}\!\"\~\^\|\<\>\=\*\?\+\-\s])/, '\\\\\1')
104
+ end
105
+
106
+ def instance_from_sid_s(sid_s)
107
+ sid = SID.from_s(sid_s)
108
+ data_class = Isomorfeus.cached_data_class(sid.class_name)
109
+ data_class.load(key: sid.key)
110
+ end
111
+
112
+ def query(name, query_string, options = nil)
113
+ queries[name] = { query_string: query_string, options: options }
114
+ end
115
+
116
+ def queries
117
+ @queries ||= {}
118
+ end
119
+
120
+ def field_types
121
+ # determines how the item is stored in the index
122
+ @field_types ||= {}
123
+ end
124
+
125
+ def field_options
126
+ # options for the index
127
+ @field_options ||= {}
128
+ end
129
+
130
+ def field_conditions
131
+ # conditions for validating the field data
132
+ @field_conditions ||= {}
133
+ end
134
+
135
+ def _register_field(name, type, options)
136
+ field_options[name] = {}
137
+ field_options[name][:default_boost] = options.delete(:default_boost) if options.key?(:default_boost)
138
+ field_options[name][:default_boost] = options.delete(:default_boost) if options.key?(:boost)
139
+ field_options[name][:index] = options.delete(:index) { :no }
140
+ field_options[name][:store] = options.delete(:store) { :yes }
141
+ field_options[name][:compress] = options.delete(:compress) if options.key?(:compress)
142
+ if field_options[name][:index] == :yes
143
+ field_options[name][:term_vector] = options.delete(:term_vector) { type == :text_field ? :with_positions_offsets : :no }
144
+ elsif field_options[name][:index] == :no
145
+ field_options[name][:term_vector] = :no
146
+ end
147
+ field_conditions[name] = options
148
+ field_types[name] = type
149
+ end
150
+
151
+ def field(name, type, options)
152
+ _register_field(name, type, options)
153
+ end
154
+
155
+ def text_field(name, options = {})
156
+ default_options = { is_a: String, allow_nil: true, cast: true }
157
+ field(name, :text_field, default_options.merge(options))
158
+ end
159
+
160
+ def attribute(name, options = {})
161
+ default_options = { validate_block: ATTRIBUTE_VALIDATION_PROC }
162
+ field(name, :attribute, default_options.merge(options))
163
+ end
164
+
165
+ def object(name, options = {})
166
+ default_options = { validate_block: OBJECT_VALIDATION_PROC }
167
+ field(name, :object, default_options.merge(options))
168
+ end
169
+
170
+ def validate_field!(field_name, val)
171
+ raise "no such field" unless field_options.key?(field_name)
172
+ Isomorfeus::Props::Validator.new(self.name, field_name, val, field_conditions[field_name]).validate!
173
+ end
174
+
175
+ def valid_field?(field_name, val)
176
+ validate_field!(field_name, val)
177
+ rescue
178
+ false
179
+ end
180
+
181
+ def valid_text_field?(field_name, val)
182
+ return valid_field?(field_name, val) if field_types[field_name] == :text_field
183
+ false
184
+ rescue
185
+ false
186
+ end
187
+
188
+ def valid_attribute?(attr_name, val)
189
+ return valid_field?(attr_name, val) if field_types[attr_name] == :attribute
190
+ false
191
+ rescue
192
+ false
193
+ end
194
+ alias valid_embed? valid_attribute?
195
+
196
+ def valid_object?(obj_name, val)
197
+ return valid_field?(obj_name, val) if field_types[obj_name] == :object
198
+ false
199
+ rescue
200
+ false
201
+ end
202
+ alias valid_reference? valid_object?
203
+
204
+ def validate
205
+ Isomorfeus::Props::ValidateHashProxy.new
206
+ end
207
+
208
+ def _validate_field(field_name, val)
209
+ Isomorfeus.raise_error(message: "#{self.name}: No such text_field, attribute or object declared: '#{field_name}'!") unless field_conditions.key?(field_name)
210
+ Isomorfeus::Props::Validator.new(self.name, field_name, val, field_conditions[field_name]).validated_value
211
+ end
212
+
213
+ def _validate_fields(fields)
214
+ field_names = (fields.keys + field_conditions.keys).uniq
215
+ field_names.each do |field_name|
216
+ if field_conditions[field_name].key?(:required) && field_conditions[field_name][:required] && !fields.key?(field_name)
217
+ Isomorfeus.raise_error(message: "Required #{field_types[field_name]} #{field_name} not given!")
218
+ end
219
+ fields[field_name] = _validate_field(field_name, fields[field_name])
220
+ end
221
+ end
222
+ end
223
+
224
+ def initialize(key: nil, fields: nil, attributes: nil, _loading: false)
225
+ super(key: key)
226
+ _update_paths
227
+ @objects = {}
228
+ @field_types = @self_class.field_types
229
+ loaded = loaded?
230
+ fields = attributes if attributes
231
+ if loaded
232
+ raw_fields = Isomorfeus.store.dig(*@store_path)
233
+ if raw_fields.nil?
234
+ if fields
235
+ _validate_fields(fields)
236
+ @changed_fields = fields
237
+ else
238
+ @changed_fields = {}
239
+ end
240
+ elsif raw_fields && fields && raw_fields != fields
241
+ _validate_fields(fields)
242
+ @changed_fields = fields
243
+ else
244
+ @changed_fields = {}
245
+ end
246
+ else
247
+ fields = {} unless fields
248
+ _validate_fields(fields) unless _loading
249
+ @changed_fields = fields
250
+ end
251
+ end
252
+
253
+ def create
254
+ raise 'Already created!' if revision > 0
255
+ @last_revision = 0
256
+ f = _get_fields
257
+ unchange!
258
+ Isomorfeus.something_loading!
259
+ Isomorfeus.store.deferred_dispatch(type: "DATA_CREATE", class_name: @class_name, key: @key, fields: f, instance_uuid: instance_uuid, revision: 0)
260
+ self
261
+ end
262
+
263
+ def save
264
+ @last_revision = revision
265
+ return create if @last_revision == 0
266
+ f = _get_fields
267
+ unchange!
268
+ Isomorfeus.something_loading!
269
+ Isomorfeus.store.deferred_dispatch(type: "DATA_SAVE", class_name: @class_name, key: @key, fields: f, instance_uuid: instance_uuid, revision: @last_revision)
270
+ self
271
+ end
272
+
273
+ def _update_paths
274
+ @store_path = [:data_state, @class_name, @key, :fields]
275
+ end
276
+
277
+ def _get_object(name)
278
+ return @changed_fields[name] if @changed_fields.key?(name)
279
+ return @objects[name] if @objects.key?(name)
280
+ sid_or_array = Isomorfeus.store.dig(*@store_path)&.fetch(name, nil)
281
+ return nil unless sid_or_array
282
+ if sid_or_array.is_a?(String)
283
+ @objects[name] = @self_class.instance_from_sid_s(sid_or_array)
284
+ elsif sid_or_array.is_a?(Array)
285
+ a = sid_or_array.map do |sid_s|
286
+ @self_class.instance_from_sid_s(sid_s)
287
+ end
288
+ a.compact!
289
+ @objects[name] = a
290
+ end
291
+ @objects[name]
292
+ end
293
+
294
+ def _validate_field(field_name, val)
295
+ @self_class._validate_field(field_name, val)
296
+ end
297
+
298
+ def _validate_fields(fields)
299
+ @self_class._validate_fields(fields)
300
+ end
301
+
302
+ def [](name)
303
+ return key if name == :key
304
+ type = @field_types[name]
305
+ case type
306
+ when :text_field then _get_field(name) || ''
307
+ when :attribute then _get_field(name)
308
+ when :object then _get_object(name)
309
+ else
310
+ raise "No such field '#{name}' declared!"
311
+ end
312
+ end
313
+ alias_method :fetch, :[]
314
+
315
+ def []=(name, val)
316
+ return self.key = name if name == :key
317
+ val = _validate_field(name, val)
318
+ changed!
319
+ @changed_fields[name] = val
320
+ end
321
+ alias_method :store, :[]=
322
+
323
+ def dig(name, *more_names)
324
+ value = self[name]
325
+ if value.respond_to?(:dig)
326
+ value.dig(*more_names) rescue nil
327
+ else
328
+ value
329
+ end
330
+ end
331
+
332
+ def fields
333
+ raw_fields = Isomorfeus.store.dig(*@store_path)
334
+ hash = raw_fields ? raw_fields.dup : {}
335
+ hash.merge!(@changed_fields) if @changed_fields
336
+ hash
337
+ end
338
+
339
+ def _get_fields
340
+ sel_fields = fields.dup
341
+ @self_class.field_types.each do |field, type|
342
+ if type == :object
343
+ v = self[field]
344
+ sel_fields[field] = if v.is_a?(SID)
345
+ v.to_s
346
+ elsif v.is_a?(Array)
347
+ v.map do |o|
348
+ if o.is_a?(SID)
349
+ o.to_s
350
+ elsif o.is_a?(LucidObject)
351
+ o.sid.to_s
352
+ elsif o.is_a?(String)
353
+ o
354
+ end
355
+ end
356
+ elsif v.is_a?(LucidObject)
357
+ v.sid.to_s
358
+ elsif v.is_a?(String)
359
+ v
360
+ end
361
+ end
362
+ end
363
+ sel_fields
364
+ end
365
+
366
+ def unchange!
367
+ @changed_fields = {}
368
+ super
369
+ end
370
+
371
+ def each(&block)
372
+ fields.each(&block)
373
+ end
374
+
375
+ if RUBY_ENGINE == 'opal'
376
+ def _get_field(name)
377
+ return @changed_fields[name] if @changed_fields.key?(name)
378
+ Isomorfeus.store.dig(*@store_path)&.fetch(name) { |n| @self_class.field_conditions[n][:default] }
379
+ end
380
+ else # RUBY_ENGINE
381
+ class << self
382
+ def inherited(base)
383
+ Isomorfeus.add_valid_data_class(base) unless %w[LucidObject LucidUser].include?(base.name)
384
+ end
385
+
386
+ def exist?(key:)
387
+ raise "Not authorized!" unless current_user.authorized?(self, :load, key: key)
388
+ object_accelerator.exist?(sid_s: SID.new(self.name, key).to_s)
389
+ end
390
+
391
+ def exist_by_field?(field:, value:)
392
+ raise "Not authorized!" unless current_user.authorized?(self, :search, { query: :exist_by_field , params: { field: field, value: value }})
393
+ object_accelerator.exist_by_field?(field: field, value: value)
394
+ end
395
+
396
+ def field_by_key(key:, field:)
397
+ sid_s = SID.new(self.name, key).to_s
398
+ object_accelerator.field(sid_s: sid_s, field: field)
399
+ end
400
+
401
+ def object_accelerator
402
+ @object_accelerator ||= Isomorfeus::Data::ObjectAccelerator.new(self)
403
+ end
404
+
405
+ def each(&block)
406
+ if block_given?
407
+ object_accelerator.each do |sid_s|
408
+ sid = SID.from_s(sid_s)
409
+ block.call self.load(key: sid.key)
410
+ end
411
+ else
412
+ Enumerator.new do |yielder|
413
+ object_accelerator.each do |sid_s|
414
+ sid = SID.from_s(sid_s)
415
+ yielder << self.load(key: sid.key)
416
+ end
417
+ end
418
+ end
419
+ end
420
+ alias to_enum each
421
+ end
422
+
423
+ def _get_field(name)
424
+ return @changed_fields[name] if @changed_fields.key?(name)
425
+ if @self_class.field_conditions[name][:server_only]
426
+ return @self_class.object_accelerator.field(sid_s: sid.to_s, field: name)
427
+ end
428
+ Isomorfeus.store.dig(*@store_path)&.fetch(name) { |n| @self_class.field_conditions[n][:default] }
429
+ end
430
+ end # RUBY_ENGINE
431
+ end