require 'time' require 'dmao_api' require 'dmao/ingesters/errors/generic_ingester' require 'dmao/ingesters/errors/empty_attributes' require 'dmao/ingesters/errors/ingest_entity_error' module DMAO module Ingesters module Generic class Ingester ENTITY = nil ENTITY_ERROR = nil INVALID_ENTITY_ERROR = nil ENTITY_ERROR_MESSAGE = nil ERROR_HANDLING = nil def initialize(api_url=nil, api_token=nil, institution_id=nil) DMAO::API.configure do |config| config.base_url = api_url config.api_token = api_token config.institution_id = institution_id end if self.class::ENTITY.nil? raise "Entity not set" end add_method_name = "add_#{self.class.entity_name.tr(' ', '_')}" update_method_name = "update_#{self.class.entity_name.tr(' ', '_')}" ingest_method_name = "ingest_#{self.class.entity_name.tr(' ', '_')}" self.class.send(:define_method, add_method_name, Proc.new {|attributes| add_entity(attributes)}) unless self.respond_to?(add_method_name, include_all: true) self.class.send(:define_method, update_method_name, Proc.new {|id, attributes| update_entity(id, attributes)}) unless self.respond_to?(update_method_name, include_all: true) self.class.send(:define_method, ingest_method_name, Proc.new {|attributes={}| ingest_entity(attributes)}) unless self.respond_to?(ingest_method_name, include_all: true) end def self.entity_name self::ENTITY.to_s.split('::')[-1].gsub(/[A-Z]/, " \\0").downcase.strip end def generic_errors { "DMAO::API::Errors::InstitutionNotFound" => "Institution not found, cannot ingest #{self.class.entity_name} to non-existent institution", "DMAO::API::Errors::EntityNotFound" => "#{self.class.entity_name.capitalize} not found, cannot update #{self.class.entity_name} that does not exist" } end def ingest raise DMAO::Ingesters::Errors::GenericIngester.new("Calling ingest on generic ingester is not allowed.") end def ingest_entity attributes = {} validate_attributes_present attributes begin entity = self.class::ENTITY.find_by_system_uuid attributes[:system_uuid] current_modified_at = Time.parse(entity.system_modified_at) new_modified_at = Time.parse(attributes[:system_modified_at]) return false if current_modified_at >= new_modified_at update_entity entity.id, attributes rescue DMAO::API::Errors::EntityNotFound add_entity attributes rescue DMAO::API::Errors::InvalidResponseLength raise self.class::ENTITY_ERROR.new("More than one #{self.class.entity_name} returned for system uuid #{attributes[:system_uuid]}.") end end def parse_unprocessable_errors errors error_messages = "" errors.each_with_index do |(k, v), index| v.each_with_index do |msg, msg_index| error_messages += "#{k} - #{msg}" error_messages += ", " unless msg_index == v.size - 1 end error_messages += ", " unless index == errors.size - 1 end raise self.class::ENTITY_ERROR.new("#{self.class::ENTITY_ERROR_MESSAGE}, #{error_messages}") end private def validate_attributes_present attributes raise DMAO::Ingesters::Errors::EmptyAttributes.new("Cannot ingest #{self.class.entity_name} without attributes.") if attributes.nil? || attributes.empty? end def perform_entity_action action, arguments errors_to_handle = self.class::ERROR_HANDLING.nil? ? generic_errors : self.class::ERROR_HANDLING begin self.class::ENTITY.send(action, *arguments) rescue ingest_errors(errors_to_handle) => e error_message = errors_to_handle[e.class.to_s].nil? ? errors_to_handle[(e.class.ancestors.map {|a| a.to_s} & errors_to_handle.keys).first] : errors_to_handle[e.class.to_s] raise self.class::ENTITY_ERROR.new(error_message) rescue self.class::ENTITY::INVALID_ENTITY_CLASS => e parse_unprocessable_errors(e.errors) end end def ingest_errors(errors={}) Class.new do def self.===(other) !(other.class.ancestors.map {|a| a.to_s} & @errors.keys).empty? end end.tap do |c| c.instance_variable_set(:@errors, errors) end end def add_entity attributes={} perform_entity_action "create", [attributes] end def update_entity entity_id, attributes={} perform_entity_action "update", [entity_id, attributes] end end end end end