global-registry-bindings 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +126 -0
  4. data/lib/global_registry_bindings.rb +8 -0
  5. data/lib/global_registry_bindings/entity/delete_entity_methods.rb +22 -0
  6. data/lib/global_registry_bindings/entity/entity_methods.rb +62 -0
  7. data/lib/global_registry_bindings/entity/entity_type_methods.rb +54 -0
  8. data/lib/global_registry_bindings/entity/mdm_methods.rb +43 -0
  9. data/lib/global_registry_bindings/entity/push_entity_methods.rb +78 -0
  10. data/lib/global_registry_bindings/entity/push_relationship_methods.rb +81 -0
  11. data/lib/global_registry_bindings/entity/relationship_type_methods.rb +59 -0
  12. data/lib/global_registry_bindings/exceptions.rb +9 -0
  13. data/lib/global_registry_bindings/global_registry_bindings.rb +71 -0
  14. data/lib/global_registry_bindings/options.rb +80 -0
  15. data/lib/global_registry_bindings/options/class_options.rb +58 -0
  16. data/lib/global_registry_bindings/options/instance_options.rb +61 -0
  17. data/lib/global_registry_bindings/railtie.rb +19 -0
  18. data/lib/global_registry_bindings/version.rb +7 -0
  19. data/lib/global_registry_bindings/workers/delete_gr_entity_worker.rb +22 -0
  20. data/lib/global_registry_bindings/workers/pull_mdm_id_worker.rb +30 -0
  21. data/lib/global_registry_bindings/workers/push_gr_entity_worker.rb +21 -0
  22. data/lib/global_registry_bindings/workers/push_relationship_worker.rb +21 -0
  23. data/spec/acceptance/global_registry_bindings_spec.rb +74 -0
  24. data/spec/factories/factories.rb +37 -0
  25. data/spec/fixtures/get_entities_person.json +8 -0
  26. data/spec/fixtures/get_entities_person_mdm.json +13 -0
  27. data/spec/fixtures/get_entities_person_relationship.json +32 -0
  28. data/spec/fixtures/get_entity_types.json +9 -0
  29. data/spec/fixtures/get_entity_types_address.json +59 -0
  30. data/spec/fixtures/get_entity_types_address_partial.json +43 -0
  31. data/spec/fixtures/get_entity_types_fancy_org.json +43 -0
  32. data/spec/fixtures/get_entity_types_fancy_org_partial.json +35 -0
  33. data/spec/fixtures/get_entity_types_person.json +42 -0
  34. data/spec/fixtures/get_entity_types_person_partial.json +34 -0
  35. data/spec/fixtures/get_relationship_types.json +9 -0
  36. data/spec/fixtures/get_relationship_types_person_fancy_org.json +41 -0
  37. data/spec/fixtures/get_relationship_types_person_fancy_org_partial.json +33 -0
  38. data/spec/fixtures/post_entities_fancy_org.json +8 -0
  39. data/spec/fixtures/post_entities_fancy_org_parent.json +8 -0
  40. data/spec/fixtures/post_entities_person.json +8 -0
  41. data/spec/fixtures/post_entity_types_address.json +9 -0
  42. data/spec/fixtures/post_entity_types_fancy_org.json +9 -0
  43. data/spec/fixtures/post_entity_types_person.json +9 -0
  44. data/spec/fixtures/post_relationship_types_person_fancy_org.json +16 -0
  45. data/spec/fixtures/put_entities_address.json +20 -0
  46. data/spec/fixtures/put_entities_person_relationship.json +29 -0
  47. data/spec/fixtures/put_entities_relationship.json +8 -0
  48. data/spec/fixtures/put_relationship_types_fields.json +33 -0
  49. data/spec/internal/app/models/address.rb +11 -0
  50. data/spec/internal/app/models/application_record.rb +5 -0
  51. data/spec/internal/app/models/assignment.rb +10 -0
  52. data/spec/internal/app/models/default.rb +6 -0
  53. data/spec/internal/app/models/namespaced/person.rb +18 -0
  54. data/spec/internal/app/models/organization.rb +12 -0
  55. data/spec/internal/config/database.yml +4 -0
  56. data/spec/internal/config/initializers/global_registry.rb +8 -0
  57. data/spec/internal/config/routes.rb +5 -0
  58. data/spec/internal/db/schema.rb +41 -0
  59. data/spec/internal/log/test.log +35717 -0
  60. data/spec/models/address_spec.rb +228 -0
  61. data/spec/models/assignment_spec.rb +200 -0
  62. data/spec/models/organization_spec.rb +127 -0
  63. data/spec/models/person_spec.rb +247 -0
  64. data/spec/spec_helper.rb +55 -0
  65. data/spec/workers/delete_gr_entity_worker_spec.rb +33 -0
  66. data/spec/workers/pull_mdm_id_worker_spec.rb +71 -0
  67. data/spec/workers/push_gr_entity_worker_spec.rb +27 -0
  68. data/spec/workers/push_relationship_worker_spec.rb +27 -0
  69. metadata +458 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 70066ac805a72a8e4cd636ef0ae96c987d68900f
4
+ data.tar.gz: 1e2db15b8712f76b2cc51cfe1dfca72725b6ac2a
5
+ SHA512:
6
+ metadata.gz: cf361f3f9f6a0d0d9f33c3f1ac287a7eeae6f5750d90a5d1d8308f9ca807dbc82568e1bb22afa946b9367a39fc39386435a65ee53bc2ddbccdaba6203e234f34
7
+ data.tar.gz: b121644f0d8fc5c50a10e803a9c3941349813e76926f42ebe92dc8316112f569713f444923dec4d0c1919f8ba0d956922140b6d60480e84f8e4c13c11f4fd2e1
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 Cru
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # Global Registry Bindings
2
+
3
+ Global Registry Bindings are a set of bindings to push ActiveRecord models to the Global Registry.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add to your Gemfile:
9
+ ```ruby
10
+ gem 'global-registry-bindings'
11
+ ```
12
+
13
+ Add a Global Registry initializer.
14
+ `config/initializers/global_registry.rb`
15
+ ```ruby
16
+ require 'global_registry'
17
+ GlobalRegistry.configure do |config|
18
+ config.access_token = ENV['GLOBAL_REGISTRY_TOKEN'] || 'fake'
19
+ config.base_url = ENV['GLOBAL_REGISTRY_URL'] || 'https://backend.global-registry.org'
20
+ end
21
+ ```
22
+
23
+ Make sure sidekiq is configured. See [Using Redis](https://github.com/mperham/sidekiq/wiki/Using-Redis) for information.
24
+
25
+ ## Usage
26
+
27
+ To make use of `global-registry-bindings` your model will need a few additional columns.
28
+ To push models to Global Registry, you will need a `global_registry_id` column. You additionally need a
29
+ `global_registry_mdm_id` to pull a Global Registry MDM (master data model) id. These columns should be of type
30
+ `:string` or `:uuid` and allow null values. Column names are customizable through options.
31
+ ```ruby
32
+ class CreatePeople < ActiveRecord::Migration
33
+ def self.up
34
+ create_table :people do |t|
35
+ t.string :name
36
+ t.string :global_registry_id, :null => true, :index => true
37
+ t.string :global_registry_mdm_id, :null => true, :index => true
38
+ end
39
+ end
40
+
41
+ def self.down
42
+ drop_table :people
43
+ end
44
+ end
45
+ ```
46
+
47
+ Enable `global-registry-bindings` functionality by declaring `global_registry_bindings` on your model.
48
+ ```ruby
49
+ class Person < ActiveRecord::Base
50
+ global_registry_bindings mdm_id_column: :global_registry_mdm_id
51
+ end
52
+ ```
53
+
54
+ ## Options
55
+
56
+ You can pass various options to the `global_registry_bindings` method. Configuration options are:
57
+
58
+ * `:id_column`: Column used to track the Global Registry ID for the model instance. Can be a :string or :uuid column.
59
+ (default: `:global_registry_id`)
60
+ * `:mdm_id_column`: Column used to enable MDM tracking and set the name of the column. MDM is disabled when this
61
+ option is nil or empty. (default: `nil`)
62
+ * `:type`: Global Registry entity type. Default value is underscored name of the model.
63
+ * `:push_on`: Array of Active Record lifecycle events used to push changes to Global Registry.
64
+ (default: `[:create, :update, :delete]`)
65
+ * `:parent_association`: Name of the Active Record parent association. Must be defined before calling
66
+ global_registry_bindings in order to determine foreign_key field. (default: `nil`)
67
+ * `:related_association`: Name of the Active Record related association. Setting this option changes the
68
+ global registry binding from entity to relationship. Active Record association must be defined before calling
69
+ global_registry_bindings in order to determine the foreign key. `:parent_relationship_name` and
70
+ `:related_relationship_name` must be set for relationship binding to work. (default: `nil`)
71
+ * `:parent_relationship_name`: Name of parent relationship role. (default: `nil`)
72
+ * `:related_relationship_name`: Name of the related relationship role. (default: `nil`)
73
+ * `:exclude_fields`: Model fields to exclude when pushing to Global Registry. Will additionally include `:mdm_id_column`
74
+ and `:parent_association` foreign key when defined.
75
+ (default: `[:id, :created_at, :updated_at, :global_registry_id]`)
76
+ * `:extra_fields`: Additional fields to send to Global Registry. This should be a hash with name as the key
77
+ and :type attributes as the value. Ex: `{language: :string}`. Name is a symbol and type is an ActiveRecord column type.
78
+ * `:mdm_timeout`: Only pull mdm information at most once every `:mdm_timeout`. (default: `1.minute`)
79
+
80
+ ## Values for `extra_fields`
81
+
82
+ Values sent to Global Registry are calculated by sending the field `name` to the model. They can be overidden by
83
+ aliasing an existing method, adding a new method to the model or by overriding the `entity_attributes_to_push`
84
+ method. If a model does not respond to a name or raises a `NoMethodError`, the field will be omitted from the request.
85
+
86
+ ```ruby
87
+ class Person < ActiveRecord::Base
88
+ # Person has first_name, last_name and guid columns
89
+ global_registry_bindings extra_fields: {full_name: :string, identity: :uuid, blargh: :integer},
90
+ exclude_fields: %i[guid]
91
+
92
+ # Person doesn't respond to 'blargh' so it is omitted from the attributes to push
93
+
94
+ alias_attribute :identity, :guid # Value for identity is aliased to guid
95
+
96
+ # Value for full_name
97
+ def full_name
98
+ "#{first_name} #{last_name}"
99
+ end
100
+
101
+ # Override entity_attributes_to_push to add or modify fields and values
102
+ def entity_attributes_to_push
103
+ entity_attributes = super
104
+ entity_attributes[:authentication] = { guid: guid }
105
+ entity_attributes
106
+ end
107
+ end
108
+ ```
109
+
110
+ ## Relationships
111
+
112
+ Global Registry allows for relating two entities together (many-to-many) through a relationship. An example of this
113
+ could be a Person to Person relationship. This relationship could be described as husband/spouse, or even
114
+ leader/employee. You could also relate a Person to an Organization through an assignment. The assignment can track
115
+ specific fields about the relationship.
116
+
117
+ `global-registry-bindings` supports this through the `:parent_association` and `:related_association` options.
118
+ Relationship roles, like husband/wife, are defined through the `:parent_relationship_name` and
119
+ `:related_relationship_name` options.
120
+
121
+ More information on Global Registry relationships and relationship types can be found
122
+ [here](https://github.com/CruGlobal/global_registry_docs/wiki/About-Relationships)
123
+
124
+ ## Example Models
125
+
126
+ Example models can be found in the [specs](https://github.com/CruGlobal/global-registry-bindings/tree/master/spec/internal/app/models).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/lazy_load_hooks'
4
+ require 'global_registry_bindings/global_registry_bindings'
5
+
6
+ ActiveSupport.on_load(:active_record) do
7
+ ActiveRecord::Base.send :extend, GlobalRegistry::Bindings
8
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'global_registry_bindings/workers/delete_gr_entity_worker'
4
+
5
+ module GlobalRegistry #:nodoc:
6
+ module Bindings #:nodoc:
7
+ module Entity #:nodoc:
8
+ module DeleteEntityMethods
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ after_commit :delete_entity_from_global_registry_async, on: :destroy
13
+ end
14
+
15
+ def delete_entity_from_global_registry_async
16
+ return unless global_registry.id_value?
17
+ ::GlobalRegistry::Bindings::Workers::DeleteGrEntityWorker.perform_async(global_registry.id_value)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'global_registry'
4
+
5
+ module GlobalRegistry #:nodoc:
6
+ module Bindings #:nodoc:
7
+ module Entity #:nodoc:
8
+ module EntityMethods
9
+ extend ActiveSupport::Concern
10
+
11
+ def entity_attributes_to_push
12
+ entity_attributes = self.class.columns_to_push.map do |name, type|
13
+ value_for_global_registry(name, type)
14
+ end.compact.to_h
15
+ entity_attributes[:client_integration_id] = id unless global_registry.exclude_fields
16
+ .include?(:client_integration_id)
17
+ entity_attributes[:client_updated_at] = updated_at.to_s(:db) if respond_to?(:updated_at)
18
+ entity_attributes[:parent_id] = global_registry.parent_id_value if global_registry.parent_is_self?
19
+ entity_attributes
20
+ end
21
+
22
+ def value_for_global_registry(name, type)
23
+ value = send(name)
24
+ return [name, value] if value.nil?
25
+ value = case type
26
+ when :datetime, :date
27
+ value.to_s(:db)
28
+ when :boolean
29
+ value ? 'true' : 'false'
30
+ else
31
+ value.to_s.strip
32
+ end
33
+ [name, value]
34
+ rescue NoMethodError
35
+ nil
36
+ end
37
+
38
+ module ClassMethods
39
+ def columns_to_push
40
+ @columns_to_push ||= columns
41
+ .collect { |c| { c.name.underscore.to_sym => normalize_column_type(c.type, c.name) } }
42
+ .reduce(&:merge)
43
+ .reject { |k, _v| global_registry.exclude_fields.include? k }
44
+ .merge(global_registry.extra_fields)
45
+ end
46
+
47
+ protected
48
+
49
+ def normalize_column_type(type, name)
50
+ if type.to_s == 'text'
51
+ :string
52
+ elsif name.ends_with?('_id')
53
+ :uuid
54
+ else
55
+ type
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'global_registry'
4
+
5
+ module GlobalRegistry #:nodoc:
6
+ module Bindings #:nodoc:
7
+ module Entity #:nodoc:
8
+ module EntityTypeMethods
9
+ extend ActiveSupport::Concern
10
+
11
+ module ClassMethods
12
+ def push_entity_type
13
+ parent_entity_id = parent_entity_type_id
14
+ entity_type = Rails.cache.fetch(entity_type_cache_key, expires_in: 1.hour) do
15
+ GlobalRegistry::EntityType.get('filters[name]' => global_registry.type,
16
+ 'filters[parent_id]' => parent_entity_id)['entity_types']&.first
17
+ end
18
+
19
+ unless entity_type
20
+ entity_type = GlobalRegistry::EntityType.post(entity_type: { name: global_registry.type,
21
+ parent_id: parent_entity_id,
22
+ field_type: 'entity' })['entity_type']
23
+ end
24
+
25
+ push_entity_type_fields(entity_type)
26
+ entity_type
27
+ end
28
+
29
+ def push_entity_type_fields(entity_type)
30
+ existing_fields = entity_type['fields']&.collect { |f| f['name'].to_sym } || []
31
+ columns_to_push
32
+ .reject { |k, _v| existing_fields.include? k }
33
+ .each do |name, type|
34
+ GlobalRegistry::EntityType.post(entity_type: { name: name,
35
+ parent_id: entity_type['id'],
36
+ field_type: type })
37
+ end
38
+ end
39
+
40
+ def parent_entity_type_id
41
+ parent_type = global_registry&.parent_type
42
+ return if parent_type.blank? || global_registry.parent_is_self?
43
+ parent_entity_type = global_registry.parent_class.send :push_entity_type
44
+ parent_entity_type&.dig('id')
45
+ end
46
+
47
+ def entity_type_cache_key
48
+ "GlobalRegistry::Bindings::EntityType::#{global_registry.type}"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'global_registry_bindings/workers/pull_mdm_id_worker'
4
+
5
+ module GlobalRegistry #:nodoc:
6
+ module Bindings #:nodoc:
7
+ module Entity #:nodoc:
8
+ module MdmMethods
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ GlobalRegistry::Bindings::Workers.mdm_worker_class(self)
13
+ after_commit :pull_mdm_id_from_global_registry_async, on: %i[create update]
14
+ end
15
+
16
+ def pull_mdm_id_from_global_registry_async
17
+ "::GlobalRegistry::Bindings::Workers::#{global_registry.mdm_worker_class_name}".constantize
18
+ .perform_async(self.class, id)
19
+ end
20
+
21
+ def pull_mdm_id_from_global_registry
22
+ unless global_registry.id_value?
23
+ raise GlobalRegistry::Bindings::RecordMissingGlobalRegistryId,
24
+ "#{self.class.name} #{id} has no #{global_registry.id_column}; will retry"
25
+ end
26
+ entity = GlobalRegistry::Entity.find(global_registry.id_value, 'filters[owned_by]' => 'mdm')
27
+ mdm_entity_id = dig_global_registry_mdm_id_from_entity(entity, global_registry.type.to_s)
28
+ unless mdm_entity_id
29
+ raise GlobalRegistry::Bindings::EntityMissingMdmId,
30
+ "GR entity #{global_registry.id_value} for #{self.class.name} #{id} has no mdm id; will retry"
31
+ end
32
+ update_column(global_registry.mdm_id_column, mdm_entity_id) # rubocop:disable Rails/SkipsModelValidations
33
+ end
34
+
35
+ def dig_global_registry_mdm_id_from_entity(entity, type)
36
+ Array.wrap(entity.dig('entity', type, "master_#{type}:relationship"))
37
+ .first # although there should not be more than one
38
+ .try(:[], "master_#{type}")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'global_registry_bindings/workers/push_gr_entity_worker'
4
+
5
+ module GlobalRegistry #:nodoc:
6
+ module Bindings #:nodoc:
7
+ module Entity #:nodoc:
8
+ module PushEntityMethods
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ after_commit :push_entity_to_global_registry_async, on: (global_registry.push_on - %i[delete])
13
+ end
14
+
15
+ def push_entity_to_global_registry_async
16
+ ::GlobalRegistry::Bindings::Workers::PushGrEntityWorker.perform_async(self.class, id)
17
+ end
18
+
19
+ def push_entity_to_global_registry
20
+ self.class.push_entity_type
21
+
22
+ if global_registry.parent_type.present? && !global_registry.parent_is_self?
23
+ create_dependent_entity_in_global_registry
24
+ elsif global_registry.id_value?
25
+ update_entity_in_global_registry
26
+ else
27
+ create_entity_in_global_registry
28
+ end
29
+ rescue RestClient::ResourceNotFound
30
+ global_registry.id_value = nil
31
+ push_entity_to_global_registry
32
+ end
33
+
34
+ def update_entity_in_global_registry
35
+ entity_attributes = { global_registry.type => entity_attributes_to_push }
36
+ GlobalRegistry::Entity.put(global_registry.id_value, entity: entity_attributes)
37
+ end
38
+
39
+ def create_entity_in_global_registry
40
+ if global_registry.parent_is_self? && global_registry.parent_id_value.blank?
41
+ # Push parent entity if it exists and is missing global_registry_id
42
+ global_registry.parent&.create_entity_in_global_registry
43
+ end
44
+ entity_attributes = { global_registry.type => entity_attributes_to_push }
45
+ entity = GlobalRegistry::Entity.post(entity: entity_attributes)
46
+ global_registry.id_value = dig_global_registry_id_from_entity(entity['entity'], global_registry.type)
47
+ update_column(global_registry.id_column, # rubocop:disable Rails/SkipsModelValidations
48
+ global_registry.id_value)
49
+ end
50
+
51
+ # Create or Update a child entity (ex: :email_address is a child of :person)
52
+ def create_dependent_entity_in_global_registry # rubocop:disable Metrics/AbcSize
53
+ return if global_registry.parent.blank?
54
+ global_registry.parent.push_entity_to_global_registry if global_registry.parent_id_value.blank?
55
+ entity_attributes = {
56
+ global_registry.parent_type => {
57
+ client_integration_id: global_registry.parent.id,
58
+ global_registry.type => entity_attributes_to_push
59
+ }
60
+ }
61
+ entity = GlobalRegistry::Entity.put(global_registry.parent_id_value, entity: entity_attributes)
62
+ global_registry.id_value = dig_global_registry_id_from_entity(entity['entity'],
63
+ global_registry.type,
64
+ global_registry.parent_type)
65
+ update_column(global_registry.id_column, # rubocop:disable Rails/SkipsModelValidations
66
+ global_registry.id_value)
67
+ end
68
+
69
+ def dig_global_registry_id_from_entity(entity, type, parent_type = nil)
70
+ return entity&.dig(type.to_s, 'id') unless parent_type
71
+ Array.wrap(entity&.dig(parent_type.to_s, type.to_s)).detect do |item|
72
+ item['client_integration_id'] == id.to_s
73
+ end&.dig('id')
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'global_registry'
4
+ require 'global_registry_bindings/workers/push_relationship_worker'
5
+
6
+ module GlobalRegistry #:nodoc:
7
+ module Bindings #:nodoc:
8
+ module Entity #:nodoc:
9
+ module PushRelationshipMethods
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ after_commit :push_relationship_to_global_registry_async, on: (global_registry.push_on - %i[delete])
14
+ end
15
+
16
+ def push_relationship_to_global_registry_async
17
+ ::GlobalRegistry::Bindings::Workers::PushRelationshipWorker.perform_async(self.class, id)
18
+ end
19
+
20
+ def push_relationship_to_global_registry
21
+ ensure_related_entities_have_global_registry_ids!
22
+ self.class.push_global_registry_relationship_type
23
+
24
+ if global_registry.id_value?
25
+ update_relationship_in_global_registry
26
+ else
27
+ create_relationship_in_global_registry
28
+ end
29
+ end
30
+
31
+ def update_relationship_in_global_registry
32
+ GlobalRegistry::Entity.put(global_registry.id_value, entity: entity_attributes_to_push)
33
+ end
34
+
35
+ def create_relationship_in_global_registry # rubocop:disable Metrics/AbcSize
36
+ entity = GlobalRegistry::Entity.put(global_registry.parent_id_value,
37
+ { entity: { global_registry.parent_type => {
38
+ "#{global_registry.related_relationship_name}:relationship" =>
39
+ entity_attributes_to_push.merge(global_registry.related_type =>
40
+ global_registry.related_id_value)
41
+ }, client_integration_id: global_registry.parent.id } },
42
+ params: {
43
+ full_response: true,
44
+ fields: "#{global_registry.related_relationship_name}:relationship"
45
+ })
46
+ global_registry.id_value = global_registry_relationship_entity_id_from_entity entity
47
+ update_column(global_registry.id_column, # rubocop:disable Rails/SkipsModelValidations
48
+ global_registry.id_value)
49
+ # Update relationship to work around bug in Global Registry
50
+ # - If current system doesn't own a copy of the parent entity, then creating a new relationship in the same
51
+ # request will not add the relationship entity_type properties.
52
+ update_relationship_in_global_registry if global_registry.id_value?
53
+ end
54
+
55
+ def global_registry_relationship_entity_id_from_entity(entity)
56
+ relationships = Array.wrap entity.dig('entity', global_registry.parent_type.to_s,
57
+ "#{global_registry.related_relationship_name}:relationship")
58
+ relationships.detect do |rel|
59
+ cid = rel['client_integration_id']
60
+ cid = cid['value'] if cid.is_a?(Hash)
61
+ cid == id.to_s
62
+ end&.dig('relationship_entity_id')
63
+ end
64
+
65
+ def ensure_related_entities_have_global_registry_ids!
66
+ return if global_registry.parent_id_value.present? && global_registry.related_id_value.present?
67
+ # Enqueue push_entity worker for related entities missing global_registry_id and retry relationship push
68
+ names = []
69
+ [global_registry.parent, global_registry.related].each do |model|
70
+ next if model.global_registry.id_value?
71
+ names << "#{model.class.name}(#{model.id})"
72
+ model.push_entity_to_global_registry_async
73
+ end
74
+ raise GlobalRegistry::Bindings::RelatedEntityMissingGlobalRegistryId,
75
+ "#{self.class.name}(#{id}) has related entities [#{names.join ', '}] missing global_registry_id;" \
76
+ ' will retry.'
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end