Skip to content

Instantly share code, notes, and snippets.

@nthj
Created June 6, 2011 03:58
Show Gist options
  • Save nthj/1009723 to your computer and use it in GitHub Desktop.
Save nthj/1009723 to your computer and use it in GitHub Desktop.
Constituent Importer for Capvoice
# constituent_importer.rb
#
# When the customer uploads an Excel file,
# we generate a unique ID for the file and
# store it on our AWS/S3 jobs bucket.
#
# Then we create a job with that unique ID,
# and our background workers execute the job as follows
class ConstituentImporter < Struct.new(:job_object_id)
extend ActiveSupport::Memoizable
include Capbot::Importer::Filterable # add before, after filters
include Capbot::Importer::Encodable # wraps the job, making sure our server doesn't get stuck in binary mode
include Capbot::Sourceable
include Capbot::Manageable # automatically scale our Heroku workers, saving us money
force_encoding 'ASCII-8BIT' # spreadsheets are binary files, not text
class InvalidJobObject < Exception; end
class NoColumnsFound < Exception; end
add_after_filter :delete_job_object
class << self
# use this in ImportController to make sure the customer uploaded a file
def validate_job_object form
raise InvalidJobObject if form.nil? || form.empty? || form[:excel].nil? || !File.size?(form[:excel].tempfile.path)
end
end
# translation magic
# guesses which Excel columns match up to our Capvoice database structure
def columnize row
return if @columns
@columns = { }
row.each_with_index do |label, column|
@columns[column] = case label
when /address/i then :address
when /city.*state/i then :city_and_state
when /city/i then :city
when /county/i then :county
when /email/i then :email
when /first/i then :first_name
when /last/i then :last_name
when /name/i then :name
when /title|office/i then :office
when /phone|number/i then :phone
when /state/i then :state
when /postal|code|zip/i then :zip_code
end
end
raise NoColumnsFound if @columns.empty?
@columns
end
def import row
attributes = { }
row.each_with_index do |value, column|
attributes.merge!(@columns[column] => value) if @columns[column]
end
# we should refactor this a bit
attributes[:city] = attributes[:city_and_state].split(',').first if attributes[:city_and_state] unless attributes[:city]
attributes[:name] = [attributes[:first_name], attributes[:last_name]].compact.join(' ') unless attributes[:name]
attributes[:state] = attributes[:city_and_state].split(',').last if attributes[:city_and_state] unless attributes[:state]
attributes[:state] = State.find_by_name(attributes[:state]).abbreviation if attributes[:state] && State.find_by_name(attributes[:state])
account.constituents.merge_or_create attributes
end
def perform
spreadsheet.worksheets.each do |sheet|
reset_columns
sheet.each do |row|
columnize row or import row
end
end
end
protected
def account
User.find(JobObject.unique_number_from_object_id(job_object_id)).account
end
memoize :account
def delete_job_object
JobObject.delete job_object_id
end
def reset_columns
@columns = nil
end
def source
JobObject.source job_object_id
end
def spreadsheet
Spreadsheet.client_encoding = @@encoding
Spreadsheet.open stream
end
memoize :spreadsheet
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment