global-registry-bindings 0.0.6 → 0.1.0

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -35
  3. data/lib/global_registry_bindings/entity/entity_type_methods.rb +35 -33
  4. data/lib/global_registry_bindings/entity/mdm_methods.rb +9 -19
  5. data/lib/global_registry_bindings/entity/push_entity_methods.rb +31 -38
  6. data/lib/global_registry_bindings/entity/push_relationship_methods.rb +61 -47
  7. data/lib/global_registry_bindings/entity/relationship_type_methods.rb +48 -37
  8. data/lib/global_registry_bindings/exceptions.rb +1 -0
  9. data/lib/global_registry_bindings/global_registry_bindings.rb +129 -46
  10. data/lib/global_registry_bindings/{entity/delete_entity_methods.rb → model/delete_entity.rb} +5 -5
  11. data/lib/global_registry_bindings/model/entity.rb +64 -0
  12. data/lib/global_registry_bindings/model/pull_mdm.rb +23 -0
  13. data/lib/global_registry_bindings/model/push_entity.rb +21 -0
  14. data/lib/global_registry_bindings/model/push_relationship.rb +79 -0
  15. data/lib/global_registry_bindings/model/relationship.rb +68 -0
  16. data/lib/global_registry_bindings/options.rb +26 -59
  17. data/lib/global_registry_bindings/options/entity_class_options.rb +34 -0
  18. data/lib/global_registry_bindings/options/entity_instance_options.rb +90 -0
  19. data/lib/global_registry_bindings/options/entity_options_parser.rb +76 -0
  20. data/lib/global_registry_bindings/options/relationship_class_options.rb +37 -0
  21. data/lib/global_registry_bindings/options/relationship_instance_options.rb +131 -0
  22. data/lib/global_registry_bindings/options/relationship_options_parser.rb +98 -0
  23. data/lib/global_registry_bindings/railtie.rb +2 -1
  24. data/lib/global_registry_bindings/version.rb +1 -1
  25. data/lib/global_registry_bindings/worker.rb +25 -0
  26. data/lib/global_registry_bindings/workers/{delete_gr_entity_worker.rb → delete_entity_worker.rb} +1 -6
  27. data/lib/global_registry_bindings/workers/pull_mdm_id_worker.rb +12 -13
  28. data/lib/global_registry_bindings/workers/push_entity_worker.rb +24 -0
  29. data/lib/global_registry_bindings/workers/push_relationship_worker.rb +17 -7
  30. data/spec/acceptance/global_registry_bindings_spec.rb +50 -40
  31. data/spec/factories/factories.rb +24 -0
  32. data/spec/fixtures/get_entity_types_area.json +44 -0
  33. data/spec/fixtures/post_entities_community.json +8 -0
  34. data/spec/fixtures/post_relationship_types_fancy_org_area.json +16 -0
  35. data/spec/fixtures/put_entities_community_relationship.json +16 -0
  36. data/spec/fixtures/put_entities_fancy_org_area_relationship.json +8 -0
  37. data/spec/fixtures/put_entities_fancy_org_relationship.json +17 -0
  38. data/spec/fixtures/put_entities_person_country_relationship.json +23 -0
  39. data/spec/fixtures/put_entities_relationship_400.json +3 -0
  40. data/spec/fixtures/put_relationship_types_fields_fancy_org_area.json +25 -0
  41. data/spec/helpers/sidekiq_helpers.rb +14 -0
  42. data/spec/internal/app/models/address.rb +7 -2
  43. data/spec/internal/app/models/area.rb +7 -0
  44. data/spec/internal/app/models/assignment.rb +5 -4
  45. data/spec/internal/app/models/community.rb +19 -0
  46. data/spec/internal/app/models/country.rb +8 -0
  47. data/spec/internal/app/models/namespaced/person.rb +48 -2
  48. data/spec/internal/app/models/organization.rb +26 -3
  49. data/spec/internal/db/schema.rb +28 -0
  50. data/spec/internal/log/test.log +71023 -0
  51. data/spec/models/address_spec.rb +6 -204
  52. data/spec/models/assignment_spec.rb +40 -186
  53. data/spec/models/organization_spec.rb +106 -92
  54. data/spec/models/person_spec.rb +158 -214
  55. data/spec/models/user_edited_person_spec.rb +2 -2
  56. data/spec/spec_helper.rb +5 -6
  57. data/spec/workers/delete_gr_entity_worker_spec.rb +4 -4
  58. data/spec/workers/pull_mdm_id_worker_spec.rb +94 -32
  59. data/spec/workers/push_entity_worker_spec.rb +476 -0
  60. data/spec/workers/push_relationship_worker_spec.rb +344 -15
  61. metadata +45 -10
  62. data/lib/global_registry_bindings/entity/entity_methods.rb +0 -62
  63. data/lib/global_registry_bindings/options/class_options.rb +0 -62
  64. data/lib/global_registry_bindings/options/instance_options.rb +0 -63
  65. data/lib/global_registry_bindings/workers/push_gr_entity_worker.rb +0 -22
  66. data/spec/workers/push_gr_entity_worker_spec.rb +0 -27
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'global_registry_bindings/workers/push_relationship_worker'
4
+ require 'global_registry_bindings/workers/delete_entity_worker'
5
+
6
+ module GlobalRegistry #:nodoc:
7
+ module Bindings #:nodoc:
8
+ module Model #:nodoc:
9
+ module PushRelationship
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ after_commit :push_relationships_to_global_registry_async, on: %i[create update]
14
+ after_commit :delete_relationships_from_global_registry_async, on: %i[destroy]
15
+ end
16
+
17
+ def push_relationships_to_global_registry_async(*types)
18
+ types = types.empty? ? self.class.global_registry_relationship_types : types
19
+ types.each do |type|
20
+ action = global_registry_relationship_change_action(type)
21
+ send("global_registry_relationship_async_#{action}".to_sym, type)
22
+ end
23
+ end
24
+
25
+ def delete_relationships_from_global_registry_async(*types)
26
+ types = types.empty? ? self.class.global_registry_relationship_types : types
27
+ types.each do |type|
28
+ next unless global_registry_relationship(type).id_value?
29
+ ::GlobalRegistry::Bindings::Workers::DeleteEntityWorker.perform_async(
30
+ global_registry_relationship(type).id_value
31
+ )
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def global_registry_relationship_async_push(type)
38
+ ::GlobalRegistry::Bindings::Workers::PushRelationshipWorker.perform_async(self.class, id, type)
39
+ end
40
+
41
+ def global_registry_relationship_async_replace(type)
42
+ # Replace deletes GR relationship immediately before scheduling an async update
43
+ ::GlobalRegistry::Bindings::Workers::DeleteEntityWorker.new.perform(
44
+ global_registry_relationship(type).id_value
45
+ )
46
+ ::GlobalRegistry::Bindings::Workers::PushRelationshipWorker.perform_async(self.class, id, type)
47
+ end
48
+
49
+ def global_registry_relationship_async_delete(type)
50
+ delete_relationships_from_global_registry_async(type)
51
+ end
52
+
53
+ def global_registry_relationship_async_ignore(_type); end # noop
54
+
55
+ # rubocop:disable Metrics/PerceivedComplexity
56
+ # rubocop:disable Metrics/AbcSize
57
+ def global_registry_relationship_change_action(type)
58
+ [global_registry_relationship(type).primary_association_foreign_key,
59
+ global_registry_relationship(type).related_association_foreign_key].each do |key|
60
+ if previous_changes.key?(key)
61
+ # Delete if changed from anything to nil
62
+ return :delete if previous_changes[key].last.nil?
63
+ # Replace if value changed
64
+ return :replace if previous_changes[key].first != previous_changes[key].last &&
65
+ !previous_changes[key].first.nil?
66
+ elsif key.present? && send(key).nil?
67
+ # Ignore if value didn't change and foreign_key is nil
68
+ return :ignore
69
+ end
70
+ end
71
+ # otherwise Create/Update
72
+ :push
73
+ end
74
+ # rubocop:enable Metrics/PerceivedComplexity
75
+ # rubocop:enable Metrics/AbcSize
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GlobalRegistry #:nodoc:
4
+ module Bindings #:nodoc:
5
+ module Model #:nodoc:
6
+ module Relationship
7
+ extend ActiveSupport::Concern
8
+
9
+ def relationship_attributes_to_push(type)
10
+ entity_attributes = relationship_columns_to_push(type).map do |name, t|
11
+ relationship_value_for_global_registry(name, t)
12
+ end.compact.to_h
13
+ unless global_registry_relationship(type).exclude_fields.include?(:client_integration_id)
14
+ entity_attributes[:client_integration_id] = global_registry_relationship(type).client_integration_id
15
+ end
16
+ entity_attributes[:client_updated_at] = updated_at.to_s(:db) if respond_to?(:updated_at)
17
+ entity_attributes
18
+ end
19
+
20
+ def relationship_value_for_global_registry(name, type)
21
+ value = send(name)
22
+ return [name, value] if value.nil?
23
+ value = case type
24
+ when :datetime, :date
25
+ value.to_s(:db)
26
+ when :boolean
27
+ value ? 'true' : 'false'
28
+ else
29
+ value.to_s.strip
30
+ end
31
+ [name, value]
32
+ rescue ::NoMethodError
33
+ nil
34
+ end
35
+
36
+ def relationship_columns_to_push(type)
37
+ @relationship_columns_to_push ||= {}
38
+ @relationship_columns_to_push[type] ||=
39
+ relationship_entity_columns(type)
40
+ .reject { |k, _v| global_registry_relationship(type).exclude_fields.include? k }
41
+ .merge(global_registry_relationship(type).extra_fields)
42
+ end
43
+
44
+ protected
45
+
46
+ def normalize_relationship_column_type(type, name)
47
+ if type.to_s == 'text'
48
+ :string
49
+ elsif name.ends_with?('_id')
50
+ :uuid
51
+ else
52
+ type
53
+ end
54
+ end
55
+
56
+ def relationship_entity_columns(type)
57
+ return {} if global_registry_relationship(type).primary_class_is_self?
58
+ self.class
59
+ .columns
60
+ .collect do |c|
61
+ { c.name.underscore.to_sym => normalize_relationship_column_type(c.type, c.name) }
62
+ end
63
+ .reduce(&:merge)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'global_registry_bindings/options/instance_options'
4
- require 'global_registry_bindings/options/class_options'
3
+ require 'global_registry_bindings/options/entity_instance_options'
4
+ require 'global_registry_bindings/options/entity_class_options'
5
+ require 'global_registry_bindings/options/relationship_instance_options'
6
+ require 'global_registry_bindings/options/relationship_class_options'
5
7
 
6
8
  module GlobalRegistry #:nodoc:
7
9
  module Bindings #:nodoc:
@@ -9,74 +11,39 @@ module GlobalRegistry #:nodoc:
9
11
  extend ActiveSupport::Concern
10
12
 
11
13
  included do
14
+ # Entity Class Options
12
15
  class_attribute :_global_registry_bindings_class_options
13
- self._global_registry_bindings_class_options ||= GlobalRegistry::Bindings::Options::ClassOptions.new(self)
16
+ self._global_registry_bindings_class_options ||=
17
+ GlobalRegistry::Bindings::Options::EntityClassOptions.new(self)
18
+ # Relationship Class Options
19
+ class_attribute :_global_registry_bindings_class_relationships
20
+ self._global_registry_bindings_class_relationships = {}
14
21
  end
15
22
 
16
- def global_registry
17
- @_global_registry_bindings_instance_options ||= GlobalRegistry::Bindings::Options::InstanceOptions.new(self)
23
+ def global_registry_entity
24
+ @_global_registry_bindings_instance_options ||=
25
+ GlobalRegistry::Bindings::Options::EntityInstanceOptions.new(self)
26
+ end
27
+
28
+ def global_registry_relationship(type)
29
+ @_global_registry_bindings_instance_relationships ||= {}
30
+ @_global_registry_bindings_instance_relationships[type] ||=
31
+ GlobalRegistry::Bindings::Options::RelationshipInstanceOptions.new(type, self)
18
32
  end
19
33
 
20
34
  module ClassMethods
21
- def global_registry
35
+ def global_registry_entity
22
36
  _global_registry_bindings_class_options
23
37
  end
24
- end
25
- end
26
-
27
- class OptionsParser
28
- def initialize(model_class)
29
- @model_class = model_class
30
- end
31
-
32
- def defaults
33
- {
34
- id_column: :global_registry_id,
35
- mdm_id_column: nil,
36
- type: @model_class.name.demodulize.underscore.to_sym,
37
- push_on: %i[create update delete],
38
- parent_association: nil,
39
- parent_association_class: nil,
40
- related_association: nil,
41
- related_association_class: nil,
42
- parent_relationship_name: nil,
43
- related_relationship_name: nil,
44
- exclude_fields: %i[id created_at updated_at],
45
- extra_fields: {},
46
- mdm_timeout: 1.minute
47
- }.freeze
48
- end
49
38
 
50
- def parse(options_hash = {})
51
- @options = defaults.deep_merge(options_hash) do |key, oldval, newval|
52
- if key == :exclude_fields
53
- oldval.concat Array.wrap(newval)
54
- else
55
- newval
56
- end
39
+ def global_registry_relationship(type)
40
+ _global_registry_bindings_class_relationships[type] ||=
41
+ GlobalRegistry::Bindings::Options::RelationshipClassOptions.new(type, self)
57
42
  end
58
- update_excludes
59
- validate_options
60
- @options
61
- end
62
-
63
- private
64
-
65
- def update_excludes
66
- @options[:exclude_fields] << @options[:id_column]
67
- @options[:exclude_fields] << @options[:mdm_id_column] if @options[:mdm_id_column].present?
68
-
69
- parent_id_column = association_foreign_key @options[:parent_association]
70
- @options[:exclude_fields] << parent_id_column.to_sym if parent_id_column
71
43
 
72
- related_id_column = association_foreign_key @options[:related_association]
73
- @options[:exclude_fields] << related_id_column.to_sym if related_id_column
74
- end
75
-
76
- def validate_options; end
77
-
78
- def association_foreign_key(name)
79
- @model_class.reflect_on_all_associations.detect { |a| a.name == name }&.foreign_key if name
44
+ def global_registry_relationship_types
45
+ _global_registry_bindings_options[:relationships].keys
46
+ end
80
47
  end
81
48
  end
82
49
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+
5
+ module GlobalRegistry #:nodoc:
6
+ module Bindings #:nodoc:
7
+ module Options
8
+ class EntityClassOptions
9
+ delegate :id_column,
10
+ :mdm_id_column,
11
+ :type,
12
+ :mdm_timeout,
13
+ :push_on,
14
+ :parent_association,
15
+ :parent_association_class,
16
+ :exclude_fields,
17
+ :extra_fields, to: :@options
18
+
19
+ def initialize(model_class)
20
+ @model_class = model_class
21
+ @options = OpenStruct.new model_class._global_registry_bindings_options[:entity]
22
+ end
23
+
24
+ def ensure_entity_type?
25
+ @options.ensure_entity_type.present?
26
+ end
27
+
28
+ def mdm_worker_class_name
29
+ "Pull#{@model_class.name.tr(':', '')}MdmIdWorker"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GlobalRegistry #:nodoc:
4
+ module Bindings #:nodoc:
5
+ module Options
6
+ class EntityInstanceOptions
7
+ delegate :id_column,
8
+ :mdm_id_column,
9
+ :mdm_timeout,
10
+ :push_on,
11
+ :parent_association,
12
+ :parent_association_class,
13
+ :mdm_worker_class_name,
14
+ :ensure_entity_type?,
15
+ to: :@class_options
16
+
17
+ def initialize(model)
18
+ @model = model
19
+ @class_options = model.class.global_registry_entity
20
+ end
21
+
22
+ def id_value
23
+ @model.send id_column
24
+ end
25
+
26
+ def id_value=(value)
27
+ @model.send "#{id_column}=", value
28
+ end
29
+
30
+ def id_value?
31
+ @model.send "#{id_column}?"
32
+ end
33
+
34
+ def type
35
+ t = @class_options.type
36
+ t.is_a?(Proc) ? t.call(@model) : t
37
+ end
38
+
39
+ def parent
40
+ @model.send(parent_association) if parent_association.present?
41
+ end
42
+
43
+ def parent_class
44
+ return if parent_association.blank?
45
+ parent_association_class
46
+ end
47
+
48
+ def parent_type
49
+ parent&.global_registry_entity&.type
50
+ end
51
+
52
+ def parent_id_value
53
+ parent&.global_registry_entity&.id_value
54
+ end
55
+
56
+ def parent_required?
57
+ parent_association.present? && !parent_is_self?
58
+ end
59
+
60
+ def parent_is_self?
61
+ parent_association.present? && parent_class == @model.class
62
+ end
63
+
64
+ def exclude_fields
65
+ option = @class_options.exclude_fields
66
+ case option
67
+ when Proc
68
+ option.call(type, @model)
69
+ when Symbol
70
+ @model.send(option, type)
71
+ else
72
+ option
73
+ end
74
+ end
75
+
76
+ def extra_fields
77
+ option = @class_options.extra_fields
78
+ case option
79
+ when Proc
80
+ option.call(type, @model)
81
+ when Symbol
82
+ @model.send(option, type)
83
+ else
84
+ option
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GlobalRegistry #:nodoc:
4
+ module Bindings #:nodoc:
5
+ module Options
6
+ class EntityOptionsParser
7
+ def initialize(model_class)
8
+ @model_class = model_class
9
+ end
10
+
11
+ def defaults
12
+ {
13
+ id_column: :global_registry_id,
14
+ mdm_id_column: nil,
15
+ type: @model_class.name.demodulize.underscore.to_sym,
16
+ push_on: %i[create update destroy],
17
+ parent_association: nil,
18
+ parent_association_class: nil,
19
+ parent_relationship_name: nil,
20
+ exclude_fields: %i[id created_at updated_at],
21
+ extra_fields: {},
22
+ mdm_timeout: 1.minute,
23
+ ensure_entity_type: true
24
+ }.freeze
25
+ end
26
+
27
+ def parse(options_hash = {})
28
+ merge_defaults options_hash
29
+ update_association_classes
30
+ update_excludes
31
+ @options
32
+ end
33
+
34
+ private
35
+
36
+ def merge_defaults(options_hash = {})
37
+ @options = defaults.merge(options_hash) do |key, oldval, newval|
38
+ if key == :exclude_fields
39
+ case newval
40
+ when Proc, Symbol
41
+ newval
42
+ else
43
+ oldval.concat Array.wrap(newval)
44
+ end
45
+ else
46
+ newval
47
+ end
48
+ end
49
+ end
50
+
51
+ def update_association_classes
52
+ unless @options[:parent_association_class] # rubocop:disable Style/GuardClause
53
+ @options[:parent_association_class] = association_class @options[:parent_association]
54
+ end
55
+ end
56
+
57
+ def update_excludes
58
+ return unless @options[:exclude_fields].is_a? Array
59
+ @options[:exclude_fields] << @options[:id_column]
60
+ @options[:exclude_fields] << @options[:mdm_id_column] if @options[:mdm_id_column].present?
61
+
62
+ parent_id_column = association_foreign_key @options[:parent_association]
63
+ @options[:exclude_fields] << parent_id_column.to_sym if parent_id_column
64
+ end
65
+
66
+ def association_foreign_key(name)
67
+ @model_class.reflect_on_association(name)&.foreign_key&.to_sym
68
+ end
69
+
70
+ def association_class(name)
71
+ @model_class.reflect_on_association(name)&.klass
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end