Rails ИМПОРТ CSV first_or_create

Привет, у меня есть очень простой импортер csv, который пользователь использует для импорта элементов. Предметы принадлежат :part_number

Когда пользователь импортирует элементы, я хочу сначала добавить или создать импорт, чтобы найти или создать номер детали по его имени. Столбцы файла CSV, которые я хочу иметь

name, part_number.name

Схема

 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

приложение/модели/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

приложение/модели/part_number.rb

class PartNumber < ApplicationRecord
    has_many :items
end

приложение/контроллеры/проекты/items_controller.rb

класс 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

person Jem Built    schedule 17.10.2019    source источник
comment
Какое поле вы хотите использовать в качестве идентификатора, чтобы решить, следует ли вам создать запись или просто найти ее? Или каждый столбец в CSV должен соответствовать каждому атрибуту записи?   -  person Mark    schedule 17.10.2019
comment
@ Отметьте имя part_number так, part_number.name. огромное спасибо. так что каждая строка может иметь другое part_number.name   -  person Jem Built    schedule 17.10.2019
comment
@Mark забыл, что теперь добавлен контроллер ... не уверен, что мои параметры верны по отношению к part_number: [:id, :name]   -  person Jem Built    schedule 17.10.2019


Ответы (1)


Если ваш параметр имени проходит как первый столбец в каждой строке (строка [0]), то я думаю, что что-то вроде этого должно работать:

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

Достойный учебник по использованию find_or_create_by при импорте CSV:

https://www.driftingruby.com/episodes/importing-and-exporting-csv-data

person Mark    schedule 17.10.2019
comment
спасибо @mark, могу я спросить, где создается PartNumber? - person Jem Built; 17.10.2019
comment
я изменил на следующее и заставил его работать def self.import(file) CSV.foreach(file.path, headers: true) do |row| item_hash = row.to_hash part_number = PartNumber.find_or_create_by!(name: item_hash['part']) item = Item.create!(name: item_hash['name'], part_number_id: part_number.id) end end - person Jem Built; 17.10.2019
comment
Соз был на встречах - рад, что ты туда попал :) - person Mark; 17.10.2019