aboutsummaryrefslogtreecommitdiffstats
path: root/app/models/simple_exporter.rb
blob: f9b1c0a806b4a23e2a4b4cee2317c3e79b6e6426 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
class SimpleExporter < SimpleInterface
  def export opts={}
    configuration.validate!
    @verbose = opts.delete :verbose

    @resolution_queue = Hash.new{|h,k| h[k] = []}
    @errors = []
    @messages = []
    @number_of_lines = 0
    @padding = 1
    @current_line = -1
    @number_of_lines = collection.size
    @padding = [1, Math.log(@number_of_lines, 10).ceil()].max

    @csv = nil
    fail_with_error "Unable to write in file: #{self.filepath}" do
      @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 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)
    ids = collection.pluck :id
    ids.in_groups_of(configuration.batch_size).each do |batch_ids|
      collection.where(id: batch_ids).each do |item|
        @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 = col[:value]
          if val.nil? || val.is_a?(Proc)
            if item.respond_to? col.attribute
              if val.is_a?(Proc)
                val = instance_exec(item.send(col.attribute), &val)
              else
                val = item.send(col.attribute)
              end
            else
              push_in_journal({event: :attribute_not_found, message: "Attribute not found: #{col.attribute}", kind: :warning})
              self.status ||= :success_with_warnings
            end
          end

          if val.nil? && col.required?
            @new_status = colorize("x", :red)
            raise "MISSING VALUE FOR COLUMN #{col.name}"
          end
          @new_status ||= colorize("✓", :green)
          val = encode_string(val) if val.is_a?(String)
          row << val
        end
        push_in_journal({event: :success, kind: :log})
        @statuses += @new_status
        print_state if @current_line % 20 == 0
        @current_line += 1
        @csv << row
      end
    end
    print_state
  end

  class Configuration < SimpleInterface::Configuration
    attr_accessor :collection
    attr_accessor :batch_size
    attr_accessor :logged_attributes

    def initialize import_name, opts={}
      super import_name, opts
      @collection = opts[:collection]
      @batch_size = opts[:batch_size] || 1000
      @logged_attributes = opts[:logged_attributes]
    end

    def options
      super.update({
        collection: collection,
        batch_size: batch_size,
        logged_attributes: logged_attributes,
      })
    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