robertguss
robertguss

Reputation: 175

Rails upload csv or text file with associations

I am building a rails application that associates serial #'s with software titles. For instance, I have a software title, and need to be able to upload batches of serial #'s(codes) and associate it with that specific software title. It needs to be simple enough for a user(authenticated) to click an upload link, select a software title from a dropdown and hit import. Here is what I have so far... It does not necessarily have to be a csv it could be a text file too. I just need help figuring out the best way to accomplish this.

Code Upload UI

Code Schema

create_table "codes", force: :cascade do |t|
    t.integer  "software_id"
    t.integer  "user_id"
    t.string   "label"
    t.string   "code"
    t.string   "in_use"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  add_index "codes", ["software_id"], name: "index_codes_on_software_id"
  add_index "codes", ["user_id"], name: "index_codes_on_user_id"

Code 'form' for UI

<%= simple_form_for(@code) do |f| %>
  <%= f.error_notification %>

  <div class="form-inputs">
    <%= f.association :software %>
    <%= f.input :label %>
    <%= f.input :code %>
  </div>

  <div class="form-actions">
    <%= f.file_field :code %>
    <br>
    <%= f.button :submit, "Upload Codes", class: 'btn btn-warning' %>
  </div>
  <br>
<% end %>

Code.rb Model

class Code < ActiveRecord::Base
  belongs_to :software
  belongs_to :user
  accepts_nested_attributes_for :software

  def self.import(file)
    CSV.foreach(file.path, headers: true) do |row|
      Code.create! row.to_hash
    end
  end
end

Software.rb Model

class Software < ActiveRecord::Base
  has_many :software_assigns
  has_many :products, through: :software_assigns
  has_many :software_downloads
  has_many :codes
end

Codes Controller

class CodesController < ApplicationController
  before_action :authenticate_user!
  before_action :verify_admin
  before_action :set_code, only: [:show, :edit, :update, :destroy]

  # GET /codes
  # GET /codes.json
  def index
    @codes = Code.all
  end

  # GET /codes/1
  # GET /codes/1.json
  def show
  end

  # GET /codes/new
  def new
    @code = Code.new
  end

  # GET /codes/1/edit
  def edit
  end

  # POST /codes
  # POST /codes.json
  def create
    @code = Code.new(code_params)

    respond_to do |format|
      if @code.save
        format.html { redirect_to @code, notice: 'Codes were successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  # PATCH/PUT /codes/1
  # PATCH/PUT /codes/1.json
  def update
    respond_to do |format|
      if @code.update(code_params)
        format.html { redirect_to @code, notice: 'Codes were successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

  # DELETE /codes/1
  # DELETE /codes/1.json
  def destroy
    @code.destroy
    respond_to do |format|
      format.html { redirect_to codes_url, notice: 'Codes were successfully destroyed.' }
    end
  end

  def import
    Code.import(params[:file])
    redirect_to codes_path, notice: 'Codes were successfully uploaded!'
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_code
      @code = Code.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def code_params
      params.require(:code).permit(:software_id, :label, :code)
    end
end

Upvotes: 0

Views: 1275

Answers (1)

Michael Cruz
Michael Cruz

Reputation: 882

Ok - doing a lot of communication in the comments and I think I can put together an answer now.

So, as per my edit to pitabas prathal's answer, you need to look for by the :code key in the code_params hash, but since the Code class has no idea that you've got a code_params[:software_id] to refer to, you'll need to pass that along. So your import method becomes:

code.rb

def self.import(file, software_id)
  CSV.foreach(file.path, headers: true) do |row|
    code = Code.new row.to_hash
    code.software_id = software_id
    code.save!
  end
end

Then, your call to this method with the new argument, from the create action (or your import method on the controller):

Code.import(code_params[:code], code_params[:software_id])

Now, you are giving your Code class all the information it needs to associate the new Code objects with the appropriate Software object.

Edit

In your GoRails post, Chris Oliver's answer would also work, with one edit:

@software = Software.find(params[:software_id])

will not work - params[:software_id] is nil. You can access software_id from the code_params hash:

@software = Software.find(code_params[:software_id])

or by adding the [:code] key in to the params reference like so:

@software = Software.find(params[:code][:software_id])

since [:software_id] is inside the [:code] array in the params hash.

Upvotes: 1

Related Questions