Rails 4 CSV upload into multiple models - ruby-on-rails

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

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.

Rails importing via CSV and setting fields at the same time

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.

Displaying from my database

I'm new to rails and I'm working on a project where I'm having an issue. I'm trying to display all the gyms that have the same zipcode. When I tried the code below, it only displays 1 and not the other ones. How can display all the gym that have the same zip code?
controller
def gym
#fitness = Fitness.find_by(zip_code: params[:zip_code])
end
gym.html.erb
<%= #fitness.name %>
You're doing this to yourself. By definition, #find_by only returns a single record, or nil. You probably want #where instead:
Fitness.where(zip_code: params[:zip_code])
If that still doesn't work, check both your table data and the content of your params hash to make sure you're creating a valid query.
def gyms
#fitness = Fitness.where("zip_code = ?", params[:zip_code])
end

Saving my rails object from an array of values sourced form a csv

i'm currently trying to update attributes via a csv upload.
My method looks like this:
def upload_csv
CSV.parse(params[:file].read, headers: true) do |row|
foo = Foo.find_by_id(row.to_hash["id"])
row.to_hash.each do |v|
if Foo.new.has_attribute?(v[0]) && v[0] != "id"
foo.update_attributes()
end
end
end
end
When it jumps into where I want to update my attributes, i'm getting an array that looks like this:
["bar", "22"]
How can I save that value to my foo object?
Ok, so reading you're code I'm concluding that your problem is really that you have a CSV that may contain some fields that are not in your model:
def upload_csv
excluded = %w/id created_at updated_at/
CSV.new( params[:file], headers: true) do |row|
rh = row.to_hash
foo = Foo.find_by id: rh['id']
foo.update! rh.slice(*foo.attribute_names).except(*excluded)
end
end
Note that I'm assuming params[:file] is an uploaded file from a form, in which case it's an IO object, and so can be passed into CSV.new directly (no need to read it all into memory and pass it to CSV.parse).

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