Unknown attribute error on CSV upload - ruby-on-rails

I am trying to upload a CSV file into an existing database and receiving the following
error: ActiveRecord::RecordInvalid in UploadsController#import! Validation failed: Email has already been taken.
Controller:
class UploadsController < ApplicationController
def index
#uploads = Upload.all
end
def import
Upload.import(params[:file])
redirect_to uploads_path, notice: "Employee data imported!"
end
end
Model:
class Upload < ActiveRecord::Base
require 'csv'
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
Employee.create! row.to_hash
end
end
end
Table:
create_table "employees", force: :cascade do |t|
t.string "last_name"
t.string "first_name"
t.string "employee_code"
t.string "email"
t.string "level"
t.string "dept"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I checked and and my database headers for employees do match my first row of the CSV file (beginning row 1, col 1). Any thoughts?

The keys of the hash you supply to Employee.create! (the CSV headers in this case) need to match exactly with the names of attributes on your model. Your problem is probably caused by one or more of the following:
There is a column where the header is not all lowercase. E.g., the CSV column name may be "Email" or "EMAIL" but should be "email".
There is a column where the header has spaces in it. E.g., the CSV column name may be "first name" or "last name" but should be "first_name" or "last_name", respectively.
There is a column in the CSV in which the column header is misspelled or otherwise doesn't belong to your table schema in the db at all. (Though you have said all of your CSV column headers match the database table columns, maybe check again if the problem is not solved by the first two bullets.)
Any one of these will cause the ActiveModel::UnknownAttributeError you are seeing. If the problem is being caused by one or multiple of the above then you can obviously solve the problem by editing your CSV file to make sure every header is lowercase with underscores instead of spaces. Alternatively, you could pass in the :header_converters => :symbol option to CSV.foreach like this:
CSV.foreach(file.path, headers: true, header_converters: :symbol) do |row|
Employee.create! row.to_hash
end
This option will transform your headers by converting them to lowercase, replacing spaces with underscores, and then converting them to symbols. This may be a more reliable option if you have a lot of column headers to tidy up, or if you will frequently receive the files with "slightly bad" headers suffering from one of the problems described above.
EDIT
I see you updated your question with more detail about the error. Rails is telling you that there is a column with header "last_name" in your CSV but there is no such column in your database (i.e. no such attribute on your Employee model). As Muhamad suggested, you could create a migration to add the last_name column to Employee. Can you verify that your employees table in the database has this column?

Related

Why integer is automatically converted to string when updating/saving in DB?

I have a column that is supposed to be a string. In schema.rb it looks something like this:
create_table "users", force: :cascade do |t|
t.string "login_token", default: "xxxxx", null: false
end
But if I try to update the column, the DB accepts integers and automatically converts them to strings for some reason.
user = User.first.update(login_token: 1)
#=> true
user.login_token
#=> "1"
Why is this, and is it possible to add any restrictions to the DB or validations in Rails to prevent this kind of typecasting?
Works the other way around too. If you have an integer column and pass a string, rails tries to convert it. Very useful when you, say, create a record from an html form (most everything comes as a string from the browser).
user_params # => { "age" => "20" }
u = User.create(user_params)
u.age # => 20
It's a feature/convention. I wouldn't fight it, if I were you.

Importing info into existing table Rails

I'm trying to import CSV data into an already existing Rails table.
My CSV has columns of lec_exam, location, meeting_days, begin_time, and end_time. My table looks as follows:
create_table "courses", force: true do |t|
t.string "lec_exam"
t.string "location"
t.string "meeting_days"
t.time "begin_time"
t.time "end_time"
t.datetime "created_at"
t.datetime "updated_at"
t.string "status"
end
The status (i.e. taken vs open) field is something I want to update based on the current time versus the presence of a course occurring at that time.
Every time I import the CSV data the last column (end_time) does not get properly imported because each course has an end_time of nil, when a simple glance at the CSV shows otherwise.
I have tried
CSV.foreach(file.path, headers: true) do |row|
#course_hash = row.to_hash # exclude the price field
#course = Course.where(id: course_hash["id"])
row = Course.create!({
:lec_exam => row[0],
:location => row[1],
:meeting_days => row[2],
:begin_time => row[3],
:end_time => row[4]
})
as well as the to_hash method. Any help towards a solution would be great. Thanks!
To import a CSV data into an existing Rails table add:
require 'csv'
csv_text = File.read('...')
csv = CSV.parse(csv_text, :headers => true)
csv.each do |row|
Moulding.create!(row.to_hash)
end
In the rake task, or in a controller action.
Source: Ruby on Rails - Import Data from a CSV file
Hope this helps!
Solved.
Model had an unneccessary
attr_accessor:
tag. Bleh.

Am I using find_or_create_by method properly?

I am trying to populate a new table from an existing database but my method does not seem to be working properly. Below is my code.
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.string :first_name, null: false
t.string :last_name, null: false
t.string :email, null: false
t.timestamps
end
Sale.find_each do |sale|
unless Employee.exists?(sale.employee)
puts "Employee #{sale.employee} created!"
else
puts "Employee #{sale.employee} already existed!"
end
employee_info = sale.employee.split
Employee.find_or_create_by(first_name: employee_info[0], last_name: employee_info[1], email:employee_info[2])
end
end
end
What I have is a main database called sales that with a field that contains employee. In that field you will find a string entry as so: "Mary Higgins higgins#korning.com".
Basically the sales database contains four distinct employees but the employees are listed many times. What I'm trying to do is to create four unique rows. I thought the code above would work but something seems to be off with my logic. When I run the above code it, goes through the n amount of rows and creates the Employee object so, essentially the unless statement never results to true for some reason. Could the problem lie in the .find_each method. Would a .each suffice? I don't know if any more information would need to be provided with my database but if its needed I'll supply more details.
sale.employee is a string eg "Mary Higgins higgins#korning.com"
exists? excepts a hash with the conditions like Employee.exists?(:email => "higgins#korning.com"). If you pass a string like you did, first, it converts the string to an integer then tries to find the record with that id which in your case will be 0 and because of that it always returns false.
I would change the find_each loop like this:
Sale.find_each do |sale|
employee_info = sale.employee.split
employee = Employee.find_or_create_by(first_name: employee_info[0], last_name: employee_info[1], email:employee_info[2])
if employee.new_record?
puts "Employee #{sale.employee} created!"
else
puts "Employee #{sale.employee} already existed!"
end
end

Importing CSV file into Rails sqlite3 database

I have created Rails database using the following schema:
ActiveRecord::Schema.define(:version => 20090807141407) do
create_table "trunks", :force => true do |t|
t.integer "npa"
t.integer "nxxFrom"
t.integer "nxxTo"
t.string "trnk"
t.datetime "created_at"
t.datetime "updated_at"
end
end
In my CSV file, I only have the first four columns (npa, nxxFrom, nxxTo, and trnk).
How can I import the CSV data while also updating the last two columns?
Thanks always
To use the csv module which is part of the standard Ruby library:
require 'csv'
# row will be an array with the fields in the order they appear in the file
CSV.open('myfile.csv', 'r') do |row|
# assuming the fields in the CSV file are in order npa, nxxFrom, nxxTo, trnk
# create and save a Trunk model for each row
Trunk.create!(:npa => row[0], :nxxFrom => row[1], :nxxTo => row[2], :trnk => row[3])
end
I haven't used fastercsv but looking at its documentation the approach seems the same.
Use the FasterCSV gem.
A nice example is at http://espndev.com/blog/csv-imports-using-fastercsv/
The last two columns will be updated by ActiveRecord. If they exist in the schema then ActiveRecord will add a value for the created_at and updated_at upon creation of the object. Any modifications to the object will cause the updated_at column to be auto updated.
So when importing from a CSV just map the first 4 columns and save the object. When you retrieve the object you will see the other two values have also been set automatically.

globalize2 problem

I have strange globalize2 problem. I'm trying to use globalize 2 and acts_as_textiled and acts_as_commentable. For example - lets we have Post model, that acts_as_commentable. From console
p = Post.find 1
c = p.comments.find 1
works fine, but in browser - nothing displayed
Similar, when Post contains
acts_as_textiled :body
from console body is containing correct data, but in browser i see nothing :(
Any ideas how to correct it?
Upd: "nothing displayed" means,
that for code like
class Post < ActiveRecord::Base
translates :title, :body
acts_as_textiled :body
end
on access to Post.body i've got nil, but on disabled globalize2 or
acts_as_textiled body returns his value. I've tried with different
locales - the same result.
Have you performed the necessary migrations? For localised content you should remove the localised fields in the main table (posts) and create a table for the localisations, like this:
create_table "post_translations", :force => true do |t|
t.string "locale"
t.integer "product_id"
t.string "title"
t.text "body"
end
Just guessing here :)

Resources