Hi I have a very basic csv importer that a user uses to import items. Items belong_to a :part_number
When the user imports the items I want to add a first or create to the import to find or create a part number by its name. CSV File columns i want to have
name, part_number.name
Schema
create_table "items", force: :cascade do |t|
t.bigint "project_id"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "status", default: 0
t.bigint "part_number_id"
t.index ["part_number_id"], name: "index_items_on_part_number_id"
t.index ["project_id"], name: "index_items_on_project_id"
end
create_table "part_numbers", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
app/models/item.rb
class Item < ApplicationRecord
belongs_to :project
belongs_to :part_number
def self.import(file)
CSV.foreach(file.path, headers: true, header_converters: :symbol) do |row|
Item.create! row.to_hash
end
end
end
app/models/part_number.rb
class PartNumber < ApplicationRecord
has_many :items
end
app/controllers/projects/items_controller.rb
class Projects::ItemsController < ApplicationController
# GET /items/new
def new
@project = Project.find(params[:project_id])
@item = Item.new
end
def index
@project = Project.find(params[:project_id])
@items = @project.items.all
respond_to do |format|
format.html
format.csv { send_data @items.to_csv }
end
end
# GET /items/1/edit
def edit
end
# POST /items
# POST /items.json
def create
@project = Project.find(params[:project_id])
@item = Item.new(item_params)
@item.project_id = @project.id
respond_to do |format|
if @item.save
format.html { redirect_to @item.project, notice: 'Item was successfully created.' }
format.json { render :show, status: :created, location: @item.project }
else
format.html { render :new }
format.json { render json: @item.project.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /items/1
# PATCH/PUT /items/1.json
def update
@item = Item.find(params[:id])
@project = Project.find(params[:project_id])
respond_to do |format|
if @item.update(item_params)
format.html { redirect_to @item.project, notice: 'Item was successfully updated.' }
format.json { render :show, status: :ok, location: @item.project }
else
format.html { render :edit }
format.json { render json: @item.project.errors, status: :unprocessable_entity }
end
end
end
# DELETE /items/1
# DELETE /items/1.json
def destroy
@item = Item.find(params[:id])
@project = Project.find(params[:project_id])
title = @item.model
if @item.destroy
flash[:notice] = "One \'#{title}' was successfully destroyed."
redirect_to @project
else
flash[:notice] = "Error Yo"
render :show
end
end
def import
@project = Project.find(params[:project_id])
@project.items.import(params[:file])
redirect_to projects_path(@project), notice: "Sucessfully Imported Items!"
end
private
# Use callbacks to share common setup or constraints between actions.
# Never trust parameters from the scary internet, only allow the white list through.
def item_params
params.require(:item).permit(:model, :project_id, :name, :search, part_number: [:id, :name])
end
end
If your name param is coming through as the first column in each row (row[0]), then I think something like this should work:
class Item < ApplicationRecord
belongs_to :project
belongs_to :part_number
def self.import(file)
CSV.foreach(file.path, headers: true, header_converters: :symbol) do |row|
Item.where(name: row[0]).find_or_create_by do |item|
item.update_attributes(row.to_hash)
end
end
end
end
A decent tutorial for using find_or_create_by when importing CSV's:
https://www.driftingruby.com/episodes/importing-and-exporting-csv-data