app/models/setup/legacy_translator.rb
module Setup
class LegacyTranslator < Translator
include ReqRejValidator
include SnippetCode
# = Translator
#
# A translator defines a logic for data manipulation
abstract_class true
legacy_code_attribute :transformation
field :type, type: StringifiedSymbol, default: -> { self.class.transformation_type }
belongs_to :source_data_type, class_name: Setup::DataType.to_s, inverse_of: nil
belongs_to :target_data_type, class_name: Setup::DataType.to_s, inverse_of: nil
field :discard_events, type: Mongoid::Boolean
field :style, type: String
field :mime_type, type: String
field :file_extension, type: String
field :bulk_source, type: Mongoid::Boolean, default: false
field :source_handler, type: Mongoid::Boolean
belongs_to :source_exporter, class_name: Setup::Translator.to_s, inverse_of: nil
belongs_to :target_importer, class_name: Setup::Translator.to_s, inverse_of: nil
field :discard_chained_records, type: Mongoid::Boolean
before_save :validates_configuration, :validates_code
def validates_configuration
requires(:name)
errors.add(:type, 'is not valid') unless type_enum.include?(type)
errors.add(:style, 'is not valid') unless style_enum.include?(style)
case type
when :Import, :Update
rejects(:source_data_type, :mime_type, :file_extension, :bulk_source, :source_exporter, :target_importer, :discard_chained_records)
requires(:code)
rejects(:source_handler) if type == :Import
when :Export
rejects(:target_data_type, :source_handler, :source_exporter, :target_importer, :discard_chained_records)
requires(:code)
if bulk_source && NON_BULK_SOURCE_STYLES.include?(style)
errors.add(:bulk_source, "is not allowed with '#{style}' style")
self.bulk_source = false
end
if mime_type.present?
if (extensions = file_extension_enum).empty?
self.file_extension = nil
elsif file_extension.blank?
extensions.length == 1 ? (self.file_extension = extensions[0]) : errors.add(:file_extension, 'has multiple options')
else
errors.add(:file_extension, 'is not valid') unless extensions.include?(file_extension)
end
end
when :Conversion
rejects(:mime_type, :file_extension, :bulk_source)
requires(:source_data_type)
requires(:target_data_type) unless source_handler
if style == 'chain'
requires(:source_exporter, :target_importer)
rejects(:source_handler)
if errors.blank?
errors.add(:source_exporter, "can't be applied to #{source_data_type.title}") unless source_exporter.apply_to_source?(source_data_type)
errors.add(:target_importer, "can't be applied to #{target_data_type.title}") unless target_importer.apply_to_target?(target_data_type)
end
self.code = "#{source_data_type.title} -> [#{source_exporter.name} : #{target_importer.name}] -> #{target_data_type.title}" if errors.blank?
elsif style == 'mapping'
self.code = "Mapping #{source_data_type.title} to #{target_data_type.title}" if errors.blank?
else
requires(:code)
rejects(:source_exporter, :target_importer)
rejects(:source_handler) unless style == 'ruby'
end
end
abort_if_has_errors
end
def type_enum
self.class.type_enum
end
def validates_code
if style == 'ruby'
Capataz.validate(code).each { |error| errors.add(:code, error) }
end
abort_if_has_errors
end
def reject_message(field = nil)
(style && type).present? ? "is not allowed for #{style} #{type.to_s.downcase} translators" : super
end
def source_bulkable?
type == :Export && !NON_BULK_SOURCE_STYLES.include?(style)
end
NON_BULK_SOURCE_STYLES = %w(double_curly_braces xslt liquid)
STYLES_MAP = {
'liquid' => { Setup::Transformation::LiquidExportTransform => [:Export],
Setup::Transformation::LiquidConversionTransform => [:Conversion] },
'xslt' => { Setup::Transformation::XsltConversionTransform => [:Conversion],
Setup::Transformation::XsltExportTransform => [:Export] },
# 'json.rabl' => {Setup::Transformation::ActionViewTransform => [:Export]},
# 'xml.rabl' => {Setup::Transformation::ActionViewTransform => [:Export]},
# 'xml.builder' => {Setup::Transformation::ActionViewTransform => [:Export]},
# 'html.haml' => {Setup::Transformation::ActionViewTransform => [:Export]},
'html.erb' => { Setup::Transformation::ActionViewTransform => [:Export] },
# 'csv.erb' => {Setup::Transformation::ActionViewTransform => [:Export]},
'js.erb' => { Setup::Transformation::ActionViewTransform => [:Export] },
# 'text.erb' => {Setup::Transformation::ActionViewTransform => [:Export]},
'ruby' => { Setup::Transformation::Ruby => [:Import, :Export, :Update, :Conversion] },
'pdf.prawn' => { Setup::Transformation::PrawnTransform => [:Export] },
#'chain' => { Setup::Transformation::ChainTransform => [:Conversion] },
'mapping' => { Setup::Transformation::MappingTransform => [:Conversion] }
}
def code_extension
case style
when 'ruby', 'pdf.prawn'
'.rb'
when 'chain'
''
else
".#{style}"
end
end
EXPORT_MIME_FILTER = {
'double_curly_braces': ['application/json'],
'xslt': %w(application/xml text/html),
'json.rabl': ['application/json'],
'xml.rabl': ['application/xml'],
'xml.builder': ['application/xml'],
'html.haml': ['text/html'],
'html.erb': ['text/html'],
'csv.erb': ['text/csv'],
'js.erb': %w(application/x-javascript application/javascript text/javascript),
'text.erb': ['text/plain'],
'pdf.prawn': ['application/pdf']
}.stringify_keys
def style_enum
styles = []
STYLES_MAP.each { |key, value| styles << key if value.values.detect { |types| types.include?(type) } } if type.present?
styles.uniq
end
def mime_type_enum
EXPORT_MIME_FILTER[style] || ::MIME::Types.inject([]) { |types, t| types << t.to_s }
end
def file_extension_enum
extensions = []
if (types = ::MIME::Types[mime_type])
types.each { |type| extensions.concat(type.extensions) }
end
extensions.uniq
end
def ready_to_save?
(type && style).present? && (style != 'chain' || (source_data_type && target_data_type && source_exporter).present?)
end
def can_be_restarted?
type.present?
end
def data_type
(type == :Import || type == :Update) ? target_data_type : source_data_type
end
def apply_to_source?(data_type)
source_data_type.blank? || source_data_type == data_type
end
def apply_to_target?(data_type)
target_data_type.blank? || target_data_type == data_type
end
def run(options = {})
context_options = try("context_options_for_#{type.to_s.downcase}", options) || {}
self.class.fields.keys.each { |key| context_options[key.to_sym] = send(key) }
self.class.relations.keys.each { |key| context_options[key.to_sym] = send(key) }
context_options[:data_type] = data_type
context_options.merge!(options) { |_, context_val, options_val| !context_val ? options_val : context_val }
context_options[:options] ||= {}
# TODO: Remove transformation local after migration
context_options[:transformation] = context_options[:code] = code
context_options[:target_data_type].regist_creation_listener(self) if context_options[:target_data_type]
context_options[:source_data_type].regist_creation_listener(self) if context_options[:source_data_type]
context_options[:translator] = self
begin
context_options[:result] = STYLES_MAP[style].keys.detect { |t| STYLES_MAP[style][t].include?(type) }.run(context_options)
rescue Exception => ex
ex.backtrace.unshift("In translator #{namespace}::#{name}")
raise ex
end
context_options[:target_data_type].unregist_creation_listener(self) if context_options[:target_data_type]
context_options[:source_data_type].unregist_creation_listener(self) if context_options[:source_data_type]
try("after_run_#{type.to_s.downcase}", context_options)
context_options[:result]
end
def before_create(record)
record.instance_variable_set(:@discard_event_lookup, true) if discard_events
if type == :Conversion && discard_chained_records
record.orm_model.data_type == target_data_type
else
true
end
end
def context_options_for_import(options)
raise Exception.new('Target data type not defined') unless (data_type = target_data_type || options[:target_data_type])
{ target_data_type: data_type, targets: Set.new }
end
def source_options(options, source_key_options)
data_type_key = source_key_options[:data_type_key] || :source_data_type
if (data_type = send(data_type_key) || options[data_type_key] || options[:data_type])
model = data_type.records_model
offset = options[:offset] || 0
limit = options[:limit]
source_options =
if source_key_options[:bulk]
{
source_key_options[:sources_key] || :sources =>
if (object_ids = options[:object_ids])
model.any_in(id: (limit ? object_ids[offset, limit] : object_ids.from(offset))).to_enum
elsif (objects = options[:objects])
objects
else
enum = (limit ? model.limit(limit) : model.all).skip(offset).to_enum
options[:object_ids] = enum.collect { |obj| obj.id.is_a?(BSON::ObjectId) ? obj.id.to_s : obj.id }
enum
end
}
else
{
source_key_options[:source_key] || :source =>
begin
obj = options[:object] ||
((id = (options[:object_id] || (options[:object_ids] && options[:object_ids][offset]))) && model.where(id: id).first) ||
model.all.skip(offset).first
options[:object_ids] = [obj.id.is_a?(BSON::ObjectId) ? obj.id.to_s : obj.id] unless options[:object_ids] || obj.nil?
obj
end
}
end
{ source_data_type: data_type }.merge(source_options)
else
{}
end
end
def context_options_for_export(options)
source_options(options, bulk: bulk_source)
end
def context_options_for_update(options)
source_options(options, data_type_key: :target_data_type, bulk: source_handler, sources_key: :targets, source_key: :target)
end
def context_options_for_conversion(options)
if source_handler
source_options(options, bulk: true)
else
{ source: options[:object], target: style == 'ruby' ? target_data_type.records_model.new : nil }
end
end
def after_run_update(options)
if (target = options[:object])
target.instance_variable_set(:@discard_event_lookup, options[:discard_events])
fail TransformingObjectException.new(target) unless Cenit::Utility.save(target)
end
options[:result] = target
end
def after_run_conversion(options)
return unless (target = options[:target])
if options[:save_result].blank? || options[:save_result]
target.instance_variable_set(:@discard_event_lookup, options[:discard_events])
fail TransformingObjectException.new(target) unless Cenit::Utility.save(target)
end
options[:result] = target
end
def link?(call_symbol)
link(call_symbol).present?
end
def link(call_symbol)
Setup::Algorithm.where(name: call_symbol).first
end
def linker_id
't' + id.to_s
end
class << self
def mime_type_filter_enum
Setup::Renderer.where(:mime_type.ne => nil).distinct(:mime_type).flatten.uniq
end
def file_extension_filter_enum
Setup::Renderer.where(:file_extension.ne => nil).distinct(:file_extension).flatten.uniq
end
end
end
class TransformingObjectException < Exception
attr_reader :object
def initialize(object)
msg =
if object.new_record?
"Creating object of type #{object.orm_model.data_type.custom_title}: "
else
"Transforming object #{object.id} of type #{object.orm_model.data_type.custom_title}: "
end + object.errors.full_messages.to_sentence
@object = object
super(msg)
end
end
end