require 'spreadsheet' require 'active_support' module ActiveMigration class ActiveMigartionError < StandardError; end class ActiveMigartionDataSourceError < ActiveMigartionError; end class AciteMigrationInvalidRecordError < ActiveMigartionError; end class Schema ## # @attr columns Hash with name and type # @attr format Describe data format # @attr url define path or object schema attr_accessor :columns, :format, :url end ## # Module Data Transformation # When passing schema file or struct, the data between datasources # needs a transformations module Transformer ## # called on start of migration def begin(schema_from, schema_to) # nothing end ## # called on start of transaction def begin_transaction(schema_from, schema_to) # nothing end ## # transform from data row to destinate data row # @result true, false or :ignore to ignore this row def transform(row) raise "Implements transform method!" end ## # called on ending migration def end(schema_from, schema_to) # nothing end ## # called on ending transaction def end_transaction(schema_from, schema_to) # nothing end ## # def after_row_saved(row, object) # end def transform_ignore_fields(row) #delete ignore row.reject! { |key,value| key.to_s.start_with?("ignore") } end end ## # == Class Migration # # Migrate data between data source and transforme to destination # class Migration attr_accessor :schema_from, :schema_to, :transformer, :name # constructor def initialize(schema_url=nil) self.load_schema(schema_url) unless schema_url.nil? end ## # load yml schemas from and to def load_schema(url) schema = YAML::load(File.open(url)) self.schema_from = schema[:from] self.schema_to = schema[:to] end ## # Running migration from configured files # # ps> Default Behaviour Ignore First line - assumes head line def migrate! raise "schema_from needs" if @schema_from.nil? raise "schema_to needs" if @schema_to.nil? res = @transformer.begin_transaction(@schema_from, @schema_to) unless @transformer.nil? ActiveRecord::Base.transaction do begin_migration() # TODO: Make flexible configurable and more input formats if @schema_from[:format].to_s.to_sym == :XLS xls_migrate() end end_migration() end res = @transformer.end_transaction(@schema_from, @schema_to) unless @transformer.nil? return true end def xls_migrate begin @xls = Spreadsheet.open @schema_from[:url] # TODO: make others workbook accessible by configuration sheet = @xls.worksheet 0 @line = 0 # ignore head line sheet.each 1 do |row| @column = 0 row_to = { } #read schema columns and types @schema_from[:columns].each do |schema_column, schema_type| row_to.merge!(schema_column.to_sym => row[@column]) @column+=1 end #transform row to @schema_to res = true res = @transformer.transform(row_to) unless @transformer.nil? if (res!=:ignore) res = res==true && send_row_to_schema(row_to) raise_migration if (res==false) @transformer.after_row_saved(row_to, @last_object) unless @transformer.nil? end @line+=1 end rescue Exception => e line = @line.nil? ? 0 : @line column = @column.nil? ? 0 : @column raise ActiveMigartionDataSourceError.new ("Failing import excel source format from %s. %d:%d [ignored head]. " % [@schema_from[:url], column, line]).concat(e.message).concat("\n----"+e.backtrace.to_yaml) end end def begin_migration # TODO: make transactional res = @transformer.begin(@schema_from, @schema_to) unless @transformer.nil? end def end_migration res = @transformer.end(@schema_from, @schema_to) unless @transformer.nil? end def raise_migration raise "failing migration %s. Line: %d, Column: %d" % [@name, @line, @column] end def send_row_to_schema(row) if @schema_to[:format].to_sym == :ACTIVE_RECORD # TODO: optimize on initialize migration class_schema_to = eval @schema_to[:url] @last_object = class_schema_to.new(row) res = @last_object.save if (!res) raise AciteMigrationInvalidRecordError.new "[Schema:%s] Error on send to ACTIVE_RECORD %s. \n%s \nrow: \n%s" % [@name, @schema_to[:url], @last_object.errors.to_yaml, row.to_yaml] end return res else raise "Not valid schema::TO format! %s" % @name end end ## # loads yml file and convert to hash on schema_from # @deprecated def load_schema_from(url) self.schema_from = YAML::load(File.open(url)) end ## # load yml file and convert to hash on schema_to # @deprecated def load_schema_to(url) self.schema_to = YAML::load(File.open(url)) end end end