global-registry-bindings 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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