Global Registry Bindings
Global Registry Bindings are a set of bindings to push ActiveRecord models to the Global Registry.
Installation
Add to your Gemfile:
gem 'global-registry-bindings'
Add a Global Registry initializer.
config/initializers/global_registry.rb
require 'global_registry'
require 'global_registry_bindings'
GlobalRegistry.configure do |config|
config.access_token = ENV['GLOBAL_REGISTRY_TOKEN'] || 'fake'
config.base_url = ENV['GLOBAL_REGISTRY_URL'] || 'https://backend.global-registry.org'
end
Make sure sidekiq is configured. See Using Redis for information.
Additional Configuration
Sidekiq options
The global-registry-bindings
gem allows for configuring default sidekiq options for all workers. You can configure
this by creating a custom initializer, or adding to the global_registry initializer the following.
GlobalRegistry::Bindings.configure do |config|
# Run global-registry-bindings workers in a :custom queue
config. = { queue: :custom }
end
Custom sidekiq options will apply to all Global Registry Bindings sidekiq Workers.
Redis Error Action
This option defines what global-registry-bindings
does when a Redis error is encountered while adding a sidekiq
worker to the queue. Valid actions are :ignore
, :log
and :raise
.
GlobalRegistry::Bindings.configure do |config|
config.redis_error_action = :ignore # Silently ignore redis issues
end
The default behaviour is to :log
the error to Rollbar
if present.
Usage
To make use of global-registry-bindings
your model will need a few additional columns.
To push models to Global Registry, you will need a global_registry_id
column. You additionally need a
global_registry_mdm_id
to pull a Global Registry MDM (master data model) id. Additionally, relationships will also
require columns to track relationship ids. These columns should be of type
:string
or :uuid
and allow null values. Column names are customizable through options.
class CreatePeople < ActiveRecord::Migration
def change
add_column :people, :global_registry_id, :string, null: true, default: nil
add_column :people, :global_registry_mdm_id, :string, null: true, default: nil
end
end
Enable global-registry-bindings
functionality by declaring global_registry_bindings
on your model.
class Person < ActiveRecord::Base
global_registry_bindings mdm_id_column: :global_registry_mdm_id
end
Options
You can pass various options to the global_registry_bindings
method. Options will list whether they are valid for
:entity
, :relationship
or both bindings.
:binding
: Type of Global Registry binding. Either:entity
or:relationship
. (default::entity
):id_column
: Column used to track the Global Registry ID for the entity or relationship entity. Can be a:string
or:uuid
column. (default::global_registry_id
) [:entity
,:relationship
]:type
: Global Registry Entity Type name. This name should be unique in Global Registry or point to an existing Entity Type. When used in a:relationship
binding, it is required to be unique across all relationships on this ActiveRecord class. Accepts a Symbol or a Proc. Symbol is the name of the Entity Type, Proc is passed the model instance and must return a symbol which is the Entity Type. Defaults to the underscored name of the class. Ex:type: proc { |model| model.name.to_sym }
. [:entity
,:relationship
]:push_on
: Array of Active Record lifecycle events used to push changes to Global Registry. (default:[:create, :update, :destroy]
) [:entity
]:parent
: Name of the Active Record parent association (:belongs_to
,:has_one
...). Must be defined before callingglobal_registry_bindings
in order to determine foreign_key for use in exclude. Used to create a hierarchy or to push child entity types. (Ex: person -> address) (default:nil
) [:entity
]:parent_class
: Active Record Class name of the parent. Required if:parent
can not be used to determine the parent class. This can happen if parent is defined by another gem, likeancestry
. (default:nil
) [:entity
]:primary_binding
: Determines what type of global-registry-binding the primary association points to. Defaults to:entity
, but can be set to a:relationship
type (ex::assignment
) to create a relationship_type between a relationship and an entity. (default::entity
) [:relationship
]:primary
: Name of the Active Record primary association. Must be defined before calling global_registry_bindings in order to determine foreign_key for use in exclude. If missing,:primary
is assumed to be the current Active Record model. (default:nil
) [:relationship
]:primary_class
: Class name of the primary model. Required if:primary
can not be used to determine the primary class. This can happen if parent is defined by another gem, likeancestry
. (default:self.class
) [:relationship
]:primary_foreign_key
: Foreign Key column for the primary association. Used if foreign_key can not be determined from:primary
. (default::primary.foreign_key
) [:relationship
]:primary_name
: Required Name of primary relationship (Global Registry relationship1). Should be unique to prevent ambiguous relationship names. (default:nil
) [:relationship
]:related
: Name of the Active Record related association. Active Record association must be defined before calling global_registry_bindings in order to determine the foreign key. (default:nil
) [:relationship
]:related_class
: Class name of the related model. Required if:related_association
can not be used to determine the related class. (default:nil
) [:relationship
]:related_foreign_key
: Foreign Key column for the related association. Used if foreign_key can not be determined from:related
. (default::related.foreign_key
) [:relationship
]:related_name
: Required Name of the related relationship (Global Registry relationship2). Should be unique to prevent ambiguous relationship names (default:nil
) [:relationship
]:related_type
: Name of the related association Entity Type. Required if unable to determined:type
from related. (default:nil
) [:relationship
]:related_global_registry_id
: Global Registry ID of a remote related entity. Proc or Symbol. Implementation should cache this as it may be requested multiple times. (default:nil
) [:relationship
]:ensure_type
: Ensure Global Registry Entity Type or Relationship Entity Type exists and is up to date. (default:true
) [:entity
,:relationship
]:client_integration_id
: Client Integration ID for relationship. Proc or Symbol. (default::primary.id
) [:relationship
]:include_all_columns
: Include all model columns in the fields to push to Global Registry. Iffalse
, fields must be defined in the:fields
option. (default:false
) [:entity
,:relationship
]:exclude
: Array, Proc or Symbol. Array of Model fields (as symbols) to exclude when pushing to Global Registry. Array Will additionally include:mdm_id_column
and:parent_association
foreign key when defined. If Proc, is passed type and model instance and should return an Array of the fields to exclude. If Symbol, this should be a method name the Model instance responds to. It is passed the type and should return an Array of fields to exclude. When Proc or Symbol are used, you must explicitly return the standard defaults. (default:[:id, :created_at, :updated_at, :global_registry_id]
) [:entity
,:relationship
]:fields
: Additional fields to send to Global Registry. Hash, Proc or Symbol. As a Hash, names are the keys and :type attributes are the values. Ex:{language: :string}
. Name is a symbol and type is an ActiveRecord column type. As a Proc, it is passed the type and model instance, and should return a Hash. As a Symbol, the model should respond to this method, is passed the type, and should return a Hash. [:entity
,:relationship
]:mdm_id_column
: Column used to enable MDM tracking and set the name of the column. MDM is disabled when this option is nil or empty. (default:nil
) [:entity
]:mdm_timeout
: Only pull mdm information at most once every:mdm_timeout
. (default:1.minute
) [:entity
]:fingerprint_column
: Column used to enable fingerprints and set the name of the column. fingerprints are disabled when this option is nil or empty. Enabling fingerprints result in bindings only pushing data on a tracked change. (default:nil
) [:entity
]:if
,:unless
: Proc or Symbol, called to determine if the change should be sent (enqueue a worker) to Global Registry. Proc and Symbol will both receive the model for an entity, and the type and model for a relationship. See Conditional Push for examples. [:entity
,:relationship
]
Entities
global-registry-bindings
default bindings is to push an Active Record class as an Entity to Global Registry.
This can be used to push root level entities, entities with a parent and entities with a hierarchy. You can also
enable fetching of a Master Data Model from Global Registry.
See About Entities for more information on Global Registry Entities.
Root Entity
class Person < ActiveRecord::Base
global_registry_bindings mdm_id_column: :global_registry_mdm_id
end
This will push the Person Active Record model to Global Registry as a person
Entity Type, storing the resulting id
value in the global_registry_id
column, as well as fetching a master_person
Entity and storing it in the
global_registry_mdm_id
column.
Parent/Child Entity
class Person < ActiveRecord::Base
has_many :addresses, inverse_of: :person
global_registry_bindings
end
class Address < ActiveRecord::Base
belongs_to :person
global_registry_bindings
end
This will push the Person model to Global Registry as a person
Entity Type, and the Address model as an address
Entity Type that has a parent of person
.
Entity Hierarchy
class Ministry < ActiveRecord::Base
has_many :children, class_name: 'Ministry', foreign_key: :parent_id
belongs_to :parent, class_name: 'Ministry'
global_registry_bindings parent: :parent
end
This will push the Ministry model to Global Registry as well as the parent/child hierarchy. Global Registry only allows a single parent, and does not allow circular references. Hierarchy is also EntityType specific, and not saved per system in Global Registry, meaning, the last system to push a parent wins (You can accidently override another systems hierarchy. This should be avoided and instead pushed as a relationship if needed).
Relationships
global-registry-bindings
can also be configured to push relationships between models to Global Registry. All
relationships in Global Registry are many to many, but by using Active Record associations, we can simulate one to many
and one to one.
See About Relationships for more information on Global Registry relationships.
Many-to-Many with join model
class Ministry < ActiveRecord::Base
has_many :assignments
has_many :people, through: :assignments
global_registry_bindings
end
class Person < ActiveRecord::Base
has_many :assignments
has_many :ministries, through: :assignments
global_registry_bindings
end
class Assignment < ActiveRecord::Base
belongs_to :person
belongs_to :ministry
global_registry_bindings binding: :relationship,
primary: :person,
primary_name: :people,
related: :ministry,
related_name: :ministries
end
This will push Ministry and Person to Global Registry as Entities, and Assignment join model as a relationship between
them, storing the relationship id in the Assignment global_registry_id
column.
One-to-Many
class Person < ActiveRecord::Base
has_many :pets
global_registry_bindings
end
class Pet < ActiveRecord::Base
belongs_to :person
global_registry_bindings binding: :relationship,
type: :owner,
related: :person
end
Fields and Values
Both Entities and Relationships include fields that will be pushed to Global Registry.
Fields
The fields that are pushed to Global Registry are defined with a combination of the :fields
, :exclude
and
:include_all_columns
options. The :fields
option defines the fields and field types to be pushed. If
:include_all_columns
is set to true
,:fields
are appended to the list of all model columns. :exclude
option is
then used to remove fields from the list. If :ensure_type
is true
, the Global Registry EntityType or
RelationshipType will be updated when new fields are defined. If :ensure_type
is false, and fields are missing
from the EntityType or RelationshipType, Global Registry will throw an error. It is the developers job to ensure Global
Registry Entity and Relationship Types are accurate when :ensure_type
is disabled.
Given an Active Record model:
create_table :products do |t|
t.string :name
t.text :description
t.string :global_registry_id
t.references :supplier, index: true, foreign_key: true
t.string :supplier_gr_rel_id, null: true, default: nil
t. null: false
end
And the following global_registry_bindings
:
class Product < ActiveRecord::Base
belongs_to :supplier
global_registry_bindings fields: { name: string, description: :text }
end
Will result in the following fields {:name=>:string, :description=>:text}
class Product < ActiveRecord::Base
belongs_to :supplier
global_registry_bindings include_all_columns: true,
exclude: %i[supplier_id]
end
Will result in the following fields {:name=>:string, :description=>:text}
, :id
, :global_registry_id
and timestamp
fields are excluded by default when :include_all_columns
is true
.
You can add additional fields by specifying them in the :fields
option.
class Product < ActiveRecord::Base
belongs_to :supplier
global_registry_bindings include_all_columns: true,
exclude: %i[supplier_id],
fields: {color: :string}
end
Will result in the following fields {:name=>:string, :description=>:text, :color=>:string}
Relationships can also include fields:
class Product < ActiveRecord::Base
belongs_to :supplier
global_registry_bindings fields: { name: string, description: :text }
global_registry_bindings binding: :relationship,
type: :supplier,
related: :supplier,
id_column: :supplier_gr_rel_id,
extra: {quantity: :integer}
end
Will result in the following fields {:quantity=>:integer}
:fields
and :exclude
can also be defined as a proc, labmda or symbol. Symbol must point to a method that will
return either the extra or excluded fields.
class Product < ActiveRecord::Base
belongs_to :supplier
global_registry_bindings include_all_columns: true,
exclude: ->(type, model) { model.name == 'Sprocket' ? [] : %i[:field1] },
fields: :extra_fields
def extra_fields(type)
# type === :product
{field1: :string, field2: :boolean}
end
end
You can debug the current fields that will be pushed using the rails console:
irb> Product.first.entity_columns_to_push
=> {:name=>:string, :description=>:text}
irb> Product.first.relationship_entity_columns(:supplier)
=> {}
Values
When a model is pushed to global registry, global-registry-bindings
will attempt to determined the values for
each of the fields. This is done by calling the field name on the model. If the model responds, the value will be sent
with the entity. Model and implement or override values with a few different options. They can use
alias_attribute :new_name, :old_name
, define a method def field_name; "value"; end
or override
entity_attributes_to_push
or relationship_attributes_to_push
respectively. When the *_attributes_to_push
methods
are used, you can modify values for other attributes as well as add additional fields and values. This is helpful
when adding fields and values which may not be tracked directly on this model. An instance of this is adding an
authentication: { guid: 'UUID' }
field to a person
entity_type to utilize Global Registry linked_identities.
See Entity Matching.
class Person < ActiveRecord::Base
alias_attribute :field1, :name
global_registry_bindings fields: { name: string, description: :text, field1: :boolean, field2: :integer }
def field2
"#{name}:2"
end
def entity_attributes_to_push
attrs = super # Calls super to get field values, then modify them.
attrs[:description] = "Huge: #{attrs[:description]}"
attrs[:authentication] = { guid: 'UUID' }
attrs
end
end
As an example, this would alias field1
to name
and use the method field2
to determine the value for field2
. It
subsequently changes the value of :description
and adds an :authentication
field using the
entity_attributes_to_push
override.
Conditional Push
Entities and relationships can be conditionally pushed to Global Registry using the :if
and :unless
options. These
options take either a Proc or a Symbol and should return true/false depending on if the Model should be pushed.
Using a proc:
class Product < ActiveRecord::Base
attr_accessor :should_push
global_registry_bindings if: proc { |model| model.should_push }
end
Using a Symbol:
class Product < ActiveRecord::Base
global_registry_bindings unless: :should_push
def should_push(_model)
return ::GlobalConfig.gr_enabled?
end
end
Example Models
Example models can be found in the specs.
Testing
Global Registry Bindings includes a testing helper to better help test your project when gelobal-registry-bindings
are included. Since Global Registry Bindings uses sidekiq, it's possible to have these workers executed in your
projects tests (ex: running sidekiq/testing in inline! mode). You
can use the following test modes:
require 'global_registry_bindings/testing'
GlobalRegistry::Bindings::Testing.disable_test_helper! # disables the test helper, adding workers to a queue. (default).
GlobalRegistry::Bindings::Testing.skip_workers!
Each of the above methods also accepts a block.
require 'global_registry_bindings/testing'
GlobalRegistry::Bindings::Testing.disable_test_helper!
# Some tests
around(:example) do |example|
GlobalRegistry::Bindings::Testing.skip_workers!(&example)
end
# OR
GlobalRegistry::Bindings::Testing.skip_workers! do
# Some other tests
end