app/models/maestrano/connector/rails/concerns/entity.rb in maestrano-connector-rails-0.2.16 vs app/models/maestrano/connector/rails/concerns/entity.rb in maestrano-connector-rails-0.2.17
- old
+ new
@@ -15,152 +15,195 @@
# ----------------------------------------------
# Mapper methods
# ----------------------------------------------
# Map a Connec! entity to the external format
def map_to_external(entity, organization)
- self.mapper_class.normalize(entity)
+ mapper_class.normalize(entity)
end
# Map an external entity to Connec! format
def map_to_connec(entity, organization)
- self.mapper_class.denormalize(entity)
+ mapper_class.denormalize(entity)
end
# ----------------------------------------------
+ # IdMap methods
+ # ----------------------------------------------
+ def names_hash
+ {
+ connec_entity: connec_entity_name.downcase,
+ external_entity: external_entity_name.downcase
+ }
+ end
+
+ def find_or_create_idmap(organization_and_id)
+ Maestrano::Connector::Rails::IdMap.find_or_create_by(names_hash.merge(organization_and_id))
+ end
+
+ # organization_and_id can be either:
+ # * {connec_id: 'id', organization_id: 'id'}
+ # * {external_id: 'id', organization_id: 'id'}
+ # Needs to include either connec_entity or external_entity for complex entities
+ def find_idmap(organization_and_id)
+ Maestrano::Connector::Rails::IdMap.find_by(names_hash.merge(organization_and_id))
+ end
+
+ def create_idmap_from_external_entity(entity, organization)
+ h = names_hash.merge({
+ external_id: get_id_from_external_entity_hash(entity),
+ name: object_name_from_external_entity_hash(entity),
+ organization_id: organization.id
+ })
+ Maestrano::Connector::Rails::IdMap.create(h)
+ end
+
+ def create_idmap_from_connec_entity(entity, organization)
+ h = names_hash.merge({
+ connec_id: entity['id'],
+ name: object_name_from_connec_entity_hash(entity),
+ organization_id: organization.id
+ })
+ Maestrano::Connector::Rails::IdMap.create(h)
+ end
+ # ----------------------------------------------
# Connec! methods
# ----------------------------------------------
def normalized_connec_entity_name
- if self.singleton?
- self.connec_entity_name.downcase
+ if singleton?
+ connec_entity_name.downcase
else
- self.connec_entity_name.downcase.pluralize
+ connec_entity_name.downcase.pluralize
end
end
def get_connec_entities(client, last_synchronization, organization, opts={})
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching Connec! #{self.connec_entity_name}")
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching Connec! #{connec_entity_name}")
entities = []
# Fetch first page
if last_synchronization.blank? || opts[:full_sync]
- Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{self.connec_entity_name}, fetching all data")
- response = client.get("/#{self.normalized_connec_entity_name}")
+ Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{connec_entity_name}, fetching all data")
+ response = client.get("/#{normalized_connec_entity_name}")
else
- Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{self.connec_entity_name}, fetching data since #{last_synchronization.updated_at.iso8601}")
+ Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{connec_entity_name}, fetching data since #{last_synchronization.updated_at.iso8601}")
query_param = URI.encode("$filter=updated_at gt '#{last_synchronization.updated_at.iso8601}'")
- response = client.get("/#{self.normalized_connec_entity_name}?#{query_param}")
+ response = client.get("/#{normalized_connec_entity_name}?#{query_param}")
end
- raise "No data received from Connec! when trying to fetch #{self.connec_entity_name.pluralize}" unless response
+ raise "No data received from Connec! when trying to fetch #{connec_entity_name.pluralize}" unless response
response_hash = JSON.parse(response.body)
- Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "received first page entity=#{self.connec_entity_name}, response=#{response.body}")
- if response_hash["#{self.normalized_connec_entity_name}"]
- entities << response_hash["#{self.normalized_connec_entity_name}"]
+ Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "received first page entity=#{connec_entity_name}, response=#{response.body}")
+ if response_hash["#{normalized_connec_entity_name}"]
+ entities << response_hash["#{normalized_connec_entity_name}"]
else
- raise "Received unrecognized Connec! data when trying to fetch #{self.connec_entity_name.pluralize}"
+ raise "Received unrecognized Connec! data when trying to fetch #{connec_entity_name.pluralize}"
end
# Fetch subsequent pages
while response_hash['pagination'] && response_hash['pagination']['next']
# ugly way to convert https://api-connec/api/v2/group_id/organizations?next_page_params to /organizations?next_page_params
- next_page = response_hash['pagination']['next'].gsub(/^(.*)\/#{self.normalized_connec_entity_name}/, self.normalized_connec_entity_name)
+ next_page = response_hash['pagination']['next'].gsub(/^(.*)\/#{normalized_connec_entity_name}/, normalized_connec_entity_name)
response = client.get(next_page)
- raise "No data received from Connec! when trying to fetch subsequent page of #{self.connec_entity_name.pluralize}" unless response
- Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "received next page entity=#{self.connec_entity_name}, response=#{response.body}")
+ raise "No data received from Connec! when trying to fetch subsequent page of #{connec_entity_name.pluralize}" unless response
+ Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "received next page entity=#{connec_entity_name}, response=#{response.body}")
response_hash = JSON.parse(response.body)
- if response_hash["#{self.normalized_connec_entity_name}"]
- entities << response_hash["#{self.normalized_connec_entity_name}"]
+ if response_hash["#{normalized_connec_entity_name}"]
+ entities << response_hash["#{normalized_connec_entity_name}"]
else
- raise "Received unrecognized Connec! data when trying to fetch subsequent page of #{self.connec_entity_name.pluralize}"
+ raise "Received unrecognized Connec! data when trying to fetch subsequent page of #{connec_entity_name.pluralize}"
end
end
entities = entities.flatten
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received data: Source=Connec!, Entity=#{self.connec_entity_name}, Data=#{entities}")
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received data: Source=Connec!, Entity=#{connec_entity_name}, Data=#{entities}")
entities
end
def push_entities_to_connec(connec_client, mapped_external_entities_with_idmaps, organization)
- self.push_entities_to_connec_to(connec_client, mapped_external_entities_with_idmaps, self.connec_entity_name, organization)
+ push_entities_to_connec_to(connec_client, mapped_external_entities_with_idmaps, connec_entity_name, organization)
end
def push_entities_to_connec_to(connec_client, mapped_external_entities_with_idmaps, connec_entity_name, organization)
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending #{@@external_name} #{self.external_entity_name.pluralize} to Connec! #{connec_entity_name.pluralize}")
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending #{@@external_name} #{external_entity_name.pluralize} to Connec! #{connec_entity_name.pluralize}")
mapped_external_entities_with_idmaps.each do |mapped_external_entity_with_idmap|
external_entity = mapped_external_entity_with_idmap[:entity]
idmap = mapped_external_entity_with_idmap[:idmap]
- if idmap.connec_id.blank?
- connec_entity = self.create_connec_entity(connec_client, external_entity, connec_entity_name, organization)
- idmap.update_attributes(connec_id: connec_entity['id'], connec_entity: connec_entity_name.downcase, last_push_to_connec: Time.now, message: nil)
- else
- connec_entity = self.update_connec_entity(connec_client, external_entity, idmap.connec_id, connec_entity_name, organization)
- idmap.update_attributes(last_push_to_connec: Time.now, message: nil)
+ begin
+ if idmap.connec_id.blank?
+ connec_entity = create_connec_entity(connec_client, external_entity, connec_entity_name, organization)
+ idmap.update_attributes(connec_id: connec_entity['id'], connec_entity: connec_entity_name.downcase, last_push_to_connec: Time.now, message: nil)
+ else
+ connec_entity = update_connec_entity(connec_client, external_entity, idmap.connec_id, connec_entity_name, organization)
+ idmap.update_attributes(last_push_to_connec: Time.now, message: nil)
+ end
+ rescue => e
+ # Store Connec! error if any
+ idmap.update_attributes(message: e.message)
end
-
- # Store Connec! error if any
- idmap.update_attributes(message: connec_entity['errors'].first['title']) unless connec_entity.blank? || connec_entity['errors'].blank?
end
end
def create_connec_entity(connec_client, mapped_external_entity, connec_entity_name, organization)
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending create #{connec_entity_name}: #{mapped_external_entity} to Connec!")
response = connec_client.post("/#{normalized_connec_entity_name}", { "#{normalized_connec_entity_name}".to_sym => mapped_external_entity })
- raise "No response received from Connec! when trying to create a #{self.connec_entity_name}" unless response
- JSON.parse(response.body)["#{normalized_connec_entity_name}"]
+ response = JSON.parse(response.body)
+ raise "Connec!: #{response['errors']['title']}" if response['errors'] && response['errors']['title']
+ response["#{normalized_connec_entity_name}"]
end
def update_connec_entity(connec_client, mapped_external_entity, connec_id, connec_entity_name, organization)
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending update #{connec_entity_name}: #{mapped_external_entity} to Connec!")
response = connec_client.put("/#{normalized_connec_entity_name}/#{connec_id}", { "#{normalized_connec_entity_name}".to_sym => mapped_external_entity })
- raise "No response received from Connec! when trying to update a #{self.connec_entity_name}" unless response
- JSON.parse(response.body)["#{normalized_connec_entity_name}"]
+ response = JSON.parse(response.body)
+ raise "Connec!: #{response['errors']['title']}" if response['errors'] && response['errors']['title']
+ response["#{normalized_connec_entity_name}"]
end
def map_to_external_with_idmap(entity, organization)
- idmap = Maestrano::Connector::Rails::IdMap.find_by(connec_id: entity['id'], connec_entity: self.connec_entity_name.downcase, organization_id: organization.id)
+ idmap = find_idmap({connec_id: entity['id'], organization_id: organization.id})
if idmap && ((!idmap.to_external) || (idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']))
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{self.connec_entity_name} : #{entity}")
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{connec_entity_name} : #{entity}")
nil
else
- {entity: self.map_to_external(entity, organization), idmap: idmap || Maestrano::Connector::Rails::IdMap.create(connec_id: entity['id'], connec_entity: self.connec_entity_name.downcase, organization_id: organization.id, name: object_name_from_connec_entity_hash(entity))}
+ {entity: map_to_external(entity, organization), idmap: idmap || create_idmap_from_connec_entity(entity, organization)}
end
end
# ----------------------------------------------
# External methods
# ----------------------------------------------
def get_external_entities(client, last_synchronization, organization, opts={})
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching #{@@external_name} #{self.external_entity_name.pluralize}")
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching #{@@external_name} #{external_entity_name.pluralize}")
raise "Not implemented"
end
def push_entities_to_external(external_client, mapped_connec_entities_with_idmaps, organization)
- push_entities_to_external_to(external_client, mapped_connec_entities_with_idmaps, self.external_entity_name, organization)
+ push_entities_to_external_to(external_client, mapped_connec_entities_with_idmaps, external_entity_name, organization)
end
def push_entities_to_external_to(external_client, mapped_connec_entities_with_idmaps, external_entity_name, organization)
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending Connec! #{self.connec_entity_name.pluralize} to #{@@external_name} #{external_entity_name.pluralize}")
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending Connec! #{connec_entity_name.pluralize} to #{@@external_name} #{external_entity_name.pluralize}")
mapped_connec_entities_with_idmaps.each do |mapped_connec_entity_with_idmap|
- self.push_entity_to_external(external_client, mapped_connec_entity_with_idmap, external_entity_name, organization)
+ push_entity_to_external(external_client, mapped_connec_entity_with_idmap, external_entity_name, organization)
end
end
def push_entity_to_external(external_client, mapped_connec_entity_with_idmap, external_entity_name, organization)
idmap = mapped_connec_entity_with_idmap[:idmap]
connec_entity = mapped_connec_entity_with_idmap[:entity]
begin
if idmap.external_id.blank?
- external_id = self.create_external_entity(external_client, connec_entity, external_entity_name, organization)
+ external_id = create_external_entity(external_client, connec_entity, external_entity_name, organization)
idmap.update_attributes(external_id: external_id, external_entity: external_entity_name.downcase, last_push_to_external: Time.now, message: nil)
else
- self.update_external_entity(external_client, connec_entity, idmap.external_id, external_entity_name, organization)
+ update_external_entity(external_client, connec_entity, idmap.external_id, external_entity_name, organization)
idmap.update_attributes(last_push_to_external: Time.now, message: nil)
end
rescue => e
# Store External error
idmap.update_attributes(message: e.message)
@@ -191,59 +234,61 @@
# * Discards entities that do not need to be pushed because they have not been updated since their last push
# * Discards entities from one of the two source in case of conflict
# * Maps not discarded entities and associates them with their idmap, or create one if there isn't any
# * Return a hash {connec_entities: [], external_entities: []}
def consolidate_and_map_data(connec_entities, external_entities, organization, opts={})
+ return consolidate_and_map_singleton(connec_entities, external_entities, organization, opts) if singleton?
+
mapped_external_entities = external_entities.map{|entity|
- idmap = Maestrano::Connector::Rails::IdMap.find_by(external_id: self.get_id_from_external_entity_hash(entity), external_entity: self.external_entity_name.downcase, organization_id: organization.id)
+ idmap = find_idmap({external_id: get_id_from_external_entity_hash(entity), organization_id: organization.id})
# No idmap: creating one, nothing else to do
unless idmap
- next {entity: self.map_to_connec(entity, organization), idmap: Maestrano::Connector::Rails::IdMap.create(external_id: self.get_id_from_external_entity_hash(entity), external_entity: self.external_entity_name.downcase, organization_id: organization.id, name: self.object_name_from_external_entity_hash(entity))}
+ next {entity: map_to_connec(entity, organization), idmap: create_idmap_from_external_entity(entity, organization)}
end
# Not pushing entity to Connec!
next nil unless idmap.to_connec
# Entity has not been modified since its last push to connec!
- if idmap.last_push_to_connec && idmap.last_push_to_connec > self.get_last_update_date_from_external_entity_hash(entity)
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard #{@@external_name} #{self.external_entity_name} : #{entity}")
- next nil
- end
+ next nil if self.class.not_modified_since_last_push_to_connec(idmap, entity, self, organization)
# Check for conflict with entities from connec!
- if idmap.connec_id && connec_entity = connec_entities.detect{|connec_entity| connec_entity['id'] == idmap.connec_id}
- # We keep the most recently updated entity
- if !opts[:connec_preemption].nil?
- keep_external = !opts[:connec_preemption]
- else
- keep_external = connec_entity['updated_at'] < self.get_last_update_date_from_external_entity_hash(entity)
- end
-
- if keep_external
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{@@external_name} #{self.external_entity_name} #{entity} and Connec! #{self.connec_entity_name} #{connec_entity}. Entity from #{@@external_name} kept")
- connec_entities.delete(connec_entity)
- {entity: self.map_to_connec(entity, organization), idmap: idmap}
- else
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{@@external_name} #{self.external_entity_name} #{entity} and Connec! #{self.connec_entity_name} #{connec_entity}. Entity from Connec! kept")
- nil
- end
-
- else
- {entity: self.map_to_connec(entity, organization), idmap: idmap}
- end
+ self.class.solve_conflict(entity, self, connec_entities, connec_entity_name, idmap, organization, opts)
}
mapped_external_entities.compact!
mapped_connec_entities = connec_entities.map{|entity|
- self.map_to_external_with_idmap(entity, organization)
+ map_to_external_with_idmap(entity, organization)
}
mapped_connec_entities.compact!
return {connec_entities: mapped_connec_entities, external_entities: mapped_external_entities}
end
+ def consolidate_and_map_singleton(connec_entities, external_entities, organization, opts={})
+ return {connec_entities: [], external_entities: []} if external_entities.empty? && connec_entities.empty?
+ idmap = find_or_create_idmap({organization_id: organization.id})
+
+ if external_entities.empty?
+ keep_external = false
+ elsif connec_entities.empty?
+ keep_external = true
+ elsif !opts[:connec_preemption].nil?
+ keep_external = !opts[:connec_preemption]
+ else
+ keep_external = self.class.is_external_more_recent?(connec_entities.first, external_entities.first, self)
+ end
+ if keep_external
+ idmap.update(external_id: get_id_from_external_entity_hash(external_entities.first))
+ return {connec_entities: [], external_entities: [{entity: map_to_connec(external_entities.first, organization), idmap: idmap}]}
+ else
+ idmap.update(connec_id: connec_entities.first['id'])
+ return {connec_entities: [{entity: map_to_external(connec_entities.first, organization), idmap: idmap}], external_entities: []}
+ end
+ end
+
# ----------------------------------------------
# Entity specific methods
# Those methods need to be define in each entity
# ----------------------------------------------
# Is this resource a singleton (in Connec!)?
@@ -272,7 +317,50 @@
end
# Return a string representing the object from an external entity hash
def object_name_from_external_entity_hash(entity)
raise "Not implemented"
+ end
+
+
+ # ----------------------------------------------
+ # Internal helper methods
+ # ----------------------------------------------
+ module ClassMethods
+ def not_modified_since_last_push_to_connec(idmap, entity, entity_instance, organization)
+ result = idmap.last_push_to_connec && idmap.last_push_to_connec > entity_instance.get_last_update_date_from_external_entity_hash(entity)
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard #{entity_instance.external_entity_name} : #{entity}") unless result
+ result
+ end
+
+ def is_external_more_recent?(connec_entity, external_entity, entity_instance)
+ connec_entity['updated_at'] < entity_instance.get_last_update_date_from_external_entity_hash(external_entity)
+ end
+
+ def solve_conflict(external_entity, entity_instance, connec_entities, connec_entity_name, idmap, organization, opts)
+ if idmap.connec_id && connec_entity = connec_entities.detect{|connec_entity| connec_entity['id'] == idmap.connec_id}
+ # We keep the most recently updated entity
+ if !opts[:connec_preemption].nil?
+ keep_external = !opts[:connec_preemption]
+ else
+ keep_external = is_external_more_recent?(connec_entity, external_entity, entity_instance)
+ end
+
+ if keep_external
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{entity_instance.external_entity_name} #{external_entity} and Connec! #{connec_entity_name} #{connec_entity}. Entity from external kept")
+ connec_entities.delete(connec_entity)
+ entity_instance.map_external_entity_with_idmap(external_entity, connec_entity_name, idmap, organization)
+ else
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{entity_instance.external_entity_name} #{external_entity} and Connec! #{connec_entity_name} #{connec_entity}. Entity from Connec! kept")
+ nil
+ end
+
+ else
+ entity_instance.map_external_entity_with_idmap(external_entity, connec_entity_name, idmap, organization)
+ end
+ end
+ end
+
+ def map_external_entity_with_idmap(external_entity, connec_entity_name, idmap, organization)
+ {entity: map_to_connec(external_entity, organization), idmap: idmap}
end
end
\ No newline at end of file