# frozen_string_literal: true require 'global_registry' require 'global_registry_bindings/workers/delete_entity_worker' module GlobalRegistry #:nodoc: module Bindings #:nodoc: module Entity #:nodoc: module PushRelationshipMethods extend ActiveSupport::Concern def relationship global_registry_relationship(type) end def push_relationship_to_global_registry return unless valid_update? ensure_related_entities_have_global_registry_ids! push_global_registry_relationship_type create_relationship_in_global_registry end def valid_update? # We can't update relationship if related model is missing, but we may need to delete if relationship.related.nil? if relationship.related_id_value.nil? && relationship.id_value # Delete relationship if it exists and the related id_value is missing delete_relationship_from_global_registry(false) return false end # Do nothing if related model is missing and related_binding is :entity, :remote binding allows # empty related model return false if relationship.related_binding == :entity end true end def create_relationship_in_global_registry entity = put_relationship_to_global_registry relationship.id_value = global_registry_relationship_entity_id_from_entity entity model.update_column( # rubocop:disable Rails/SkipsModelValidations relationship.id_column, relationship.id_value ) end def global_registry_relationship_entity_id_from_entity(entity) relationships = Array.wrap entity.dig( 'entity', relationship.primary_type.to_s, "#{relationship.related_name}:relationship" ) relationships.detect do |rel| cid = rel['client_integration_id'] cid = cid['value'] if cid.is_a?(Hash) cid == relationship.client_integration_id.to_s end&.dig('relationship_entity_id') end def ensure_related_entities_have_global_registry_ids! return if relationship.primary_id_value && relationship.related_id_value # Enqueue push_entity worker for related entities missing global_registry_id and retry relationship push names = [] unless relationship.primary_id_value names << push_primary_to_global_registry end unless relationship.related_id_value names << push_related_to_global_registry end raise GlobalRegistry::Bindings::RelatedEntityMissingGlobalRegistryId, "#{model.class.name}(#{model.id}) has related entities [#{names.compact.join ', '}] missing " \ 'global_registry_id; will retry.' end def push_primary_to_global_registry model = relationship.primary if relationship.primary_binding == :entity model.push_entity_to_global_registry_async else model.push_relationships_to_global_registry_async(relationship.primary_binding) end "#{model.class.name}(#{model.id})" end def push_related_to_global_registry model = relationship.related model.push_entity_to_global_registry_async "#{model.class.name}(#{model.id})" end def relationship_entity { entity: { relationship.primary_type => { "#{relationship.related_name}:relationship" => model.relationship_attributes_to_push(type) .merge(relationship.related_type => relationship.related_id_value) }, client_integration_id: relationship.primary.id } } end def put_relationship_to_global_registry GlobalRegistry::Entity.put( relationship.primary_id_value, relationship_entity, params: { full_response: true, fields: "#{relationship.related_name}:relationship" } ) rescue RestClient::BadRequest => e response = JSON.parse(e.response.body) raise unless response['error'] =~ /^Validation failed:.*already exists$/i # Delete relationship entity and retry on 400 Bad Request (client_integration_id already exists) delete_relationship_from_global_registry end def delete_relationship_from_global_registry(and_retry = true) GlobalRegistry::Bindings::Workers::DeleteEntityWorker.new.perform(relationship.id_value) model.update_column( # rubocop:disable Rails/SkipsModelValidations relationship.id_column, nil ) return unless and_retry raise GlobalRegistry::Bindings::RelatedEntityExistsWithCID, "#{model.class.name}(#{model.id}) #{relationship.related_name}" \ ':relationship already exists with client_integration_id(' \ "#{relationship.client_integration_id}). Will delete and retry." end end end end end