Rails Parse CSV with empty cells and correctly handle exceptions - ruby-on-rails

I am trying to allow users to upload csv files, however I am having some issues trying to figure out how to handle errors from the file itself.
My controller method:
def create
Product.import_csv(file)
redirect_to products_path, :flash => { :notice => "Items Added!" }
end
My model method to import the file:
def self.import_csv(file)
csv = CSV.read(file.path), :headers => true)
csv.each do |row|
item_id = row[0]
start_date = Date.parse(row[1])
order_date = Date.parse(row[2])
new_rec = where(item_id:item_id, order_date:order_date).first_or_initialize
new_rec.save!
end
end
All this works well when the file is properly formatted, however Im confused as to how to handle exceptions. Once such exception is when start_date or order_date are missing; I get an no implicit conversion of nil into String because I'm attempting to parse the date of an empty cell. Even though I have validations in my model for presence, they only get fired on the save action.
I don't want to silently ignore these errors with a rescue block, but instead redirect back, and notify the user.
How can I handle such exceptions, so that the jobs fails, and the user gets notified of the error, not specifically solely the error given above, but including errors we can't account for? Another example would be a empty line or something we cant even account for. How can I handle such errors, and notify the user with, for example, a generic "Bad data" message?
Should I handle this in my model or my controller?

If you have presence validations for the dates, you can simply assign them only if they exist, and let your model do the rest:
def self.import_csv(file)
csv = CSV.read(file.path), :headers => true)
csv.each do |row|
item_id = row[0]
start_date = row[1].nil? ? nil : Date.parse(row[1])
order_date = row[2].nil? ? nil : Date.parse(row[2])
new_rec = where(item_id:item_id, order_date:order_date).first_or_initialize
new_rec.save!
end
end

Related

Update record if exists with roo gem

I have an import working correctly from a Spreadsheet using Roo gem.
The problem is every time I call the rake task, new records are created.
I want to update_attributes of the records in case the record exists.
Is there any way to approach this? I've tried this with no luck:
namespace :import do
desc "Import data from spreadsheet" # update this line
task data: :environment do
data = Roo::Spreadsheet.open('lib/t3.xlsx') # open spreadsheet
headers = data.row(1) # get header row
data.each_with_index do |row, idx|
next if idx == 0 # skip header
# create hash from headers and cells
product_data = Hash[[headers, row].transpose]
product = Product.new(product_data)
puts "Guardando Producto #{product.name}"
if product?
product.update_attributes
else
product.save!
end
rescue ActiveRecord::RecordInvalid => invalid
puts invalid.record.errors
end
end
end
if product? will never return false. You're testing whether the variable contains a falsy value (nil/false) or any other value. After calling product = Product.new, the value stored in product can never be nil or false.
What you want is to first find, and if not found, new, and then update_attributes on the resulting object:
product = Product.find_by(product_data.name) || Product.new
product.update_attributes(product_data)

Ruby on Rails beginner question : equality

I'm starting to know ROR and I was doing a kind of blog with articles, etc...
I did this code :
def show
id = params[:id]
list = Article.all
is_valid = false
list.all.each do |article|
if article.id == id
#is_valid = true
break
end
end
As you can see, this code just wants to check if the article ID exists or not. So I'm testing equality between id and article.id (which's a model linked to the appropriated table in the database) BUT when I try to use or display #is_valid boolean I saw that article.id == id is FALSE every time, even if article.id = 2 and id = 2. I tried to think about everything that can make this occuring, but I admit I still misunderstand this.
Then I ask you if you know why this is occuring. Of course, an equality like 2 == 2 will change #is_valid to true.
Thank you for your help !
Maybe its because params[:id] it's a string and article.id it's an Integer
(byebug) params
{"controller"=>"admin/my_controller", "action"=>"edit", "id"=>"1"}
And yes it is... "id" is a string "1", so you may try this:
def show
id = params[:id].to_i
list = Article.all
is_valid = false
list.all.each do |article|
if article.id == id
#is_valid = true
break
end
end
end
And maybe could work.
This is the answer to your question,
But if you want to learn a little more about Activerecord you can do this
Article.exists?(params[:id])
and that will do what you are trying to do just with a query against db.
and if you want to get just a simple article
record = Article.find_by(id: params[:id]) #return nil when not exist
if record # if nil will threat like false on ruby
#my code when exist
else
#my code when not exist
end
will work (you also can use find but find will throw an exception ActiveRecord::RecordNotFound when not exists so you have to catch that exception.
Activerecord has many ways to check this you dont need to do it by hand.
def show
#article = Article.find(params[:id])
end
This will create a database query which returns a single row. .find raises a ActiveRecord::NotFound exception if the record is not found. Rails catches this error and shows a 404 page. Article.find_by(id: params[:id]) is the "safe" alternative that does not raise.
Your code is problematic since list = Article.all will load all the records out of the database which is slow and will exhaust the memory on the server if you have enough articles. Its the least effective way possible to solve the task.
If you want to just test for existence use .exists? or .any?. This creates a COUNT query instead of selecting the rows.
Article.where(title: 'Hello World').exists?

Import and Create Records from CSV

I'm trying create records in the Pairing table from a CSV file upload. The file given will be in this format
supervisor,student,project_title
Bob,Alice,Web Site
Bob,Charlie,Web Application
Issue is the Pairing table doesn't hold supervisor or student names but rather their IDs, so it would be necessary to search the User table for these given names and select their IDs then create the Pairing with these ids and the given project title.
The code below is giving me a too many redirects error and inserting a null record into the pairings table.
Pairing.rb
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
supervisorName = row[0]
studentName = row[1]
title = row [2]
supervisorID = User.select(:id).where(name: supervisorName)
studentID = User.select(:id).where(name: studentName)
pair = Pairing.new
pair.supervisor_id = supervisorID
pair.student_id = studentID
pair.project_title = title
pair.save
end
end
Pairings_controller.rb
def new
#pairing = Pairing.new
end
def create
#pairing = Pairing.new(pairing_params)
if #pairing.save
redirect_to pairings_path, :notice => "Pairing Successful!"
else
redirect_to pairings_path, :notice => "Pairing Failed!"
end
end
def import
Pairing.import(params[:file])
redirect_to pairings_path, :notice => "Pairs Imported"
end
The statement User.select(:id).where(name: supervisorName) won't return an integer value as you're expecting. Consider using User.find_by(name: supervisorName).id Instead.
As for too many redirects, make sure that the action matching your pairings_path doesn't redirect back to itself or other actions that may yield circular redirects.

Take random ids, then store those random ids into the db

so I'm working on a code snippet that essentially takes out 35 random ids from the table List.
What I would like to do to find the ids that got randomly generated, store them into a database called Status.
The purpose is to avoid duplication the next time I get a new 35 random ids from the List. So I never get the same random id twice.
Here's what I've tried, but been unsuccessful to get working.
#schedule = current_user.schedules.new
if #schedule.save
#user = User.find(params[:id])
Resque.enqueue(ScheduleTweets, #user.token)
#schedule.update_attribute(:trial, true)
flash[:notice] = "success"
redirect_to :back
else
flash[:alert] = "Try again."
redirect_to :back
end
and the worker:
def self.perform(user_token)
list = List.first(6)
#status = list.statuses.create
list.each do |list|
Status.create(list_id: "#{list}")
if list.avatar.present?
client.create_update(body: {text: "#{list.text}", profile_ids: profile_ids, media: { 'thumbnail' => 'http://png-1.findicons.com/files/icons/85/kids/128/thumbnail.png', 'photo' => 'http://png-1.findicons.com/files/icons/85/kids/128/thumbnail.png' } })
end
end
end
however the Status.create(list_id: #list) doesn't work.
Does anybody have any idea what is going on, and how I can make the list_ids get saved successfully to Status?
It's also associated:
list has many statuses, and status belongs to list
The following line of code is wrong:
Status.create(list_id: "#{list}") # WRONG
In your case the list variable is a List instance. And you're passing its string version to list_id which expects an integer.
Do this:
Status.create(list_id: list.id)
Or this:
list.statuses.create
I think the following will also work:
Status.create(list: list)

assigning values to model

I'm kinda new to coding on rails. It would be great if you could help me out with what I think might be noob question.Here's my code:
def create
#project = Project.new(params[:project])
if #project.save
redirect_to new_project_path
end
student=#project.student_str.split(";")
#users = User.where(:code => student)
#users.each do |c|
puts c.email
end
#users.each do |c|
puts "I'm here"
c.projects = "#{c.projects};#{#project.id}"
end
end
So, in the create method, Each time a new project is created a string called student_str is stored where the ID number of each student is seperated by a ";". I split that string to an array using the split function to get an array of student ID's. I have the puts c.email and puts "I'm here" to make sure the loops are working fine. I get the proper outputs on terminal.
The problem here is the
c.projects = "#{c.projects};#{#project.id}"
That simply does not seem to be working.
My model is not updated when this line is executed. I get no errors though.
Can you tell me what I might have to do to fix this?
thanks!
You have to call c.save after you updated the projects attribute. Otherwise the object is updated but not the database so the next time you load it the changes are gone.

Resources