Rails 4 - Import CSV and blank values - ruby-on-rails

I have an ecommerce app in Rail 4. There is an import csv function to allow me to bulk upload product details. Each product has a price and a saleprice. The price is a required field. The saleprice is optional.
When I import a file with saleprice populated or not populated for new products (not on site yet), it uploads as intended. Since the saleprice field is in the hash, when it's blank, it assumes there is no saleprice. This is fine and as intended.
However, when I want to update a product that has a saleprice to remove the saleprice, then it doesn't remove the sale price. If I put the saleprice outside the hash like :saleprice => row['SalePrice'].to_i, then it shows saleprice as $0 (when it's blank)
When saleprice is blank, I want it to assume there is no saleprice so ignore it.
Same behavior for other fields in the list_hash
Appreciate the help.
here is the code:
# model
validates :saleprice, numericality: {greater_than: 0}, :allow_blank => true
def importcsv(file_path, user_id)
open(file_path) do |f|
CSV.parse(f.read , headers: true, skip_blanks: true) do |row|
listing_hash = {:name => row['Product_title'],
:made_to_order => row['Made_to_order'],
:description => row['Description'],
:sku => row['Product_ID'],
:price => row['Price'].to_i,
:category => row['Category'].titleize,
:inventory => row['Quantity_in_stock'].to_i,
:image => URI.parse(row['Image']),
:user_id => user_id}.tap do |list_hash|
list_hash[:designer_or_brand] = row['Designer_or_Brand'] if row['Designer_or_Brand']
list_hash[:saleprice] = row['SalePrice'].to_i if row['SalePrice']
list_hash[:image2] = URI.parse(row['Image2']) if row['Image2']
list_hash[:image3] = URI.parse(row['Image3']) if row['Image3']
list_hash[:image4] = URI.parse(row['Image4']) if row['Image4']
end
listing = Listing.where(sku: listing_hash[:sku], user_id: listing_hash[:user_id])
if listing.count == 1
listing.first.update_attributes(listing_hash)
else
Listing.create!(listing_hash)
end
end

Related

Uploading csv file and save in database

I have one problem when I try to save some data into my database, imported from a CSV file (uploaded).
My environment is about a classroom reservation. I have the following code for my model Reservation:
class Reservation < ActiveRecord::Base
require 'csv'
belongs_to :classroom
validates :start_date, presence: true
validates :end_date, presence: true
validates :classroom_id, presence: true
validate :validate_room
scope :filter_by_room, ->(room_id) { where 'classroom_id = ?' % room_id }
def self.import(file)
CSV.foreach(file, headers: true ) do |row|
room_id = Classroom.where(number: row[0]).pluck(:id)
Reservation.create(classroom_id: room_id, start_date: row[1], end_date: row[2])
end
end
private
def validate_room
if Reservation.filter_by_room(classroom_id).nil?
errors.add(:classroom_id, ' has already booked')
end
end
end
The CSV file comes with these three headers: "classroom number", "start date", "end date".
Note that "classroom number" header came from a column of classroom table.
My job is to get the classroom.id using the "number" and create the row in the database of the reservation table.
Ok, but the problem is when I get the classroom_id in "self.import" method and print on the console, he exists. When I use the scope to filter the classroom_id, he is empty.
Expect I've expressed myself like I want.
Sorry for my bad English :/
Edit: Discovered that classroom_id before Reservation.create become nil when I use inside the create method. If I use row[0] works, but I need to use classroom_id.
{ where 'classroom_id = ?' % room_id }
Should be
{ where 'classroom_id = ?', room_id }
The answer is simple, I forgot to use .first after pluck(:id) method.
The pluck method returns a value wrapped in an array:
room_id = Classroom.where(number: row[0]).pluck(:id).first

No Method When Importing to Rails via CSV

I'm importing into my Rails App via CSV. The import worked until I tried to create a new contact using one of the columns on my CSV. Here's the code that imports the data:
row = Hash[[header, spreadsheet.row(i)].transpose]
hotel = find_by_id(row["id"]) || new
hotel.attributes = row.to_hash.select { |k,v| allowed_attributes.include? k }
hotel_name = row["hotel_title"]
hotel_id = row["id"]
if row.to_hash["contact_name"]
hotel.contact = Contact.create(:hotel_id => row["id"], :name => row["contact_name"])
end
hotel.map = Map.create(:hotel_id => row["id"], :status => 0, :title => "Map Publication for #{hotel_name}")
hotel.app = App.create(:hotel_id => row["id"], :status => 0, :title => "Bespoke App for #{hotel_name}")
hotel.save!
When I run the import, I recieve the following error:
undefined method `contact=' for #
The Contacts belong to the Hotel, and the Hotel has_many Contacts. I have created Contacts using the App, but I'm struggling to get it working using this import method. I'm not sure what I'm doing wrong here, as the Map and App models are both being created (w/ the ID of the Hotel) fine after import. The import now fails when I include the code relating to the contact_name.
class Contact < ActiveRecord::Base
belongs_to :hotel
end
class Hotel < ActiveRecord::Base
has_many :contacts
end
You should use << operator, as it's plural association:
hotel.contacts << Contact.create(name: row['contact_name'])
Note that I removed :hotel_id key from parameters, as it would be redundant (it's already set via association).
You can do it also from the other end of association:
Contact.create(name: row['contact_name'], hotel: hotel)
Just remove hotel.contact =. You don't need it, as you are explicitly providing :hotel_id while creating the contact record.
So, you need to replace
if row.to_hash["contact_name"]
hotel.contact = Contact.create(:hotel_id => row["id"], :name => row["contact_name"])
end
with
Contact.create(:hotel_id => row["id"], :name => row["contact_name"]) if row.to_hash["contact_name"]

Rails + Postgresql: data not saved first time

The first time I try to submit the form I get the error saying
"Price is not a valid number"
It's OK the second time I try to submit it (with the same valid data in :price field).
If I don't add validation in the model, then the form is submitted, but value of price is not saved.
What could be going on? Is there something special about .decimal field?
db schema:
t.decimal "price"
model
validates :price, numericality: { :greater_than => 0, :less_than_or_equal_to => 100000000 }
form view file
<%= f.number_field :price, class: "short red" %>
controller
def new
#product = Product.new
end
def create
#product = Product.new(product_params)
if #product.save
redirect_to #product
else
render :new
end
end
private
def product_params
params.require(:product).permit(:name, :description, :image, :price, :user_id)
end
logs
Started POST "/products" for xxx.132 at 2014-10-15 22:56:51 +0000
Processing by ProductsController#create as HTML
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"abte/LtO0T/ZtSXQIuXVVjjUvwHw5jDUJ1yIKCOWRx2=",
"product"=>{"name"=>"", "description"=>"", "user_id"
=>"1"}, "commit"=>"Submit"}
Some things you can check:
The snippet from your form starts f.number_field. Check that you are using something like <%= form_for(#product) do |f| %> at the top of the form.
Try to create a product using the rails console.
In the rails console, try something like this:
> p = Product.new
> p.valid?
#=> TRUE or FALSE should appear
> p.errors.full_messages.to_sentence
# you should see a full list of all failed validations from your Product model
If these don't help, try pasting in the entire product_controller.rb and _form.html.erb files into your question, and I'll take a look again.
Try to change your migration to:
t.decimal :price, precision: 8, scale: 2 #for example
Then, change validation to:
validates :price, numericality: {greater_than_or_equal_to: 0.01, :less_than_or_equal_to => 100000000 }
In PostgreSQL next implementations behavior with :decimal columns:
PostgreSQL: :precision [1..infinity], :scale [0..infinity]. No
default.
I hope, this example from "Agile Web Development with Rails 4" help you to understand validation of decimal numbers:
it’s possible to enter a number such as 0.001 into this field. Because
the database stores just two digits after the decimal point, this
would end up being zero in the database, even though it would pass the
validation if we compared against zero. Checking that the number is at
least 1 cent ensures only correct values end up being stored.

Rails 4 - ActiveMerchant Paypal Express Checkout Issue with Passing Item Quantity

I have a weird issue with the PayPal express checkout. When I pass in an options[:item][:quantity], I get an error code from PayPal that the transaction in invalid.
#controllers/orders_controller.rb
def express
options = {
:ip => request.remote_ip,
:return_url => new_order_url,
:cancel_return_url => products_url,
:items => current_cart.line_items_hash
}
response = EXPRESS_GATEWAY.setup_purchase(current_cart.build_order.price_in_cents,
options)
redirect_to EXPRESS_GATEWAY.redirect_url_for(response.token)
end
# models/cart.rb
def line_items_hash
self.line_items.map do |line_item|
{
:name => Product.find(line_item.product_id).name
# :quantity => line_item.quantity,
:description => line_item.product.description,
:amount => line_item.gross_price_in_cents}
end
end
So this works fine. The problem is that the correct quantity is not shown on the PayPal order confirmation page. However, if I uncomment the quantity variable in the line_items_hash function the whole thing breaks and I get "invalid transaction from paypal". Any ideas?
Silly old me. Paypal keeps invalidating my transaction because I'm passing bad information. I was setting the amount to line_item total which is already the total of quantity * product.price. So the information that Paypal was receiving is quantity * (quantity * product.price).
Silly little mistake but I managed to catch it in the end.
# models/cart.rb
def line_items_hash
self.line_items.map do |line_item|
{
:name => Product.find(line_item.product_id).name
:quantity => line_item.quantity,
:description => line_item.product.description,
:amount => line_item.price.in_cents}
end
end

CSV Validation columns

I have code to check if a CSV file is correct. I would like to write idiomatic code to check if the columns are correct. I don't want to write in block check if we have got first line check columns.
CSV.foreach(#csv) { |person|
first_name, last_name, person_id, email, title, phone, mobile, department, address, city = person[0..9]
zip_code, state, country, manager_id =person[10..13]
#managers << manager_id
#persons << person_id
validate = false unless validate_email(email)
validate = false unless validate_first_name(first_name)
validate = false unless validate_last_name(last_name)
validate = false unless validate_person_id(person_id)
}
Does the CSV have headers or can you add them? If yes you can do CSV.foreach(#csv, :headers => true) and get the column values like person['first_name']. Then the checks at the end become
validate = false unless validate_email(person['email'])
That said, it seems like your entire validation at the end could be written as
validate_email(person['email']) && validate_first_name(person['first_name']) etc.
+1 for Michael's advice for :headers => true
But if you want to validate all of the named fields in your example and are aiming for DRY (though, admittedly, maybe a bit too clever for maintainability), you could use Enumerable#inject:
CSV.foreach(#cvs, :headers => true) { |person|
#managers << person[:manager_id]
#persons << person[:person_id]
# Array of columns to be validated
validate_cols = [:first_name, :last_name, :person_id, :email, :title, :phone,
:mobile, :department, :address, :city, :zip_code, :state,
:country, :manager_id]
valid = validate_cols.inject(true){|valid_sum, col|
valid_sum && send("validate_#{col}", person[col])
}
}
This assumes you have validate_* methods for every column named in the validate_cols array.

Resources