Rails importing via CSV and setting fields at the same time - ruby-on-rails

I have the ability to import people details via CSV as entries.
If the spreadsheet contains no id on that row, it creates the entry, otherwise it updates the entry according to the id number.
Ultimately, what I want to do is to have the 'CreatedAt' field update when importing the entry if there is no id, otherwise, leave it alone (each entry only needs 'CreatedAt' to be set once).
If there already is a 'CreatedAt' set and we are just updating, it needs to do nothing.
This is the code I have so far:
controller file:
def import
Votsphonebook.import(params[:file])
redirect_to root_url, notice: "Entries updated!"
end
model file:
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
votsphonebook_hash = row.to_hash
votsphonebook = Votsphonebook.where(id: votsphonebook_hash["id"])
if votsphonebook.count == 1
votsphonebook.first.update_attributes(votsphonebook_hash)
else
Votsphonebook.create!(votsphonebook_hash)
end
end
end
I just need an entry point in my code where I can take the current row it is looking at and run the if statement.
Thank you

The updated_at and created_at columns are automatically populated by Active Record. There are no additional steps required to achieve what you want.
If you want to capture the current time on a custom column you could do this:
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
votsphonebook_hash = row.to_hash
votsphonebook = Votsphonebook.find(votsphonebook_hash["id"])
if votsphonebook
votsphonebook.update_attributes(votsphonebook_hash.merge(your_custom_column: Time.now))
else
Votsphonebook.create!(votsphonebook_hash)
end
end
end
Please note I refactored your finder a little bit for clarity.

Related

Script to Update field in rails

I have a csv file which containing parent and their children data.Parent has many children association. What I want is if the family_situation/gross_income or both are different for different children I want the last family_situation/gross_income for that parent to be saved into the database. Like in this case below I want family_situation to be "family of three" and gross income to be "16775". What I current have in my parent model is as below. How can I achieve this please help.
parent.rb
def self.import(file)
CSV.foreach(file.path, headers:true) do |row|
parent = Parent.find_or_create_by(
parent_1_firstname: row["parent_1_firstname"],
parent_1_lastname: row["parent_1_lastname"],
family_situation: row["family_situation"],
gross_income: row["gross_income"],
)
parent.children.find_or_create_by(
firstname: row["firstname"],
gender: row["gender"]
)
end
end
parent.csv
parent_1_firstname,parent_1_lastname,family_situation,gross_income,childfirstname,childgender
Josh,Wilson,"family of two",13000,Jessica,Female
Josh,Wilson,"family of three",16775,Jamie,Male
As you want to save only last value for family_situation and gross_income you can just find existing parent and update it:
parent = Parent.find_or_initialize_by(
parent_1_firstname: row["parent_1_firstname"],
parent_1_lastname: row["parent_1_lastname"]
).tap do |p|
p.family_situation = row["family_situation"]
p.gross_income = row["gross_income"]
p.save
end
Here you're trying to find a parent with particular first&last names. If it exists, it is instantiated, if not - it is builded with given names. After it you're assigning family_situation and gross_income and save the instance.

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.

Rails 4 CSV upload into multiple models

I am trying to upload a csv file that has data that belongs in two different models: Project and PurchaseOrder. I am using a has_many, :through relationship between the models.
I'm having problems with the upload. I have whitelisted the shared attributes in both controllers and have included the accepts_nested_attributes in both models.
Here's my code to read in the csv.
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
project_hash = row.to_hash
project = Project.where(project_name: project_hash["project_number"]).first
if project.present?
project.first.update_attributes(project_hash)
else
Project.create! (project_hash)
end
purchase_order = PurchaseOrder.where(po_number: project_hash["po_number"]).first
if purchase_order.present?
PurchaseOrder.create!
end
I have two questions/problems.
I cannot read in the po_number attribute. I get this error unknown attribute 'po_number' for Project.
I do not know how to push the created purchase order into the project hash so that it updates the nested attribute value.
Thank you in advance to anyone who is able to help!
**** Update
With this method, the data is saved to the correct tables. However, the association between PurchaseOrder and Project is not saved. Any thoughts?
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
project_hash = row.to_hash
po_hash = {}
po = PurchaseOrder.new
project_hash.each { |k, v| po_hash[k] = project_hash.delete(k) if po.respond_to?(k) }
project = Project.where(project_name: project_hash["project_number"]).first
if project.present?
project.first.update_attributes(project_hash)
else
Project.create! (project_hash)
end
po = PurchaseOrder.where(po_number: po_hash["po_number"]).first
if po.present?
po.first.update_attributes(po_hash)
else
PurchaseOrder.create! (po_hash)
end
end
end
These lines are attempting to update / create the project using all the values in the hash...
project.first.update_attributes(project_hash)
...
Project.create! (project_hash)
But clearly some hash elements (such as "po_number") don't have columns in the projects table.
You need to split out the csv hash elements based on which elements belong to which model...
e.g.
po_hash = {}
po = PurchaseOrder.new
project_hash.each { |k, v| po_hash[k] = project_hash.delete(k) if po.respond_to?(k) }
This will remove the purchase order fields from project_hash and add them to po_hash so you can use the correct hash to update the correct table.
To establish the association...
change
Project.create!(project_hash)
into
project = Project.create!(project_hash)
so that regardless of whether the project exists or is newly created, it's stored in the variable "project". Similarly, do
po = PurchaseOrder.create!(po_hash)
Then after you have created or updated the purchase order, you can simply assign the po to the project...
project.purchase_orders << po

Before Action on Import from CSV

I have a simple CSV import where the provided file is already broken (UTF characters) (German).
e.g.: The list has :
G%C3%B6tterbote
where as the right name should be
Götterbote
I'm trying to force the encoding when importing the CSV.
My Import Action
def import
Player.import(params[:file])
redirect_to players_path, notice: "Players Imported successfully"
end
My Import Method
def self.import(file)
SmarterCSV.process(file.path) do |row|
Player.create(row.first)
end
end
I found out that this converts the String successfully, but couldn't implement it successfully:
u = "G%C3%B6tterbote"
=> "G%C3%B6tterbote"
u1 = CGI::unescape(u).force_encoding('UTF-8')
=> "Götterbote"
So basically i need something like a before_action (i guess)..
You don't need a before action.
You need a pre-prossessor, well actually you need to pre-prossess yourself.
Your CSV comes with columns. Column 0, 1, 2, 3 etc (since you don't use headers).
So, for your text columns, let's call them for the sake of the example columns 1, 3, 5.
def self.import(file)
text_cols=[1,3,5] #for example
SmarterCSV.process(file.path) do |row|
text_cols.each do |column|
row[column]=CGI::unescape(row[column]).force_encoding('UTF-8')
end
Player.create(row)
end
end
Or simply, for your particular case:
def self.import(file)
SmarterCSV.process(file.path) do |row|
row.first=CGI::unescape(row.first).force_encoding('UTF-8')
Player.create(row.first)
end
end

created_at attribute doesn't update when importing CSV

I tried to import a CSV file to my database. I need the app to
use the exact created_at attribute from the CSV file, but it won't work.
I see only Time.now.
What am I doing wrong?
The CSV import code in the model:
def self.import(file, current_user)
allowed_attributes = [ "id","created_at","updated_at"]
#current_user = current_user
CSV.foreach(file.path, headers: true) do |row|
energy = find_by_id(row["id"]) || new
h1 = { "user_id" => #current_user.id }
h2 = row.to_hash.slice(*accessible_attributes)
h3 = h1.merge(h2)
energy.attributes = h3
energy.save!
end
end
From the documentation:
attr_accessible(*args) public
Specifies a white list of model attributes that can be set via mass-assignment.
Mass-assignment will only set attributes in this list, to assign to the rest of attributes you can use direct writer methods.
You need to add to attr_accessible all the attributes you are willing to update, including created_at.

Resources