Import and Create Records from CSV - ruby-on-rails

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.

Related

How to enter a list of codes in the text field, through "," and get the same number of separate objects Rails

I am writing one project Ruby on Rails, and i'm stuck in one place.. I have a class Product, this class has_many: class Codes. Now, i enter the codes one at a time into the text box, and I get one object with one code.
I want to enter a list of codes in the text field, through "," and get the same number of separate objects.
I am getting this error -
TypeError in GiftCodesController#create
no implicit conversion of String into Integer
When I check with a debugger, I don't see any Integer
#code = Code.new(code_params)
if #code.code.include? ","
#array = #code.ode.split(",")
#array.each do |n|
#code.code = #array[n]
#code.save
end
flash[:success] = "CodeĀ“s added!"
redirect_to root_path
else
if #code.save
flash[:success] = "Code added!"
redirect_to root_path
else
flash[:danger] = "Error!"
end
end
The each method returns the VALUE of each array element, not the index.
So this is wrong...
#array.each do |n|
#code.code = #array[n]
#code.save
end
This would be correct...
So this is wrong...
#array.each do |n|
#code.code = n
#code.save
end
But even that would be wrong, as you want to create a new code for each array element, so...
#array.each do |n|
code = #product.codes.build
code.code = n
code.save
end
There are some edge cases you'll also need to address, like how to edit existing codes for a product, but hopefully this will start you off.

rails: nested form attributes how to avoid creating new entries in the database?

I have a model Books and a model Authors.
The form for adding books, contains a nested for allowing to add authors. That works. However, I have an autocomplete function on the authors fields, so when the form is posted to the controller, the author (almost) for sure exists in the database.
I should somehow do a find_or_initialize_by on the nested attributed.
I'm maybe looking at the wrong place, but I can't find this in the rails guides. I tried this (found on SO):
def create
#book = Book.new(params_book)
small_name = params[:book][:authors_attributes]["0"]["name"].downcase
aut_id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join
#book.authors = Author.find_or_initialize_by(id: aut_id)
if #book.save
redirect_to see_book_url(Book.last)
else
render 'new'
end
end
This creates an error:
undefined method `each' for #<Author:0x007fac59c7e1a8>
referring to the line #book.authors = Author.find_or_initialize_by(id: aut_id)
EDIT
After the comments on this question, I updated the code to this:
def create
book_params = params_book
small_name = params[:book][:authors_attributes]["0"]["name"].downcase
id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join
book_params["authors_attributes"]["0"]["id"] = id
#book = Book.new(book_params)
if #book.save
redirect_to see_book_url(Biblio.last)
else
....
The book params look like this:
<ActionController::Parameters {"title"=>"Testus Testa",
"authors_attributes"=><ActionController::Parameters {
"0"=><ActionController::Parameters {"name"=>"Vabien", "id"=>"22"}
permitted: true>} permitted: true>} permitted: true>
That looks fine to me, BUT, I get this error:
ActiveRecord::RecordNotFound in Administration::BooksController#create
Couldn't find Author with ID=22 for Book with ID=
Ok so the easiest way to get what you want is to change autocomplete in your form from an array of names like: ['author 1 name', 'author 2 name'] change it to an array of objects containing the name and id of the author like: [{label: 'author 1 name', value: 0}, {label: 'author 2 name', value: 1}] so then as long as that form field is now for "id" instead of "name" then in your controller all you have to do is:
def create
#book = Book.new(params_book)
if #book.save
redirect_to see_book_url(Book.last)
else
render 'new'
end
end
Because only attributes without an ID will be created as new objects. Just make sure you set accepts_nested_attributes_for :authors in your Book model.
The error you are getting is because #book.authors is a many relationship so it expects a collection when you set it not an individual author. To add an individual author to the collection you do #book.authors << Author.find_or_initialize_by(id: aut_id) instead of #book.authors = Author.find_or_initialize_by(id: aut_id) although its redundant to fetch the id using the name just to initialize with an id. The id will be created automatically. Use Author.find_or_initialize_by(name: small_name) instead.
In your current code you have multiple authors being created not only due to the lack of "id" being used but because #book = Book.new(params_book) passes the nested attributes to the object initializer and then after you are accessing the nested attribute params and adding authors again. Also if you have multiple authors with the same name then Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join would actually make an ID out of the combined ID of all authors with that name.
If you want to do it manually then remove :authors_attributes from your permit in "params_book" method so it won't be passed to Book.new then do the following:
def create
#book = Book.new(params_book)
params[:book][:author_attributes].each{|k,v| #book.authors << Author.find_or_initialize_by(name: v['name'])}
if #book.save
redirect_to see_book_url(Book.last)
else
render 'new'
end
end
Let me know if you have trouble!
After response from poster
remove :authors_attributes from your permit in "params_book" method and try this:
def create
#book = Book.new(params_book)
#book.authors_attributes = params[:book][:author_attributes].inject({}){|hash,(k,v)| hash[k] = Author.find_or_initialize_by(name: v['name']).attributes.merge(v) and hash}
if #book.save
redirect_to see_book_url(Book.last)
else
render 'new'
end
end
Solved, thanks a lot to Jose Castellanos and this post:
Adding existing has_many records to new record with accepts_nested_attributes_for
The code now is:
# the strong params isn't a Hash, so this is necessary
# to manipulate data in params :
book_params = params_book
# All registrations in the DB are small case
small_name = params[:book][:authors_attributes]["0"]["name"].downcase
# the form sends the author's name, but I need to test against the id:
id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join
book_params["authors_attributes"]["0"]["name"] = params[:book][:authors_attributes]["0"]["name"].downcase
# this author_ids is the line that I was missing! necessary to
# test whether the author already exists and avoids adding a
# new identical author to the DB.
book_params["author_ids"] = id
book_params["authors_attributes"]["0"]["id"] = id
# the rest is pretty standard:
#book = Book.new(book_params)
if #book.save
redirect_to see_book_url(Book.last)
else

Searching table by id and other fields in Rails

I'm trying to do a search on the following fields in my rails model: id, description, notes, and customer_name.
In my model TechServiceSpecial:
def self.search(params)
q = "%#{params[:search].try(:strip).tr('%', '')}%"
#build up general search
general_search_fields.reduce(nil) do |running_query, field|
if field =~ /id/
main_check = arel_table[field].eq(q.to_i)
else
main_check = arel_table[field].matches(q)
end
if running_query.nil?
main_check
else
running_query.or(main_check)
end
end
end
private
def self.general_search_fields
%i(id description notes customer_name)
end
In my controller (I'm using Kaminari for pagination):
def search
specials = TechServiceSpecial.where(TechServiceSpecial.search(params))
#tech_service_specials = specials.page(params[:page]).per(200)
render 'index'
end
This will not find a record if I search using an id. So, if I have a record with id of 1005, that record will not be returned in the search results for '1005', unless the description, notes or customer_name also happen to have 1005 in them. Any record that happens to have 1005 in the description, notes, or customer_name will be returned.
So, I tweaked the controller to look like this:
def search
specials = TechServiceSpecial.where(TechServiceSpecial.search(params))
specials.merge(TechServiceSpecial.where(id: params[:search].to_i))
#tech_service_specials = specials.page(params[:page]).per(200)
render 'index'
end
But I get the same results. Will not return the record with an id of 1005.
How can I make this return a record that has the id that equals the string I'm searching with?
In your TechServiceSpecial.search method:
q = "%#{params[:search].try(:strip).tr('%', '')}%"
...
if field =~ /id/
main_check = arel_table[field].eq(q.to_i)
...
the value for the id will always be 0, since q starts with a "%".
You should set q to params[:search].to_i if the field is id.

Take random ids, then store those random ids into the db

so I'm working on a code snippet that essentially takes out 35 random ids from the table List.
What I would like to do to find the ids that got randomly generated, store them into a database called Status.
The purpose is to avoid duplication the next time I get a new 35 random ids from the List. So I never get the same random id twice.
Here's what I've tried, but been unsuccessful to get working.
#schedule = current_user.schedules.new
if #schedule.save
#user = User.find(params[:id])
Resque.enqueue(ScheduleTweets, #user.token)
#schedule.update_attribute(:trial, true)
flash[:notice] = "success"
redirect_to :back
else
flash[:alert] = "Try again."
redirect_to :back
end
and the worker:
def self.perform(user_token)
list = List.first(6)
#status = list.statuses.create
list.each do |list|
Status.create(list_id: "#{list}")
if list.avatar.present?
client.create_update(body: {text: "#{list.text}", profile_ids: profile_ids, media: { 'thumbnail' => 'http://png-1.findicons.com/files/icons/85/kids/128/thumbnail.png', 'photo' => 'http://png-1.findicons.com/files/icons/85/kids/128/thumbnail.png' } })
end
end
end
however the Status.create(list_id: #list) doesn't work.
Does anybody have any idea what is going on, and how I can make the list_ids get saved successfully to Status?
It's also associated:
list has many statuses, and status belongs to list
The following line of code is wrong:
Status.create(list_id: "#{list}") # WRONG
In your case the list variable is a List instance. And you're passing its string version to list_id which expects an integer.
Do this:
Status.create(list_id: list.id)
Or this:
list.statuses.create
I think the following will also work:
Status.create(list: list)

Rails Parse CSV with empty cells and correctly handle exceptions

I am trying to allow users to upload csv files, however I am having some issues trying to figure out how to handle errors from the file itself.
My controller method:
def create
Product.import_csv(file)
redirect_to products_path, :flash => { :notice => "Items Added!" }
end
My model method to import the file:
def self.import_csv(file)
csv = CSV.read(file.path), :headers => true)
csv.each do |row|
item_id = row[0]
start_date = Date.parse(row[1])
order_date = Date.parse(row[2])
new_rec = where(item_id:item_id, order_date:order_date).first_or_initialize
new_rec.save!
end
end
All this works well when the file is properly formatted, however Im confused as to how to handle exceptions. Once such exception is when start_date or order_date are missing; I get an no implicit conversion of nil into String because I'm attempting to parse the date of an empty cell. Even though I have validations in my model for presence, they only get fired on the save action.
I don't want to silently ignore these errors with a rescue block, but instead redirect back, and notify the user.
How can I handle such exceptions, so that the jobs fails, and the user gets notified of the error, not specifically solely the error given above, but including errors we can't account for? Another example would be a empty line or something we cant even account for. How can I handle such errors, and notify the user with, for example, a generic "Bad data" message?
Should I handle this in my model or my controller?
If you have presence validations for the dates, you can simply assign them only if they exist, and let your model do the rest:
def self.import_csv(file)
csv = CSV.read(file.path), :headers => true)
csv.each do |row|
item_id = row[0]
start_date = row[1].nil? ? nil : Date.parse(row[1])
order_date = row[2].nil? ? nil : Date.parse(row[2])
new_rec = where(item_id:item_id, order_date:order_date).first_or_initialize
new_rec.save!
end
end

Resources