Class: Effective::CsvImporter

Inherits:
Object
  • Object
show all
Defined in:
app/models/effective/csv_importer.rb

Constant Summary collapse

A =
0
B =
1
C =
2
D =
3
E =
4
F =
5
G =
6
H =
7
I =
8
J =
9
K =
10
L =
11
M =
12
N =
13
O =
14
P =
15
Q =
16
R =
17
S =
18
T =
19
U =
20
V =
21
W =
22
X =
23
Y =
24
Z =
25
AA =
26
AB =
27
AC =
28
AD =
29
AE =
30
AF =
31
AG =
32
AH =
33
AI =
34
AJ =
35
AK =
36
AL =
37
AM =
38
AN =
39
AO =
40
AP =
41
AQ =
42
AR =
43
AS =
44
AT =
45

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(csv_file = default_csv_files(), header: true) ⇒ CsvImporter

Returns a new instance of CsvImporter.



12
13
14
15
16
17
# File 'app/models/effective/csv_importer.rb', line 12

def initialize(csv_file = default_csv_files(), header: true)
  @has_header_row = header

  @csv_file = Array(csv_file).find { |csv_file| File.exist?(csv_file) }
  raise "#{csv_file} does not exist" unless @csv_file
end

Instance Attribute Details

#csv_fileObject (readonly)

Returns the value of attribute csv_file.



5
6
7
# File 'app/models/effective/csv_importer.rb', line 5

def csv_file
  @csv_file
end

#current_rowObject (readonly)

Returns the value of attribute current_row.



5
6
7
# File 'app/models/effective/csv_importer.rb', line 5

def current_row
  @current_row
end

#last_rowObject (readonly)

Returns the value of attribute last_row.



5
6
7
# File 'app/models/effective/csv_importer.rb', line 5

def last_row
  @last_row
end

Instance Method Details

#after_importObject



29
# File 'app/models/effective/csv_importer.rb', line 29

def after_import ; end

#assign_columns(obj, only: [], except: []) ⇒ Object

Takes an object and loops through all columns assigning the current row values



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'app/models/effective/csv_importer.rb', line 111

def assign_columns(obj, only: [], except: [])
  assigns = (
    if only.present?
      only = Array(only)
      columns.keep_if { |key, _| only.include?(key) }
    elsif except.present?
      except = Array(except)
      columns.delete_if { |key, _| except.include?(key) }
    end
  )

  (assigns || columns).each do |column, _|
    obj.send("#{column}=", col(column)) if obj.respond_to?(column)
  end

  obj
end

#assign_valid_email(user, at: 'example.com') ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'app/models/effective/csv_importer.rb', line 129

def assign_valid_email(user, at: 'example.com')
  raise 'expected an object that responds to email=' unless user.respond_to?('email=')

  # Normalize email
  user.email = user.email.to_s.strip.downcase.gsub(' ', '').presence
  user.email = nil unless user.email.to_s.count('@') == 1

  # Assign if empty
  if user.email.blank?
    if user.respond_to?(:first_name) && user.respond_to?(:last_name)
      user.email ||= [user.first_name.to_s.parameterize.presence, user.last_name.to_s.parameterize.presence].compact.join('.').presence
    end

    if user.respond_to?(:full_name)
      user.email ||= user.full_name.to_s.parameterize.presence
    end

    if user.respond_to?(:name)
      user.email ||= user.name.to_s.parameterize.presence
    end

    user.email ||= user.object_id

    user.email = "#{user.email}@#{at.sub('@', '')}"
  end

  # Check for uniqueness
  unique = 0
  email = user.email

  while user.class.where(email: email).where.not(id: user.id).present?
    pieces = user.email.split('@')
    email = pieces.first + "+#{(unique += 1)}@" + pieces.last
  end

  user.email = email

  user
end

#before_importObject

Override me if you need some before/after hooks



28
# File 'app/models/effective/csv_importer.rb', line 28

def before_import ; end

#columnsObject



19
20
21
# File 'app/models/effective/csv_importer.rb', line 19

def columns
  raise "Please define a method 'def columns' returning a Hash of {id: A, name: B}"
end

#error(message) ⇒ Object



173
174
175
176
# File 'app/models/effective/csv_importer.rb', line 173

def error(message)
  @errors_count += 1
  puts "\n#{colorize('Error', :red)} (.csv line #{@current_row_number}) #{message}"
end

#find(attributes) ⇒ Object



70
71
72
# File 'app/models/effective/csv_importer.rb', line 70

def find(attributes)
  where(attributes).first
end

#find!(attributes) ⇒ Object



74
75
76
# File 'app/models/effective/csv_importer.rb', line 74

def find!(attributes)
  find(attributes).presence || raise("csv row with #{attributes} not found")
end

#import!Object

This runs through each row and calls process_row() on it



32
33
34
35
36
37
38
39
40
41
42
# File 'app/models/effective/csv_importer.rb', line 32

def import!
  log "Importing #{csv_file.split('/').last.sub('.csv', '')}"

  @errors_count = 0

  before_import()
  with_each_row { process_row }
  after_import()

  log "Import complete (#{@errors_count} errors in #{@has_header_row ? @current_row_number-1 : @current_row_number} rows)"
end

#log(message) ⇒ Object



169
170
171
# File 'app/models/effective/csv_importer.rb', line 169

def log(message)
  puts "\n#{message}";
end

#normalize(column, value) ⇒ Object

Normalize the value based on column name



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
# File 'app/models/effective/csv_importer.rb', line 79

def normalize(column, value)
  column = column.to_s
  value = value.to_s

  if column.ends_with?('?')  # Boolean
    truthy?(value)
  elsif column.ends_with?('_at')  # DateTime
    parse_datetime(column, value)
  elsif column.ends_with?('_on')  # Date
    parse_datetime(column, value).beginning_of_day
  elsif column.ends_with?('_to_i')
    value.to_i
  elsif column.ends_with?('_to_f')
    value.to_f
  elsif column.ends_with?('_to_s')
    value.to_s
  elsif column.ends_with?('_to_a')
    if ['[]', '{}'].include?(value)
      []
    elsif value.starts_with?('{') && value.ends_with?('}')
      YAML::load(value).keys.select { |str| str.to_s.present? }
    else
      YAML::load(value).to_a.select { |str| str.to_s.present? }
    end
  elsif column == 'id' || column.ends_with?('_id')
    value.present? ? value.to_i : nil
  else
    value.presence
  end
end

#process_rowObject



23
24
25
# File 'app/models/effective/csv_importer.rb', line 23

def process_row
  raise "Please define a method 'def process_row' to process your row"
end

#rowsObject

Returns an Array of Arrays, with each row run through normalize



45
46
47
48
49
50
51
# File 'app/models/effective/csv_importer.rb', line 45

def rows
  @rows ||= [].tap do |rows|
    CSV.foreach(csv_file, headers: @has_header_row) do |row|
      rows << columns.map { |column, index| normalize(column, row[index].try(:strip).presence) }
    end
  end
end

#warn(message) ⇒ Object



178
179
180
# File 'app/models/effective/csv_importer.rb', line 178

def warn(message)
  puts "\n#{colorize('Warning', :yellow)} (.csv line #{@current_row_number}) #{message}"
end

#where(attributes) ⇒ Object

UserStudentInfosImporter.new().where(id: 3, title: ‘thing’) Returns an Array of Hashes, representing any row that matches the selector



55
56
57
58
59
60
61
62
63
64
# File 'app/models/effective/csv_importer.rb', line 55

def where(attributes)
  raise 'expected a Hash of attributes' unless attributes.kind_of?(Hash)
  attributes.each { |column, _| raise "unknown column :#{column}" unless columns.key?(column) }

  rows.map do |row|
    if attributes.all? { |column, value| row[columns[column]] == value }
      columns.inject({}) { |retval, (column, index)| retval[column] = row[index]; retval }
    end
  end.compact
end

#where!(attributes) ⇒ Object



66
67
68
# File 'app/models/effective/csv_importer.rb', line 66

def where!(attributes)
  where(attributes).presence || raise("csv row with #{attributes} not found")
end