Files
dunlop-core/app/models/dunlop/target_file_model.rb
T

225 lines
5.9 KiB
Ruby

module Dunlop::TargetFileModel
extend ActiveSupport::Concern
extend Memoist
included do
include Dunlop::Loggable
has_many :downloads, class_name: 'Dunlop::FileDownload', as: :file
attr_accessor :working_file, :original_file
attr_accessor :resource
mount_uploader :original_file, Dunlop::TargetFileUploader
# used by dunlop-file_transfer
def default_file
original_file
end
state_machine initial: :new do
before_transition on: :generating, do: :set_start_time
before_transition on: [:completed,:failed], do: :set_end_time
event(:scheduled) { transition new: :scheduled }
event(:generating) { transition scheduled: :generating }
event(:completed) { transition generating: :completed }
event(:failed) { transition any => :failed }
end
end
def set_start_time
self.start_time = Time.zone.now
end
def set_end_time
self.end_time = Time.zone.now
end
def duration
if start_time and end_time
end_time - start_time
elsif start_time
Time.zone.now - start_time
else
0
end
end
def generate_and_post_process_file
self.working_file = new_working_file
generate_file
post_process_file
attach_target_file
post_generate_hook
ensure
working_file.close! rescue SystemCallError # container permission issue
end
def local_target_filename
result = File.join(Rails.application.config.working_path, target_basename)
gzip_file? ? result + ".gz" : result
end
# TODO: consider builder.file_name if builder option is used by default.
# This will synchronize file names between regular controller downloads and
# target file creation.
def target_basename
"#{self.class.name.demodulize.underscore}-#{date_number}.csv"
end
def post_generate_hook
# hook to implement post generation actions like sending by email and/or to other systems by means of web call and/or to create a transfer instance
end
# Implement in Subclass. Example:
# CsvBuilder::BladiBla.new(resource).data
def generate_file
working_file.puts builder.data
end
def builder
@builder ||= self.class.builder_class.new(resource, builder_options)
end
# can be implemented by specific builder classes, for customized builder behaviour
def builder_options
{}
end
def header_line_count
builder.headers.present? ? 1 : 0
end
def post_process_file
self.working_file.close
self.number_of_records = line_count
self.number_of_records -= header_line_count
gzip_file if gzip_file?
self.size = working_file_size
self.save!
end
def attach_target_file
%x[cp #{Shellwords.escape(working_file.path)} #{Shellwords.escape(local_target_filename)}]
self.original_file = File.open(local_target_filename, 'r')
self.save!
ensure
FileUtils.rm_f(local_target_filename)
end
def new_working_file
#local_working_file = Tempfile.new("target_file", Rails.application.config.working_path)
local_working_file = Tempfile.new([target_basename, File.extname(target_basename)], Rails.application.config.working_path) # keep reference for tempfile till unzip completed and keep extension preserved
FileUtils.chmod(0666, local_working_file.path)
local_working_file
end
def gzip_file?
false #override in subclass
end
def gzip_file
return if Dunlop::FileZip.mime_type(working_file.path) == "application/x-gzip" # no double action and working file replacement
local_working_file = new_working_file
Dunlop::FileZip.gzip(working_file.path,local_working_file.path)
working_file.unlink rescue SystemCallError # container permission issue
self.working_file = local_working_file
end
def line_count
%x[wc -l #{Shellwords.escape(working_file.path)}].to_i
end
def sql_template_path
Rails.root.join("app/models/target_file/sql_templates")
end
def prepare_working_file_for_sql_generation
working_file.close
FileUtils.rm_f(working_file.path)
end
def execute_generation_process
with_nested_logger_and_catch_failed do
scheduled! unless scheduled?
generating!
generate_and_post_process_file
completed!
self # return generated file instance
end
end
def date_number
Date.current.to_s(:number)
end
module ClassMethods
def factory(attrs)
attrs ||= {}
klass = classes.find { |c| c.to_s == attrs[:sti_type].to_s } || self
klass.new(attrs)
end
def classes
@classes ||= class_names.map(&:constantize)
end
def class_names
target_file_names.map{|tfn| tfn.is_a?(Class) ? tfn.name : "#{self.name}::#{tfn.to_s.camelize}"}
end
def setup_target_files(target_file_names)
@target_file_names = target_file_names
end
def target_file_names
@target_file_names
end
def latest
completed.last
end
def completed
where(state: 'completed')
end
def activate_dunlop!
end
# Allow resource as argument. Changed from resource to rsrc to be sure to avoid method naming collision confusion
def generate!(rsrc = nil)
new_record = create
new_record.resource = rsrc if rsrc.present?
new_record.execute_generation_process
new_record
end
def cleanup
#noop, can't make assumptions here
end
def create_from_source_file!(source_file)
target_file = self.new(
working_file: File.open(source_file.original_file.path, 'r'),
state: 'generating',
start_time: Time.now,
number_of_records: source_file.number_of_records,
)
target_file.size = File.size(target_file.working_file.path)
target_file.attach_target_file
target_file.completed!
target_file
end
def builder_class
"#{name}CsvBuilder".safe_constantize || ::CsvBuilder
end
end
private
def working_file_size
File.size(working_file.path)
end
end