Class: Import

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
Imports::Processing, Imports::Status
Defined in:
app/models/import.rb

Defined Under Namespace

Classes: RowError, RuntimeError

Constant Summary collapse

DATE_INPUT_FORMAT =
"%m/%d/%Y"
DATE_INPUT_FORMAT_WITH_TIME =
"%m/%d/%Y %l:%M%P"

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Imports::Processing

#csv_data, included, #s3_service, #time_zone_parser

Methods included from Imports::Status

#approve!, #caching!, #failed!, #failed?, #imported!, #importing!, included, #invalidate!, #pending!, #recalled!, #recalling!

Class Method Details

.build(type) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
# File 'app/models/import.rb', line 25

def self.build(type)
  case type
  when "events"
    EventsImport.new
  when "people"
    PeopleImport.new
  when "donations"
    DonationsImport.new
  else
    nil
  end
end

Instance Method Details

#attach_person(parsed_row) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'app/models/import.rb', line 149

def attach_person(parsed_row)
  person = self.people.build(parsed_row.person_attributes)
  person.organization = self.organization

  if person.subtype.downcase =~ /business|foundation|government|nonprofit|other/
    person.type = "Company"
  else
    person.type = "Individual"
  end

  person.build_address(hash_address(parsed_row))
  person.tag_list = parsed_row.tags_list.join(", ")

  1.upto(3) do |n|
    kind = parsed_row.send("phone#{n}_type")
    number = parsed_row.send("phone#{n}_number")
    if kind.present? && number.present?
      person.phones << Phone.new(kind: kind, number: number)
    end
  end
  person.skip_commit = true
  person
end

#cache_dataObject



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'app/models/import.rb', line 105

def cache_data
  raise "Cannot load CSV data" unless csv_data.present?

  self.import_headers = nil
  self.import_rows.delete_all
  self.import_errors.delete_all

  csv_data.gsub!(/\\"(?!,)/, '""') # Fix improperly escaped quotes.

  CSV.parse(csv_data, :headers => false) do |row|
    if self.import_headers.nil?
      self.import_headers = row.to_a
      #TODO: Validate headers right here
      self.save!
    else
      self.import_rows.create!(:content => row.to_a)
      parsed_row = ParsedRow.parse(self.import_headers, row.to_a)
      
      unless row_valid?(parsed_row)
        self.invalidate! 
        return
      end
    end
  end

  self.pending!
  
#TODO: Needs to be re-worked to include the row number in the error
rescue CSV::MalformedCSVError => e
  error_message = "There was an error while parsing the CSV document: #{e.message}"
  self.import_errors.create!(:error_message => error_message)
  self.invalidate!
rescue Exception => e
  self.import_errors.create!(:error_message => e.message)
  self.invalidate!
rescue Import::RowError => e
  self.import_errors.create!(:error_message => e.message)
  self.invalidate!
end

#create_person(parsed_row) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/models/import.rb', line 173

def create_person(parsed_row)
  if !attach_person(parsed_row).naming_details_available?
    person = self.organization.dummy
  elsif !parsed_row.email.blank?
    person = Person.first_or_create({:email => parsed_row.email, :organization => self.organization}.merge(parsed_row.person_attributes), {}) do |p|
      p.import = self
    end
    person.update_from_import(attach_person(parsed_row))
    person.skip_commit = true
    person.save
  else    
    person = attach_person(parsed_row)
    if !person.save
      self.reload
      fail!(RowError.new(person.errors.full_messages.join(", ")), parsed_row.row)
    end 
  end
  person  
end

#fail!(error, row = nil, row_num = 0) ⇒ Object

This composes errors thrown during the import. For validation errors, see invalidate!



76
77
78
79
80
81
82
# File 'app/models/import.rb', line 76

def fail!(error, row = nil, row_num = 0)
  Rails.logger.error "IMPORT ERROR [#{self.id}]: #{error.message}"
  error.backtrace.each { |line| Rails.logger.error "     #{line}" } if error.backtrace 
  self.import_errors.create! :row_data => row, :error_message => "Row #{row_num}: #{error.message}"
  failed!
  rollback
end

#hash_address(parsed_row) ⇒ Object



145
146
147
# File 'app/models/import.rb', line 145

def hash_address(parsed_row)
  parsed_row.address_attributes
end

#headersObject



38
39
40
# File 'app/models/import.rb', line 38

def headers
  self.import_headers
end

#importObject



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'app/models/import.rb', line 55

def import
  self.importing!

  self.people.destroy_all
  self.import_errors.delete_all

  rows.each_with_index do |row, index|
    begin
      Rails.logger.info("----- Import #{id} Processing row #{index} ------")
      process(ParsedRow.parse(headers, row))
    rescue => error
      fail!(error, row, index)
      return
    end
  end
  self.imported!
end

#message(row_number = nil, parsed_row = nil, person, message_text) ⇒ Object



21
22
23
# File 'app/models/import.rb', line 21

def message(row_number=nil, parsed_row=nil, person, message_text)
  self.import_messages.create({:row_number => row_number, :row_data => parsed_row.try(:row), :person => person, :message => message_text})
end

#parsed_rowsObject



95
96
97
98
99
100
101
102
103
# File 'app/models/import.rb', line 95

def parsed_rows
  return @parsed_rows if @parsed_rows
  @parsed_rows = []
  
  rows.each do |row|
    @parsed_rows << ParsedRow.parse(headers, row)
  end
  @parsed_rows
end

#performObject



46
47
48
49
50
51
52
53
# File 'app/models/import.rb', line 46

def perform
  if status == "caching"
    self.cache_data
  elsif status == "approved"
    self.import
    Sunspot.delay.commit
  end
end

#process(parsed_row) ⇒ Object

Subclasses must implement process and rollback



85
86
# File 'app/models/import.rb', line 85

def process(parsed_row)
end

#recallable?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'app/models/import.rb', line 91

def recallable?
  false
end

#rollbackObject



88
89
# File 'app/models/import.rb', line 88

def rollback
end

#rowsObject



42
43
44
# File 'app/models/import.rb', line 42

def rows
  self.import_rows.map(&:content)
end