isomorfeus-data 2.5.5 → 22.9.0.rc1

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.
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