module ActiveGraph::Relationship module Persistence extend ActiveSupport::Concern include ActiveGraph::Shared::Cypher::RelIdentifiers include ActiveGraph::Shared::Persistence class RelInvalidError < RuntimeError; end class ModelClassInvalidError < RuntimeError; end class RelCreateFailedError < RuntimeError; end def from_node_identifier @from_node_identifier || :from_node end def to_node_identifier @to_node_identifier || :to_node end def from_node_identifier=(id) @from_node_identifier = id.to_sym end def to_node_identifier=(id) @to_node_identifier = id.to_sym end def cypher_identifier @cypher_identifier || :rel end def save(*) create_or_update end def save!(*args) save(*args) or fail(RelInvalidError, inspect) # rubocop:disable Style/AndOr end # Increments concurrently a numeric attribute by a centain amount # @param [Symbol, String] attribute name of the attribute to increment # @param [Integer, Float] by amount to increment def concurrent_increment!(attribute, by = 1) increment_by_query! query_as(:r), attribute, by, :r end def create_model validate_node_classes! delete_has_one_rel rel = _create_rel return self unless rel.respond_to?(:properties) init_on_load(rel, from_node, to_node, @type) true end def delete_has_one_rel to_node.delete_reverse_has_one_relationship(self, :in, from_node) if to_node.persisted? from_node.delete_reverse_has_one_relationship(self, :out, to_node) if from_node.persisted? end def query_as(var) # This should query based on the nodes, not the rel neo_id, I think # Also, picky point: Should the var be `n`? self.class.query_as(neo_id, var) end module ClassMethods # Creates a new relationship between objects # @param [Hash] args the properties the new relationship should have def create(*args) new(*args).tap(&:save) end # Same as #create, but raises an error if there is a problem during save. def create!(*args) new(*args).tap(&:save!) end def create_method creates_unique? ? :create_unique : :create end def load_entity(id) query_as(id).pluck(:r).first end def query_as(neo_id, var = :r) ActiveGraph::Base.new_query.match("()-[#{var}]->()").where(var => { neo_id: }) end end def create_method self.class.create_method end private def destroy_query query_as(:r).delete(:r) end def validate_node_classes! [from_node, to_node].each do |node| type = from_node == node ? :_from_class : :_to_class type_class = self.class.send(type) unless valid_type?(type_class, node) fail ModelClassInvalidError, type_validation_error_message(node, type_class) end end end def valid_type?(type_object, node) case type_object when false, :any true when Array type_object.any? { |c| valid_type?(c, node) } else node.class.mapped_label_names.include?(type_object.to_s.constantize.mapped_label_name) end end def type_validation_error_message(node, type_class) "Node class was #{node.class} (#{node.class.object_id}), expected #{type_class} (#{type_class.object_id})" end def _create_rel factory = QueryFactory.new(from_node, to_node, self) factory.build! factory.unwrapped_rel end end end