I am using gem roo
to import CSV data. It works smoothly, until the point where there is an association, and am hoping that roo can translate the string into the corresponding integer value in the association. In my case, I have a Staff
model which belongs to State
.
class State < ApplicationRecord
has_many :staffs
end
class Staff < ApplicationRecord
belongs_to :state
end
This means that I have state_id
column in the staffs
table. In my CSV, however, the end user has the names of the states, which correspond to the ones in the states
tables. When I try to import the CSV, I get the error:
ActiveRecord::AssociationTypeMismatch in StaffsImportsController#create
State(#134576500) expected, got "Texas" which is an instance of String(#20512180)
The highlighted source is:
staff.attributes = row.to_hash
Is it possible for gem roo
to translate 'Texas' in the csv file to, say, id 2, instead of the end user doing a lot of translation work before uploading the data?
Here is staffs_imports.rb
class StaffsImport
include ActiveModel::Model
require 'roo'
attr_accessor :file
def initialize(attributes={})
attributes.each { |name, value| send("#{name}=", value) }
end
def persisted?
false
end
def open_spreadsheet
case File.extname(file.original_filename)
when ".csv" then Csv.new(file.path, nil, :ignore)
when ".xls" then Roo::Excel.new(file.path, nil, :ignore)
when ".xlsx" then Roo::Excelx.new(file.path)
else raise "Unknown file type: #{file.original_filename}"
end
end
def load_imported_staffs
spreadsheet = open_spreadsheet
header = spreadsheet.row(1)
(2..spreadsheet.last_row).map do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
staff = Staff.find_by_national_id(row["national_id"]) || Staff.new
staff.attributes = row.to_hash
staff
end
end
def imported_staffs
@imported_staffs ||= load_imported_staffs
end
def save
if imported_staffs.map(&:valid?).all?
imported_staffs.each(&:save!)
true
else
imported_staffs.each_with_index do |staff, index|
staff.errors.full_messages.each do |msg|
errors.add :base, "Row #{index + 6}: #{msg}"
end
end
false
end
end
end
And finally the staff_imports_controller.rb
:
class StaffsImportsController < ApplicationController
def new
@staffs_import = StaffsImport.new
end
def create
@staffs_import = StaffsImport.new(params[:staffs_import])
if @staffs_import.save
flash[:success] = "You have successfully uploaded your staff!"
redirect_to staffs_path
else
render :new
end
end
end
Any help/clues will be highly appreciated.
I managed to get a solution to this, thanks to a wonderfully detailed question and great answer provided here Importing CSV data into Rails app, using something other then the association "id"