app/models/concerns/local_import_support.rb
# frozen_string_literal: true
module LocalImportSupport
extend ActiveSupport::Concern
included do |_into|
include ImportResourcesSupport
after_commit :import_async, on: :create
delegate :line_referential, :stop_area_referential, to: :workbench
end
def import_async
enqueue_job :import
end
def import_type
self.class.name.demodulize.underscore
end
def import
Chouette::Benchmark.measure "import_#{import_type}", id: id do
update status: 'running', started_at: Time.now
@progress = 0
ActiveRecord::Base.cache do
import_without_status
end
processor.after([referential])
@progress = nil
@status ||= 'successful'
referential&.active!
update status: @status, ended_at: Time.now
end
rescue StandardError => e
update status: 'failed', ended_at: Time.now
Chouette::Safe.capture "#{self.class.name} ##{id} failed", e
if (referential && overlapped_referential_ids = referential.overlapped_referential_ids).present?
overlapped = Referential.find overlapped_referential_ids.last
create_message(
criticity: :error,
message_attributes: {
referential_name: referential.name,
overlapped_name: overlapped.name,
overlapped_url: Rails.application.routes.url_helpers.referential_path(overlapped)
},
message_key: 'referential_creation_overlapping_existing_referential'
)
else
create_message criticity: :error, message_key: :full_text, message_attributes: { text: e.message }
end
referential&.failed!
ensure
main_resource&.save
save
notify_parent
end
def processor
@processor ||= Processor.new(self)
end
def worker_died
force_failure!
Rails.logger.error "#{self.class.name} #{inspect} failed due to worker being dead"
end
def import_resources(*resources)
resources.each do |resource|
Chouette::Benchmark.measure resource do
send "import_#{resource}"
end
end
end
def create_referential
Chouette::Benchmark.measure 'create_referential' do
self.referential ||= Referential.new(
name: referential_name,
organisation_id: workbench.organisation_id,
workbench_id: workbench.id,
metadatas: [referential_metadata],
ready: false
)
begin
self.referential.save!
rescue ActiveRecord::RecordInvalid
# No double capture for Chouette::Safe
Rails.logger.error "Unable to create referential: #{self.referential.errors.messages}"
raise
end
main_resource.update referential: referential if main_resource
end
end
def referential_name
name.presence || File.basename(local_file.to_s)
end
def notify_parent
Rails.logger.info "#{self.class.name} ##{id}: notify_parent #{caller[0..2].inspect}"
main_resource&.update_status_from_importer status
super
end
attr_accessor :local_file, :download_host
def local_file
@local_file ||= download_local_file
end
def download_host
@download_host ||= Rails.application.config.rails_host
end
def local_temp_directory
@local_temp_directory ||=
begin
directory = Rails.application.config.try(:import_temporary_directory) || Rails.root.join('tmp', 'imports')
FileUtils.mkdir_p directory
directory
end
end
def local_temp_file
file = Tempfile.open('chouette-import', local_temp_directory)
file.binmode
yield file
end
def download_path
# FIXME: See CHOUETTE-205
Rails.application.routes.url_helpers.internal_download_workbench_import_path(workbench, id, token: token_download)
end
def download_uri
@download_uri ||=
begin
host = download_host
host = "http://#{host}" unless host =~ %r{https?://}
URI.join(host, download_path)
end
end
def download_local_file
local_temp_file do |file|
Net::HTTP.start(download_uri.host, download_uri.port) do |http|
http.request_get(download_uri.request_uri) do |response|
response.read_body do |segment|
file.write segment
end
end
end
file.rewind
file
end
end
def save_model(model, filename: nil, line_number: nil, column_number: nil, resource: nil)
return unless model.changed?
if resource
filename ||= "#{resource.name}.txt"
line_number ||= resource.rows_count
column_number ||= 0
end
unless model.save
Rails.logger.error "Can't save #{model.class.name} : #{model.errors.inspect}"
# if the model cannot be saved, we still ensure we store a consistent checksum
model.try(:update_checksum_without_callbacks!) if model.persisted?
model.errors.details.each do |key, messages|
messages.uniq.each do |message|
message.each do |criticity, error|
next unless Import::Message.criticity.values.include?(criticity.to_s)
create_message(
{
criticity: criticity,
message_key: error,
message_attributes: {
test_id: key,
object_attribute: key,
source_attribute: key
},
resource_attributes: {
filename: filename,
line_number: line_number,
column_number: column_number
}
},
resource: resource,
commit: true
)
end
end
end
@models_in_error ||= Hash.new { |hash, key| hash[key] = [] }
@models_in_error[model.class.name] << model_key(model)
@status = 'failed'
return
end
Rails.logger.debug "Created #{model.inspect}"
end
def check_parent_is_valid_or_create_message(klass, key, resource)
if @models_in_error&.key?(klass.name) && @models_in_error[klass.name].include?(key)
create_message(
{
criticity: :error,
message_key: :invalid_parent,
message_attributes: {
parent_class: klass,
parent_key: key,
test_id: :parent
},
resource_attributes: {
filename: "#{resource.name}.txt",
line_number: resource.rows_count,
column_number: 0
}
},
resource: resource, commit: true
)
return false
end
true
end
def unless_parent_model_in_error(klass, key, resource)
return unless check_parent_is_valid_or_create_message(klass, key, resource)
yield
end
def model_key(model)
return model.registration_number if model.respond_to?(:registration_number)
return model.comment if model.is_a?(Chouette::TimeTable)
return model.checksum_source if model.is_a?(Chouette::VehicleJourneyAtStop)
model.objectid
end
end