isomorfeus-data 2.5.4 → 22.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 198fe12b353be42d725a63ccd4495052c19a20a24e1f4223c3a2d4ac9c39fe4a
4
- data.tar.gz: bd01e302b472b1a1c847c5c49c5139edcb619faa48adfcdc3fd7e49996fcfaec
3
+ metadata.gz: 700fca91209424c5ecca86fe50ca2a5eec4edc824032dfd811bf354082b2d465
4
+ data.tar.gz: 3dcf6385c0894f78345aacb8872f83eeaed23409bddeaf0d3253afceabc60f23
5
5
  SHA512:
6
- metadata.gz: 410185c046e6c116eeca7e87fc84813f1961e23fc822f487714ab367f7e0f4f8e2e9090206138d0a0de43813420b8c9efa351aa099c8e2582e41f4ae35aa9216
7
- data.tar.gz: 7daa859033b8d5dd50d0dded8e3bcf3c2447ee825f0450358060ccee86ba32b55ea5a7cddad3e963d268db24c1545da9e7d56b1adc5e0ddca207d75c78d5ffc6
6
+ metadata.gz: 5811fe438c1ecd96f1f41da3352273c9d96c1b851e95e80496baacd9d9a905101b05288127aeaaf2b0fd458fdb989afa7de6dff200aa88e47c895428fd9f70a5
7
+ data.tar.gz: b283f5c47616218aa5c688418ff7e444e03346286a4f1968b527dd35d7de4d04bf548f17e3669f491c33f984944cb46ef19b31e259391d3e364c41f1e47d564c
@@ -1,12 +1,9 @@
1
1
  module Isomorfeus
2
2
  # available settings
3
- class << self
4
3
  if RUBY_ENGINE == 'opal'
5
- def instance_from_sid(sid)
6
- data_class = cached_data_class(sid[0])
7
- data_class.new(key: sid[1], _loading: true)
8
- end
4
+ add_client_option(:api_files_path)
9
5
 
6
+ class << self
10
7
  def cached_data_classes
11
8
  @cached_data_classes ||= `{}`
12
9
  end
@@ -16,12 +13,9 @@ module Isomorfeus
16
13
  return cached_data_classes.JS[class_name] if cached_data_classes.JS[class_name]
17
14
  cached_data_classes.JS[class_name] = "::#{class_name}".constantize
18
15
  end
19
- else
20
- def instance_from_sid(sid)
21
- data_class = cached_data_class(sid[0])
22
- data_class.new(key: sid[1])
23
- end
24
-
16
+ end
17
+ else
18
+ class << self
25
19
  def cached_data_classes
26
20
  @cached_data_classes ||= {}
27
21
  end
@@ -44,18 +38,6 @@ module Isomorfeus
44
38
  valid_data_classes[raw_class_name(klass)] = true
45
39
  end
46
40
 
47
- def valid_file_classes
48
- @valid_file_classes ||= {}
49
- end
50
-
51
- def valid_file_class_name?(class_name)
52
- valid_file_classes.key?(class_name)
53
- end
54
-
55
- def add_valid_file_class(klass)
56
- valid_file_classes[raw_class_name(klass)] = true
57
- end
58
-
59
41
  attr_accessor :storage_path
60
42
  attr_accessor :files_path
61
43
 
@@ -64,10 +46,9 @@ module Isomorfeus
64
46
 
65
47
  attr_accessor :data_object_envs_path
66
48
  attr_accessor :data_object_idxs_path
49
+ attr_accessor :api_files_path
67
50
  end
68
- end
69
51
 
70
- if RUBY_ENGINE != 'opal'
71
52
  self.storage_path = File.expand_path(File.join(Isomorfeus.root, 'storage', Isomorfeus.env))
72
53
  self.files_path = File.expand_path(File.join(self.storage_path, 'files'))
73
54
 
@@ -75,4 +56,6 @@ module Isomorfeus
75
56
  self.ferret_path = File.expand_path(File.join(self.storage_path, 'ferret'))
76
57
  self.data_documents_path = File.expand_path(File.join(self.ferret_path, 'documents'))
77
58
  end
59
+
60
+ self.api_files_path = '/files/'
78
61
  end
@@ -0,0 +1,291 @@
1
+ module Isomorfeus
2
+ module Data
3
+ module Enhancer
4
+ if RUBY_ENGINE == 'opal'
5
+ OBJECT_ACTION_TYPES = %w[DATA_LOAD DATA_SAVE DATA_CREATE DATA_SEARCH DATA_DESTROY]
6
+ OBJECT_MERGE_TYPES = %w[DATA_SAVE DATA_CREATE]
7
+
8
+ CLIENT_OBJECT_ENHANCER = proc do |action, next_enh|
9
+ if OBJECT_ACTION_TYPES.include?(action[:type])
10
+ Isomorfeus.store.promise_action(action)
11
+ if OBJECT_MERGE_TYPES.include?(action[:type])
12
+ action = { type: 'DATA_MERGE', data: { action[:class_name] => { action[:key] => { fields: action[:fields], revision: action[:revision] }}}} # optimistic
13
+ end
14
+ end
15
+ next_enh.call(action)
16
+ end
17
+
18
+ FILE_ACTION_TYPES = %w[FILE_LOAD FILE_SAVE FILE_CREATE FILE_DESTROY]
19
+ FILE_MERGE_TYPES = %w[FILE_SAVE FILE_CREATE]
20
+
21
+ CLIENT_FILE_ENHANCER = proc do |action, next_enh|
22
+ if FILE_ACTION_TYPES.include?(action[:type])
23
+ Isomorfeus.store.promise_action(action)
24
+ if FILE_MERGE_TYPES.include?(action[:type])
25
+ action = { type: 'DATA_MERGE', data: { action[:class_name] => { action[:key] => { data_uri: action[:data_uri], revision: action[:revision] }}}} # optimistic
26
+ end
27
+ end
28
+ next_enh.call(action)
29
+ end
30
+
31
+ def self.add_enhancer_to_store
32
+ ::Redux::Store.add_middleware(CLIENT_OBJECT_ENHANCER)
33
+ ::Redux::Store.add_middleware(CLIENT_FILE_ENHANCER)
34
+ end
35
+ else # RUBY_ENGINE
36
+ SERVER_OBJECT_ENHANCER = proc do |action, next_enh|
37
+ action_type = action[:type]
38
+ begin
39
+ if action_type == 'DATA_LOAD'
40
+ item_class_name = action[:class_name]
41
+ item_key = action[:key]
42
+ item_class = get_verified_class(item_class_name)
43
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :load, key: item_key)
44
+ sid_s = SID.new(item_class_name, item_key).to_s
45
+ loaded_data = item_class.object_accelerator.load(sid_s: sid_s) || {}
46
+ revision = loaded_data.delete(:_revision)
47
+ item_class.field_conditions.each do |field, options|
48
+ loaded_data.delete(field) if options[:server_only]
49
+ end
50
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision }}}}
51
+ elsif action_type == 'DATA_SAVE'
52
+ item_class_name = action[:class_name]
53
+ item_key = action[:key]
54
+ item_class = get_verified_class(item_class_name)
55
+ item_fields = action[:fields]
56
+ item_fields.transform_keys!(&:to_sym)
57
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :save, { key: item_key, fields: item_fields })
58
+ sid_s = SID.new(item_class_name, item_key).to_s
59
+ stored_revision = item_class.object_accelerator.revision(sid_s: sid_s)
60
+ if stored_revision == action[:revision]
61
+ revision = stored_revision + 1
62
+ instance_uuid = action[:instance_uuid]
63
+ if item_class.before_save_block
64
+ res = item_class.before_save_block.call(key: item_key, fields: item_fields)
65
+ if res.is_a?(Hash)
66
+ item_fields = res
67
+ item_fields.transform_keys!(&:to_sym)
68
+ convert_sids(item_class, item_fields)
69
+ end
70
+ end
71
+ if item_class.object_accelerator.save(sid_s: sid_s, fields: item_fields, revision: revision)
72
+ item_class.after_save_block.call(key: item_key, fields: item_fields) if item_class.after_save_block
73
+ else
74
+ instance_uuid = nil
75
+ end
76
+ else
77
+ revision = 0
78
+ instance_uuid = nil
79
+ end
80
+ loaded_data = item_class.object_accelerator.load(sid_s: sid_s)
81
+ revision = loaded_data.delete(:_revision)
82
+ item_class.field_conditions.each do |field, options|
83
+ loaded_data.delete(field) if options[:server_only]
84
+ end
85
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision, instance_uuid: instance_uuid }}}}
86
+ elsif action_type == 'DATA_CREATE'
87
+ item_class_name = action[:class_name]
88
+ item_key = action[:key]
89
+ item_class = get_verified_class(item_class_name)
90
+ item_fields = action[:fields]
91
+ item_fields.transform_keys!(&:to_sym)
92
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :create, { key: item_key, fields: item_fields })
93
+ sid_s = SID.new(item_class_name, item_key).to_s
94
+ stored_revision = item_class.object_accelerator.revision(sid_s: sid_s)
95
+ if stored_revision.nil?
96
+ revision = 1
97
+ instance_uuid = action[:instance_uuid]
98
+ if item_class.before_create_block
99
+ res = item_class.before_create_block.call(key: item_key, fields: item_fields)
100
+ if res.is_a?(Hash)
101
+ item_fields = res
102
+ item_fields.transform_keys!(&:to_sym)
103
+ convert_sids(item_class, item_fields)
104
+ end
105
+ end
106
+ if item_class.object_accelerator.create(sid_s: sid_s, fields: item_fields)
107
+ item_class.after_create_block.call(key: item_key, fields: item_fields) if item_class.after_create_block
108
+ else
109
+ stored_revision = 0
110
+ instance_uuid = nil
111
+ end
112
+ else
113
+ revision = stored_revision
114
+ instance_uuid = nil
115
+ end
116
+ loaded_data = item_class.object_accelerator.load(sid_s: sid_s)
117
+ revision = loaded_data.delete(:_revision)
118
+ item_class.field_conditions.each do |field, options|
119
+ loaded_data.delete(field) if options[:server_only]
120
+ end
121
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision, instance_uuid: instance_uuid }}}}
122
+ elsif action_type == 'DATA_SEARCH'
123
+ item_class_name = action[:class_name]
124
+ item_class = get_verified_class(item_class_name)
125
+ query = action[:query].to_sym
126
+ params = action[:params]
127
+ raise "Invalid params #{params}!" unless params.nil? || params.is_a?(Hash)
128
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :search, { query: query, params: params })
129
+ result = []
130
+ query_string = item_class.queries.dig(query, :query_string)
131
+ raise "Invalid query '#{query}'!" unless query_string
132
+ options = item_class.queries[query][:options] || {}
133
+ oa = item_class.object_accelerator
134
+ full_query = if params.is_a?(Hash)
135
+ if options[:escape_params]
136
+ escaped_params = params.to_h do |k,v|
137
+ [k, oa.escape(v)]
138
+ end
139
+ query_string % escaped_params
140
+ else
141
+ query_string % params
142
+ end
143
+ else
144
+ query_string
145
+ end
146
+ oa.search_each(full_query, options) do |id|
147
+ sid_s = oa.load_sid_s(id: id)
148
+ result << sid_s if sid_s
149
+ end
150
+ action = { type: 'DATA_MERGE', data: { queries: { item_class_name => { query => { Oj.dump(params, mode: :strict) => result }}}}}
151
+ elsif action_type == 'DATA_DESTROY'
152
+ item_class_name = action[:class_name]
153
+ item_key = action[:key]
154
+ item_class = get_verified_class(item_class_name)
155
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :destroy, key: item_key)
156
+ item_class.before_destroy_block.call(key: item_key) if item_class.before_destroy_block
157
+ item_class.object_accelerator.destroy(sid_s: SID.new(item_class_name, item_key).to_s)
158
+ item_class.after_destroy_block.call(key: item_key) if item_class.after_destroy_block
159
+ action = { type: 'DATA_SET', class_name: item_class_name, key: item_key, data: { revision: -1 }}
160
+ end
161
+ rescue => e
162
+ STDERR.puts "#{e.message}\n#{e.backtrace&.join("\n")}"
163
+ # do nothing
164
+ end
165
+ next_enh.call(action)
166
+ end
167
+
168
+ SERVER_FILE_ENHANCER = proc do |action, next_enh|
169
+ action_type = action[:type]
170
+ begin
171
+ if action_type == 'FILE_LOAD'
172
+ item_class_name = action[:class_name]
173
+ item_key = action[:key]
174
+ item_class = get_verified_class(item_class_name)
175
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :load, key: item_key)
176
+ sid = SID.new(item_class_name, item_key)
177
+ data, meta = item_class.file_accelerator.load(sid: sid)
178
+ item_hash = {}
179
+ if data
180
+ content_type = Marcel::MimeType.for(data)
181
+ data_uri = item_class.format_data_uri(content_type, data)
182
+ revision = meta[:revision]
183
+ item_hash = { data_uri: data_uri, revision: revision }
184
+ end
185
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => item_hash }}}
186
+ elsif action_type == 'FILE_SAVE'
187
+ item_class_name = action[:class_name]
188
+ item_key = action[:key]
189
+ item_class = get_verified_class(item_class_name)
190
+ data_uri = action[:data_uri]
191
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :save, { key: item_key, data_uri: data_uri })
192
+ revision = action[:revision]
193
+ sid = SID.new(item_class_name, item_key)
194
+ meta = item_class.file_accelerator.meta(sid: sid)
195
+ stored_revision = meta[:revision]
196
+ if stored_revision == action[:revision]
197
+ revision = stored_revision + 1
198
+ instance_uuid = action[:instance_uuid]
199
+ if item_class.before_save_block
200
+ res = item_class.before_save_block.call(key: item_key, data_uri: data_uri)
201
+ data_uri = res[:data_uri] if res.is_a?(Hash) && res.key?(:data_uri)
202
+ end
203
+ if item_class.file_accelerator.save(sid: sid, data: URI::Data.new(data_uri).data, meta: { revision: revision })
204
+ item_class.after_save_block.call(key: item_key, data_uri: data_uri) if item_class.after_save_block
205
+ else
206
+ instance_uuid = nil
207
+ end
208
+ else
209
+ revision = 0
210
+ instance_uuid = nil
211
+ end
212
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { data_uri: data_uri, revision: revision, instance_uuid: instance_uuid }}}}
213
+ elsif action_type == 'FILE_CREATE'
214
+ item_class_name = action[:class_name]
215
+ item_key = action[:key]
216
+ item_class = get_verified_class(item_class_name)
217
+ data_uri = action[:data_uri]
218
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :create, { key: item_key, data_uri: data_uri })
219
+ sid = SID.new(item_class_name, item_key)
220
+ meta = item_class.file_accelerator.meta(sid: sid)
221
+ stored_revision = meta ? meta[:revision] : nil
222
+ if stored_revision.nil?
223
+ revision = 1
224
+ instance_uuid = action[:instance_uuid]
225
+ if item_class.before_create_block
226
+ res = item_class.before_create_block.call(key: item_key, data_uri: data_uri)
227
+ data_uri = res[:data_uri] if res.is_a?(Hash) && res.key?(:data_uri)
228
+ end
229
+ if item_class.file_accelerator.create(sid: sid, data: URI::Data.new(data_uri).data, meta: { revision: revision })
230
+ item_class.after_create_block.call(key: item_key, data_uri: data_uri) if item_class.after_create_block
231
+ else
232
+ stored_revision = 0
233
+ instance_uuid = nil
234
+ end
235
+ else
236
+ revision = stored_revision
237
+ instance_uuid = nil
238
+ end
239
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { data_uri: data_uri, revision: revision, instance_uuid: instance_uuid }}}}
240
+ elsif action_type == 'FILE_DESTROY'
241
+ item_class_name = action[:class_name]
242
+ item_key = action[:key]
243
+ item_class = get_verified_class(item_class_name)
244
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :destroy, key: item_key)
245
+ item_class.before_destroy_block.call(key: item_key) if item_class.before_destroy_block
246
+ item_class.file_accelerator.destroy(sid: SID.new(item_class_name, item_key))
247
+ item_class.after_destroy_block.call(key: item_key) if item_class.after_destroy_block
248
+ action = { type: 'DATA_SET', class_name: item_class_name, key: item_key, data: { revision: -1 }}
249
+ end
250
+ rescue => e
251
+ STDERR.puts "#{e.message}\n#{e.backtrace&.join("\n")}"
252
+ # do nothing
253
+ end
254
+ next_enh.call(action)
255
+ end
256
+
257
+ class << self
258
+ def convert_sids(item_class, item_fields)
259
+ item_class.field_types.each do |field, type|
260
+ if type == :object
261
+ field_s = field.to_s
262
+ v = item_fields[field_s]
263
+ if v.is_a?(SID)
264
+ item_fields[field_s] = v.to_s
265
+ elsif v.is_a?(Array)
266
+ item_fields[field_s].map! do |o|
267
+ o.is_a?(SID) ? o.to_s : o
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ def get_verified_class(item_class_name)
275
+ raise "Invalid data class!" unless Isomorfeus.valid_data_class_name?(item_class_name)
276
+ item_class = Isomorfeus.cached_data_class(item_class_name)
277
+ end
278
+
279
+ def authorization_error!(action)
280
+ raise "Not authorized!#{" (action: #{action})" if Isomorfeus.development?}"
281
+ end
282
+
283
+ def add_enhancer_to_store
284
+ ::Redux::Store.add_middleware(SERVER_OBJECT_ENHANCER)
285
+ ::Redux::Store.add_middleware(SERVER_FILE_ENHANCER)
286
+ end
287
+ end
288
+ end # RUBY_ENGINE
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,95 @@
1
+ module Isomorfeus
2
+ module Data
3
+ class FileAccelerator
4
+ attr_reader :object_class, :object_class_name
5
+
6
+ def initialize(ruby_class)
7
+ @object_class = ruby_class
8
+ @object_class_name = ruby_class.name
9
+ @store_path = File.expand_path(File.join(Isomorfeus.files_path, "#{@object_class_name.underscore}"))
10
+ FileUtils.mkdir_p(@store_path) unless Dir.exist?(@store_path)
11
+ end
12
+
13
+ def serialize(o)
14
+ Oj.dump(o, mode: :strict)
15
+ end
16
+
17
+ def unserialize(s)
18
+ Oj.load(s, mode: :strict)
19
+ end
20
+
21
+ def destroy_store
22
+ close_store
23
+ FileUtils.rm_rf(@store_path)
24
+ end
25
+
26
+ def meta(sid:)
27
+ _path, meta_path = check_and_prepare_paths(key: sid.key)
28
+ if File.exist?(meta_path)
29
+ meta = unserialize(File.binread(meta_path))
30
+ meta.transform_keys!(&:to_sym)
31
+ end
32
+ end
33
+
34
+ def exist?(sid:)
35
+ path, meta_path = check_and_prepare_paths(key: sid.key)
36
+ File.exist?(path)
37
+ end
38
+
39
+ def create(sid:, data:, meta:)
40
+ path, meta_path = check_and_prepare_paths(key: sid.key)
41
+ File.binwrite(path, data)
42
+ File.binwrite(meta_path, serialize(meta))
43
+ true
44
+ end
45
+ alias save create
46
+
47
+ def destroy(sid:)
48
+ path, meta_path = check_and_prepare_paths(key: sid.key)
49
+ FileUtils.rm_f([path, meta_path])
50
+ true
51
+ end
52
+
53
+ def load(sid:)
54
+ path, meta_path = check_and_prepare_paths(key: sid.key)
55
+ begin
56
+ if File.exist?(meta_path)
57
+ meta = unserialize(File.binread(meta_path))
58
+ meta.transform_keys!(&:to_sym)
59
+ else
60
+ meta = {}
61
+ end
62
+ [File.binread(path), meta]
63
+ rescue Errno::ENOENT
64
+ [nil, nil]
65
+ end
66
+ end
67
+
68
+ def load_data(sid:)
69
+ path, _meta_path = check_and_prepare_paths(key: sid.key)
70
+ begin
71
+ File.binread(path)
72
+ rescue Errno::ENOENT
73
+ nil
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def check_and_prepare_paths(key:)
80
+ Isomorfeus.raise_error(message: 'Invalid key (contains ".." or "\\")') if key.include?('..') || key.include?('\\')
81
+ elements = key.split('/')
82
+ Isomorfeus.raise_error(message: 'Invalid key (contains more than 2 slashes "/")') if elements.size > 3
83
+ file = elements.pop
84
+ path = if elements.size > 0
85
+ dir_path = ::File.expand_path(::File.join(@store_path, *elements))
86
+ FileUtils.mkdir_p(dir_path) unless Dir.exist?(dir_path)
87
+ File.join(dir_path, file)
88
+ else
89
+ File.join(@store_path, file)
90
+ end
91
+ [path, "#{path}__meta__"]
92
+ end
93
+ end
94
+ end
95
+ end
@@ -11,7 +11,7 @@ module Isomorfeus
11
11
  def initialize(ruby_class)
12
12
  @object_class = ruby_class
13
13
  @object_class_name = ruby_class.name
14
- @class_cache = Isomorfeus.production?
14
+ @class_cache = !Isomorfeus.development?
15
15
 
16
16
  @store_path = File.expand_path(File.join(Isomorfeus.data_documents_path, "#{@object_class_name.underscore}"))
17
17
  open_store
@@ -19,24 +19,16 @@ module Isomorfeus
19
19
  ObjectSpace.define_finalizer(self, self.class.finalize(self))
20
20
  end
21
21
 
22
- def object_from_ref(ref, already_loaded)
23
- _, iso, type_class_name, key = ref.split('---')
24
- raise "not a valid object reference '#{ref}'" unless iso == "iso-object-reference"
25
- raise "invalid data class #{type_class_name}" unless Isomorfeus.valid_data_class_name?(type_class_name)
26
- type_class = Isomorfeus.cached_data_class(type_class_name)
27
- type_class.load(key: key, _already_loaded: already_loaded)
22
+ def serialize(o)
23
+ Oj.dump(o, mode: :strict)
28
24
  end
29
25
 
30
- def serialize(obj)
31
- Oj.dump(obj, mode: :object, circular: true, class_cache: @class_cache)
32
- end
33
-
34
- def unserialize(v)
35
- Oj.load(v, mode: :object, circular: true, class_cache: @class_cache)
26
+ def unserialize(s)
27
+ Oj.load(s, mode: :strict)
36
28
  end
37
29
 
38
30
  def destroy_store
39
- close_store
31
+ close_store rescue nil
40
32
  FileUtils.rm_rf(@store_path)
41
33
  end
42
34
 
@@ -44,97 +36,113 @@ module Isomorfeus
44
36
  @store.close
45
37
  end
46
38
 
39
+ def exist?(sid_s:)
40
+ !!get_object_id(sid_s)
41
+ end
42
+
43
+ def exist_by_field?(field:, value:)
44
+ top_docs = @store.search("#{field}:\"#{serialize(value)}\"")
45
+ top_docs.total_hits > 0
46
+ end
47
+
47
48
  def search_each(query, options, &block)
48
49
  @store.search_each(query, options, &block)
50
+ rescue => e
51
+ return nil if e.message.include?('frt_sort.c')
52
+ raise e
49
53
  end
50
54
 
51
55
  def each(&block)
52
- ft = @object_class.field_types
53
- @store.each do |doc|
54
- hash = doc.to_h do |k, v|
55
- [k, unserialize_or_load(v, ft[k], {})]
56
- end
57
- block.call hash
58
- end
56
+ @store.each { |doc| block.call(doc[:key]) }
59
57
  end
60
58
 
61
- def create_object(key, fields, already_saved)
62
- ft = @object_class.field_types
63
- hash = fields.to_h do |k, v|
64
- [k, serialize_or_save(v, ft[k], already_saved)]
59
+ def create(sid_s:, fields:, level: 10)
60
+ id = get_object_id(sid_s)
61
+ return false if id
62
+ data = fields.to_h do |k, v|
63
+ [k, serialize(v)]
65
64
  end
66
- @store.add_document(hash.merge!({key: key}))
65
+ data[:_revision] = serialize(1)
66
+ data[:key] = sid_s
67
+ @store.add_document(data)
68
+ true
69
+ rescue => e
70
+ @store.flush
71
+ raise e if level < 1
72
+ create(sid_s: sid_s, fields: fields, level: level - 1)
67
73
  end
68
74
 
69
- def destroy_object(key)
70
- id = get_object_id(key)
75
+ def destroy(sid_s:)
76
+ id = get_object_id(sid_s)
71
77
  @store.delete(id) if id
72
78
  end
73
79
 
74
- def load_object(key: nil, id: nil, already_loaded: {})
75
- hash = nil
76
- id = get_object_id(key) if key
77
- if id
78
- ft = @object_class.field_types
79
- hash = @store.doc(id)&.to_h do |k, v|
80
- [k, unserialize_or_load(v, ft[k], already_loaded)]
80
+ def load(sid_s: nil, id: nil)
81
+ id = get_object_id(sid_s) if sid_s
82
+ return nil unless id
83
+ data = @store.doc(id)&.to_h
84
+ if data
85
+ data.delete(:key)
86
+ data = data.to_h do |k, v|
87
+ [k, unserialize(v)]
81
88
  end
82
89
  end
83
- hash
90
+ data
84
91
  end
85
92
 
86
- def save_object(key, fields, already_saved)
87
- id = get_object_id(key)
88
- return create_object(key, fields, already_saved) unless id
89
- ft = @object_class.field_types
90
- hash = fields.to_h do |k, v|
91
- [k, serialize_or_save(v, ft[k], already_saved)]
92
- end
93
- @store.update(id, hash.merge!({key: key}))
94
- true
93
+ def field(sid_s:, field:)
94
+ id = get_object_id(sid_s) if sid_s
95
+ return nil unless id
96
+ doc = @store.doc(id)
97
+ v = doc[field]
98
+ unserialize(v) if v
95
99
  end
96
100
 
97
- private
98
-
99
- def unserialize_or_load(v, t, already_loaded)
100
- return unserialize(v) if t == :attribute
101
- if t == :object && v
102
- return nil if v.empty?
103
- return object_from_ref(v, already_loaded)
104
- end
105
- v
101
+ def load_sid_s(id:)
102
+ doc = @store.doc(id)
103
+ return doc[:key] if doc
106
104
  end
107
105
 
108
- def serialize_or_save(v, t, already_saved)
109
- return serialize(v) if t == :attribute
110
- return create_or_save(v, already_saved) if t == :object && v
111
- v
106
+ def revision(sid_s:)
107
+ id = get_object_id(sid_s)
108
+ doc = @store.doc(id) if id
109
+ return unserialize(doc[:_revision]) if doc
112
110
  end
113
111
 
114
- def create_or_save(v, already_saved)
115
- if get_object_id(v.key)
116
- v.save(_already_saved: already_saved)
117
- else
118
- v.create
112
+ def save(sid_s:, fields:, revision:, id: nil, level: 10)
113
+ id = get_object_id(sid_s) unless id
114
+ return false unless id
115
+ data = fields.to_h do |k, v|
116
+ [k, serialize(v)]
119
117
  end
120
- v.ref_s
118
+ data[:key] = sid_s
119
+ data[:_revision] = serialize(revision)
120
+ @store.update(id, data)
121
+ true
122
+ rescue => e
123
+ @store.flush
124
+ raise e if level < 1
125
+ save(sid_s: sid_s, fields: fields, revision: revision, id: id, level: level - 1)
121
126
  end
122
127
 
123
- def get_object_id(key)
128
+ def escape(string)
124
129
  # special characters must be escaped, characters taken from the ferret query parser documentation
125
- escaped_key = key.gsub(/([\\\&\:\(\)\[\]\{\}\!\"\~\^\|\<\>\=\*\?\+\-\s])/, '\\\\\1')
126
- top_docs = @store.search("key:\"#{escaped_key}\"", limit: 1)
127
- id = top_docs.hits[0].doc if top_docs.total_hits == 1
130
+ string.gsub(/([\\\&\:\(\)\[\]\{\}\!\"\~\^\|\<\>\=\*\?\+\-\s])/, '\\\\\1')
131
+ end
132
+
133
+ private
134
+
135
+ def get_object_id(sid_s)
136
+ top_docs = @store.search("key:\"#{sid_s}\"", limit: 1)
137
+ top_docs.hits[0].doc if top_docs.total_hits == 1
128
138
  end
129
139
 
130
140
  def open_store
131
141
  FileUtils.mkdir_p(Isomorfeus.data_documents_path) unless Dir.exist?(Isomorfeus.data_documents_path)
132
- field_infos = Isomorfeus::Ferret::Index::FieldInfos.new(store: :yes, index: :yes, term_vector: :no)
142
+ field_infos = Isomorfeus::Ferret::Index::FieldInfos.new(store: :yes, index: :no, term_vector: :no)
143
+ @object_class.field_options.each { |field, options| field_infos.add_field(field, options) }
144
+ field_infos.add_field(:key, store: :yes, index: :yes, term_vector: :yes)
133
145
  @store = Isomorfeus::Ferret::Index::Index.new(path: @store_path, key: :key, auto_flush: true, lock_retry_time: 5, field_infos: field_infos)
134
- @store.field_infos.add_field(:key, store: :yes, index: :yes, term_vector: :yes) unless @store.field_infos[:key]
135
- @object_class.field_options.each do |field, options|
136
- @store.field_infos.add_field(field, options) unless @store.field_infos[field]
137
- end
138
146
  end
139
147
  end
140
148
  end