aboutsummaryrefslogtreecommitdiffstats
path: root/app/models/simple_exporter.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/simple_exporter.rb')
-rw-r--r--app/models/simple_exporter.rb183
1 files changed, 183 insertions, 0 deletions
diff --git a/app/models/simple_exporter.rb b/app/models/simple_exporter.rb
new file mode 100644
index 000000000..c1ba75d0a
--- /dev/null
+++ b/app/models/simple_exporter.rb
@@ -0,0 +1,183 @@
+# coding: utf-8
+class SimpleExporter < SimpleInterface
+ def export opts={}
+ configuration.validate!
+
+ init_env opts
+
+ @csv = nil
+ fail_with_error "Unable to write in file: #{self.filepath}" do
+ dir = Pathname.new(self.filepath).dirname
+ FileUtils.mkdir_p dir
+ @csv = CSV.open(self.filepath, 'w', self.configuration.csv_options)
+ end
+
+ self.configuration.before_actions(:parsing).each do |action| action.call self end
+
+ @statuses = ""
+
+ if ENV["NO_TRANSACTION"]
+ process_collection
+ else
+ ActiveRecord::Base.transaction do
+ process_collection
+ end
+ end
+ self.status ||= :success
+ rescue SimpleInterface::FailedOperation
+ self.status = :failed
+ ensure
+ @csv&.close
+ self.save!
+ end
+
+ def collection
+ @collection ||= begin
+ coll = configuration.collection
+ coll = coll.call() if coll.is_a?(Proc)
+ coll
+ end
+ end
+
+ def encode_string s
+ s.encode("utf-8").force_encoding("utf-8")
+ end
+
+ protected
+ def init_env opts
+ @number_of_lines = collection.size
+
+ super opts
+ end
+
+ def process_collection
+ self.configuration.before_actions(:all).each do |action| action.call self end
+ log "Starting export ...", color: :green
+ log "Export will be written in #{filepath}", color: :green
+ @csv << self.configuration.columns.map(&:name)
+ if collection.is_a?(ActiveRecord::Relation) && collection.model.column_names.include?("id")
+ ids = collection.pluck :id
+ ids.in_groups_of(configuration.batch_size).each do |batch_ids|
+ collection.where(id: batch_ids).each do |item|
+ handle_item item
+ end
+ end
+ else
+ collection.each{|item| handle_item item }
+ end
+ print_state
+ end
+
+ def map_item_to_rows item
+ return [item] unless configuration.item_to_rows_mapping
+ configuration.item_to_rows_mapping.call(item).map {|row| row.is_a?(ActiveRecord::Base) ? row : CustomRow.new(row) }
+ end
+
+ def resolve_value item, col
+ scoped_item = col.scope.inject(item){|tmp, scope| tmp.send(scope)}
+ val = col[:value]
+ if val.nil? || val.is_a?(Proc)
+ if val.is_a?(Proc)
+ val = instance_exec(scoped_item, &val)
+ else
+ attributes = [col.attribute].flatten
+ val = attributes.inject(scoped_item){|tmp, attr| tmp.send(attr) if tmp }
+ end
+ end
+ if val.nil? && !col.omit_nil?
+ push_in_journal({event: :attribute_not_found, message: "Value missing for: #{[col.scope, col.attribute].flatten.join('.')}", kind: :warning})
+ self.status ||= :success_with_warnings
+ end
+
+ if val.nil? && col.required?
+ @new_status = colorize("x", :red)
+ raise "MISSING VALUE FOR COLUMN #{col.name}"
+ end
+
+ val = encode_string(val) if val.is_a?(String)
+ val
+ end
+
+ def handle_item item
+ number_of_lines = @number_of_lines
+ @current_item = item
+ map_item_to_rows(item).each_with_index do |item, i|
+ @number_of_lines = number_of_lines + i
+ @current_row = item.attributes
+ @current_row = @current_row.slice(*configuration.logged_attributes) if configuration.logged_attributes.present?
+ row = []
+ @new_status = nil
+ self.configuration.columns.each do |col|
+ val = resolve_value item, col
+ @new_status ||= colorize("✓", :green)
+ row << val
+ end
+ push_in_journal({event: :success, kind: :log})
+ @statuses += @new_status
+ print_state if @current_line % 20 == 0 || i > 0
+ @current_line += 1
+ @csv << row
+ end
+ end
+
+ class CustomRow < OpenStruct
+ def initialize data
+ super data
+ @data = data
+ end
+
+ def attributes
+ flatten_hash @data
+ end
+
+ protected
+ def flatten_hash h
+ h.each_with_object({}) do |(k, v), h|
+ if v.is_a? Hash
+ flatten_hash(v).map do |h_k, h_v|
+ h["#{k}.#{h_k}".to_sym] = h_v
+ end
+ else
+ h[k] = v
+ end
+ end
+ end
+ end
+
+ class Configuration < SimpleInterface::Configuration
+ attr_accessor :collection
+ attr_accessor :batch_size
+ attr_accessor :logged_attributes
+ attr_accessor :item_to_rows_mapping
+
+ def initialize import_name, opts={}
+ super import_name, opts
+ @collection = opts[:collection]
+ @batch_size = opts[:batch_size] || 1000
+ @logged_attributes = opts[:logged_attributes]
+ @item_to_rows_mapping = opts[:item_to_rows_mapping]
+ end
+
+ def options
+ super.update({
+ collection: collection,
+ batch_size: batch_size,
+ logged_attributes: logged_attributes,
+ item_to_rows_mapping: item_to_rows_mapping,
+ })
+ end
+
+ def map_item_to_rows &block
+ @item_to_rows_mapping = block
+ end
+
+ def add_column name, opts={}
+ raise "Column already defined: #{name}" if @columns.any?{|c| c.name == name.to_s}
+ super name, opts
+ end
+
+ def validate!
+ raise "Incomplete configuration, missing collection for #{@import_name}" if collection.nil?
+ end
+ end
+end