Now that you're all setup with both Connec!™ and the external application, it's time to decide which entities (contacts, accounts, events, ...) you want to synchronize. For each type of entity your connector will synchronize, you will need to create a class that inherits from Maestrano::Connector::Rails::Entity
.
An example of such a class in provided in the models/entities/ folder, and demonstrates which methods you'll need to implements for each entity. The main thing to do is the mapping between the external entity and the Connec!™ entity. For that, we use the hash_mapper gem (https://github.com/ismasan/hash_mapper). Note that in all the instance methods of those classes, you will have access to four instance variables:
@organization # The organization that it currently synchronizing @connec_client # The Connec! client @external_client # The external client created using External.get_client(@organization) @opts # Some options |
A mapping in action:
You'll find the Connec!™ API documentation here: http://maestrano.github.io/connec/, and should also refer to the external application API documentation to find out the fields you can map.
This type of entity class enable one-to-one model correspondence. For more complex needs, please refer to the complex entity section below. |
When mapping sensible data it is important to maintain consistency, and some applications might have a complex structure (.i.e allowing multi currency, different time-zones etc...). A guide to Business Rules can be found Here |
Let's imagine you are mapping people to contacts, and the have a reference to the organization (or company) they belong to. The first thing you need to do is to map the two reference fields:
Once that is done, the second and only thing you need to do is to declare that this field is a reference, so that the framework know it has something specific to do with it. That's done in a method in your entity class:
You can define as many references as you need. Note that you should always declare references using the Connec!™ name of the field. If all go well, you should see in your specs when mapping from external to Connec!™ that the organization_id field is an array of hashes with a provider, a realm and an id. If the entity you are mapping has nested sub-fields (e.g. an invoice can have an array of lines, a customer can have an array of notes, a warehouse can have an array of items etc...) you would need to declare them as well:
id_references does not prevent the entity to be pushed. |
There is two cases here:
In this case, you should not push. Even more, you should not fetch it from Connec!™ (using $filter). You should also make sure that the documentation include this limitation.
Let's push it then. You'll just need to add a default value in your mapper:
You should also add a test in your specs for when there is no description coming from Connec!™ |
Let's say your mapping products, and the field 'category' is mandatory, but Connec!™ is not giving you any 'category' field Well, unfortunately, in this case, you need to hard code it using a creation only mapper (make sure it inherits from the 'regular' ProductMapper):
This will allow the framework to map the default category the first time the entity is pushed from Connec!, but use the 'normal' mapper after that. Define the method in your simple entities:
And sub entities:
|
Some applications only provide support for Customers (People). In order to be able to create an organization in Connec!™, it is possible to use the option: `attach_to_organization` A common way to implement it, is to map the name of the organization (if provided in another field) in the `before_denormalize` hook inside the mapper:
(If the company name is not provided or missing entirely, use 'First name + Last name') This will create an Organization for each customer/person. Please refer to Connec!™ documentation to find more options. |
Okay so now you're mapping items, which can be either a product or a service. In Connec!™ we have a field type that can be either SERVICE, MANUFACTURED or PURCHASED, but in your app you have a type_id that is a meaningless integer. A typical way to approach this is to retrieve the information about the types in YourApp and set it to a TYPE_MAPPER constant:
And then use the mapper in the after_normalize/denormalize hooks:
|
You're trying to map invoices, and you've noticed that invoices can have several lines, and you don't know how to map that. You could do some loops in your after_normalize and after_denormalize hooks to handle the lines, but there's actually a far better way to do it:
|
In each entity class, you need to define two methods to give a name to a record when they are coming from either Connec!™ or your app. This name should be using a representative name, such as name, first_name last_name, reference, or something alike. Also, because record will do back and forth between Connec!™ and your app, and we will update the name each time, it is good practice to use fields that are mapped together:
Also please note that the object name is used only for display purposes, so it's nothing critical. |
Connec! has a built in filter that can be used to query for a specific subset of entities on a given endpoint. Please refer to:
I.e. In the entity below ('contact.rb'), you want to fetch only the entities that are leads. In order to maintain a consistent workflow (including web-hooks), a method to override is provided (to be used in conjunction with the filtering):
|
It is now possible to pass options to the
The Connector Framework implements a A common use is an additional API call to retrieve data from an external endpoint: i.e.
|
If an entity is read only, a method can be used to prevent any data from being pushed to that specific endpoint. This will be implemented in each read only entity.rb file:
Similarly, the following methods can be used to specify other operations:
|
Yes, in each entity you can override two methods, one for Connec! entities and one for externals:
|
Also don't forget that each entity your connector synchronize should be declared in the External class, and, because the synchronizable entities are stored in the local database for each organization, you'll need to create a migration for existing organization if you add an new entity after the release. (And only after the release. In development, just add the new entity to your organization synchronized_entities list with a rails console).
In each entity model, you can set metadata on each specific entity IdMap and specify which fields you do not want to be updated:
The specified fields will now be ignored in subsequent updates from your application. |
In production, synchronizations will be automatically triggered every hour. You can force a synchronization from the front end, or by using the following commands:
Performing the synchronization of all the linked organizations can be triggered by executing
Maestrano::Connector::Rails::AllSynchronizationsJob |
The synchronization of a specific organization can be performed by executing
Maestrano::Connector::Rails::SynchronizationJob.perform_later(organization, forced: true) |
Connec!™ issues webhooks each time an entity is created or updated. This allows real time integration from Connec!™ to the application you're integrating with.
If the application you're integrating with also support webhooks, you can and should use them to allow a full real time integration. The gem provide a job to help you do that: it will perform the necessary checks, mappings and then push the entities to Connec!™. All you have to do is build a controller and config the necessary routes to catch the webhook, and call this job.
For example:
class ExternalWebhooksController < ApplicationController def notification # The second argument should be a hash: {"external_entity_name1" => [entity1, entity2], "external_entity_name2" => []} Maestrano::Connector::Rails::PushToConnecJob.perform_later(current_organization, entities_hash) end end |
If you need a more complex mapping, like one-to-many or many-to-many ones, you can use the complex entity workflow.
The most common case is when an entity can be mapped to two different entities, either internally (Connec!) or externally.
For example `people` and `organizations` are both mapped to `contacts` in MyApp, differentiated by the field `is_organization`
Example of use case:
|
To see how it works, you can run
rails g connector:complex_entity |
This will generate some example files demonstrating a one-to-many correspondance between Connec!™ person and external contact and lead data models.
The complex entities workflow uses two methods to pre-process data which you have to implements for each complex entity (see person_and_organization.rb). They are called before the mapping step, and you can use them to perform any data model specific operations.
|
If two sub-entities have the same name, you would need to specify in which class they can be found. Let's take, for example, an account entity mapped to two sub-entities: Account e Bank Account. In this case, you would need to create three sub-entities: two named One solution is to provide the names as a Hash, specifying the entity name and the file name separately (in this case
|
You are trying to map entities that have fields split between different endpoints. A typical example is the mapping of Items/Products and Warehouses/Warehouses. Let's say that the external API gives the quantity of items inside a specific warehouse in the Item model, while Connec!™ provides a field in Warehouses to keep track of the stocks. In this case you will have to merge data from the two models, and the best place to do it is inside the complex entity
And voilà, the fields are now matching the different data models. It is now sufficient to match them in the after_normalize/denormalize hooks in
|
When creating an entity, you can specify which attributes you want to use to merge with an existing record. If a record is matched, it will behave like an update request and return the existing entity with a 200 status code. This can be specified with the option matching_fields.
In your entity_name.rb
specify which field has to be used for smart merging. The typical use case is a required field that will uniquely identify the record:
# In this example the field taken into account is 'name' in Organizations def self.connec_matching_fields ['name'] end |