module Isomorfeus module Data class ObjectAccelerator def self.finalize(acc) proc { acc.close_store } end attr_reader :object_class, :object_class_name attr_accessor :store def initialize(ruby_class) @object_class = ruby_class @object_class_name = ruby_class.name @class_cache = !Isomorfeus.development? @store_path = File.expand_path(File.join(Isomorfeus.data_documents_path, "#{@object_class_name.underscore}")) open_store ObjectSpace.define_finalizer(self, self.class.finalize(self)) end def serialize(o) Oj.dump(o, mode: :strict) end def unserialize(s) Oj.load(s, mode: :strict) end def destroy_store close_store rescue nil FileUtils.rm_rf(@store_path) end def close_store @store.close end def exist?(sid_s:) !!get_object_id(sid_s) end def exist_by_field?(field:, value:) top_docs = @store.search("#{field}:\"#{serialize(value)}\"") top_docs.total_hits > 0 end def search_each(query, options, &block) @store.search_each(query, options, &block) rescue => e return nil if e.message.include?('frt_sort.c') raise e end def each(&block) @store.each { |doc| block.call(doc[:key]) } end def create(sid_s:, fields:, level: 10) id = get_object_id(sid_s) return false if id data = fields.to_h do |k, v| [k, serialize(v)] end data[:_revision] = serialize(1) data[:key] = sid_s @store.add_document(data) true rescue => e @store.flush raise e if level < 1 create(sid_s: sid_s, fields: fields, level: level - 1) end def destroy(sid_s:) id = get_object_id(sid_s) @store.delete(id) if id end def load(sid_s: nil, id: nil) id = get_object_id(sid_s) if sid_s return nil unless id data = @store.doc(id)&.to_h if data data.delete(:key) data = data.to_h do |k, v| [k, unserialize(v)] end end data end def field(sid_s:, field:) id = get_object_id(sid_s) if sid_s return nil unless id doc = @store.doc(id) v = doc[field] unserialize(v) if v end def load_sid_s(id:) doc = @store.doc(id) return doc[:key] if doc end def revision(sid_s:) id = get_object_id(sid_s) doc = @store.doc(id) if id return unserialize(doc[:_revision]) if doc end def save(sid_s:, fields:, revision:, id: nil, level: 10) id = get_object_id(sid_s) unless id return false unless id data = fields.to_h do |k, v| [k, serialize(v)] end data[:key] = sid_s data[:_revision] = serialize(revision) @store.update(id, data) true rescue => e @store.flush raise e if level < 1 save(sid_s: sid_s, fields: fields, revision: revision, id: id, level: level - 1) end def escape(string) # special characters must be escaped, characters taken from the ferret query parser documentation string.gsub(/([\\\&\:\(\)\[\]\{\}\!\"\~\^\|\<\>\=\*\?\+\-\s])/, '\\\\\1') end private def get_object_id(sid_s) top_docs = @store.search("key:\"#{sid_s}\"", limit: 1) top_docs.hits[0].doc if top_docs.total_hits == 1 end def open_store FileUtils.mkdir_p(Isomorfeus.data_documents_path) unless Dir.exist?(Isomorfeus.data_documents_path) field_infos = Isomorfeus::Ferret::Index::FieldInfos.new(store: :yes, index: :no, term_vector: :no) @object_class.field_options.each { |field, options| field_infos.add_field(field, options) } field_infos.add_field(:key, store: :yes, index: :yes, term_vector: :yes) @store = Isomorfeus::Ferret::Index::Index.new(path: @store_path, key: :key, auto_flush: true, lock_retry_time: 5, field_infos: field_infos) end end end end