require 'digest/md5' module SpatialFeatures module FeatureImport extend ActiveSupport::Concern included do extend ActiveModel::Callbacks define_model_callbacks :update_features end def update_features!(skip_invalid: false, options: {}) options = options.reverse_merge(spatial_features_options).reverse_merge(:import => {}) ActiveRecord::Base.transaction do imports = spatial_feature_imports(options[:import], options[:make_valid]) cache_key = Digest::MD5.hexdigest(imports.collect(&:cache_key).join) return if features_cache_key_matches?(cache_key) run_callbacks :update_features do import_features(imports, skip_invalid) set_features_cache_key(cache_key) end return true end end private def spatial_feature_imports(import_options, make_valid) import_options.collect do |data_method, importer_name| data = send(data_method) "SpatialFeatures::Importers::#{importer_name}".constantize.new(data, :make_valid => make_valid) if data.present? end.compact end def import_features(imports, skip_invalid) self.features.delete_all valid, invalid = imports.flat_map(&:features).partition do |feature| feature.spatial_model = self feature.save end if !skip_invalid && invalid.present? errors = imports.flat_map(&:errors) invalid.each do |feature| errors << "Feature #{feature.name}: #{feature.errors.full_messages.to_sentence}" end raise ImportError, "Error updating #{self.class} #{self.id}. #{errors.to_sentence}" end self.features = valid end def features_cache_key_matches?(cache_key) has_spatial_features_hash? && cache_key == features_hash end def set_features_cache_key(cache_key) return unless has_spatial_features_hash? self.features_hash = cache_key update_column(:features_hash, cache_key) unless new_record? end end class ImportError < StandardError; end end