module LucidObject module Mixin def self.included(base) base.include(Isomorfeus::Data::FieldSupport) base.extend(Isomorfeus::Data::GenericClassApi) base.include(Isomorfeus::Data::GenericInstanceApi) base.include(LucidI18n::Mixin) base.instance_exec do def escape_string(s) s.gsub(/([\\\&\:\(\)\[\]\{\}\!\"\~\^\|\<\>\=\*\?\+\-\s])/, '\\\\\1') end end def [](name) send(name) end def []=(name, val) send("#{name}=".to_sym, val) end def changed! @_changed = true end def to_transport hash = { 'fields' => _get_selected_fields } hash['revision'] = revision if revision result = { @class_name => { @key => hash }} result.deep_merge!(@class_name => { @previous_key => { new_key: @key }}) if @previous_key result end def included_items_to_transport f = fields data_hash = {} self.class.field_types.each do |field, type| if type == :object v = f[field] if v data_hash.deep_merge!(v.to_transport) if v.respond_to?(:included_items_to_transport) data_hash.deep_merge!(v.included_items_to_transport) end end end end data_hash end if RUBY_ENGINE == 'opal' def initialize(key: nil, revision: nil, fields: nil, _loading: false, attributes: nil) @key = key.nil? ? SecureRandom.uuid : key.to_s @class_name = self.class.name @class_name = @class_name.split('>::').last if @class_name.start_with?('#<') _update_paths @_revision = revision ? revision : Redux.fetch_by_path(:data_state, :revision, @class_name, @key) @_objects = {} @_changed = false loaded = loaded? fields = attributes if attributes if loaded raw_fields = Redux.fetch_by_path(*@_store_path) if `raw_fields === null` if fields _validate_fields(fields) @_changed_fields = fields else @_changed_fields = {} end elsif raw_fields && fields && ::Hash.new(raw_fields) != fields _validate_fields(fields) @_changed_fields = fields else @_changed_fields = {} end else fields = {} unless fields _validate_fields(fields) unless _loading @_changed_fields = fields end end def _load_from_store! @_changed_fields = {} @_objects = {} @_changed = false end def _update_paths @_store_path = [:data_state, @class_name, @key, :fields] end def each(&block) fields.each(&block) end else # RUBY_ENGINE Isomorfeus.add_valid_data_class(base) unless base == LucidObject::Base base.instance_exec do def instance_from_transport(instance_data, included_items_data, _already_loaded = {}) key = instance_data[self.name].keys.first ref_s = gen_ref_s(key) return _already_loaded[ref_s] if _already_loaded.key?(ref_s) revision = instance_data[self.name][key].key?('revision') ? instance_data[self.name][key]['revision'] : nil data_obj = new(key: key, revision: revision) _already_loaded[ref_s] = data_obj fields = instance_data[self.name][key].key?('fields') ? instance_data[self.name][key]['fields'].transform_keys(&:to_sym) : nil if fields field_types.each do |field, type| if type == :object sid = fields[field] if sid o_class_name = sid[0] raise "invalid data class #{type_class_name}" unless Isomorfeus.valid_data_class_name?(o_class_name) o_class = Isomorfeus.cached_data_class(o_class_name) o_data = { o_class_name => { sid[1] => included_items_data[o_class_name][sid[1]] }} if included_items_data && !included_items_data[self.name]&.key?(key) included_items_data.deep_merge!(instance_data) end fields[field] = o_class.instance_from_transport(o_data, included_items_data, _already_loaded) end end end end data_obj._validate_fields(fields) data_obj.fields.merge!(fields) data_obj end def props_from_data(instance_data) key = instance_data[self.name].keys.first revision = instance_data[self.name][key].key?('revision') ? instance_data[self.name][key]['revision'] : nil fields = instance_data[self.name][key].key?('fields') ? instance_data[self.name][key]['fields'].transform_keys!(&:to_sym) : nil LucidProps.new({ key: key, revision: revision }.merge!(fields)) end def object_accelerator @object_accelerator ||= Isomorfeus::Data::ObjectAccelerator.new(self) end def each(&block) self.object_accelerator.each do |fields| block.call self.new(key: fields.delete(:key), fields: fields) end end def search(field = nil, val = nil, options = {}, query: nil) if field && !self.field_options[field]&.fetch(:index) == :yes raise "Can only search indexed #{self.field_types{field}}s, but #{self.field_types{field}} :#{field} is not indexed!" end objs = [] query = query ? query : "#{field}:#{val}" self.object_accelerator.search_each(query, options) do |id| fields = self.object_accelerator.load_object(id: id) objs << self.new(key: fields.delete(:key), fields: fields) end objs end execute_create do |_already_saved: {}| self.key = SecureRandom.uuid unless self.key unless _already_saved.key?(self.ref_s) _already_saved[self.ref_s] = self self.class.object_accelerator.create_object(self.key, fields, _already_saved) end self end execute_destroy do |key:| self.object_accelerator.destroy_object(key.to_s) true end execute_load do |key:, _already_loaded: {}| ref_s = self.gen_ref_s(@key) if _already_loaded.key?(ref_s) _already_loaded[ref_s] else _already_loaded[ref_s] = true fields = self.object_accelerator.load_object(key: key.to_s, already_loaded: _already_loaded) obj = fields ? self.new(key: fields.delete(:key), fields: fields) : nil _already_loaded[ref_s] = obj end end execute_save do |_already_saved: {}| self.key = SecureRandom.uuid unless self.key unless _already_saved.key?(self.ref_s) _already_saved[self.ref_s] = true self.class.object_accelerator.save_object(self.key, fields, _already_saved) end self end end def initialize(key: nil, revision: nil, fields: nil, attributes: nil) @key = key.nil? ? SecureRandom.uuid : key.to_s @class_name = self.class.name @class_name = @class_name.split('>::').last if @class_name.start_with?('#<') @_revision = revision @_changed = false fields = attributes if attributes fields = {} unless fields _validate_fields(fields) if fields @_raw_fields = fields end def _unchange! @_changed = false end def each(&block) @_raw_fields.each(&block) end def reload new_instance = self.class.load(key: @key) @_raw_fields = new_instance.fields _unchange! self end end # RUBY_ENGINE end end end