I'm developing an ecommerce app and I have a csv export feature which exports all product details like name, price, etc. Each product is in one row with a column for each product attribute. I want to add a column to the file which will contain the url of each product. The reason I want this is so I can use this as a product feed that can be submitted to various shopping sites.
Here is my export code in the controller. How do I add a column called route to this? I don't have a route column in the model.
#controller
def productlist
#listings = Listing.all
respond_to do |format|
format.html
format.csv { send_data #listings.to_csv(#listings) }
end
end
#model
def self.to_csv(listings)
wanted_columns = [:sku, :name, :designer_or_brand, :description, :price, :saleprice, :inventory, :category]
CSV.generate do |csv|
csv << ['Product_ID', 'Product_title', 'Designer_or_Brand', 'Description', 'Price', 'SalePrice', 'Quantity_in_stock', 'Category'] + [:Image, :Image2, :Image3, :Image4]
listings.each do |listing|
attrs = listing.attributes.with_indifferent_access.values_at(*wanted_columns)
attrs.push(listing.image.url, listing.image2.try(:url), listing.image3.try(:url), listing.image4.try(:url))
csv << attrs
end
end
end
def self.to_csv(listings)
wanted_columns = [:sku, :name, :designer_or_brand, :description, :price,
:saleprice, :inventory, :category]
header = %w(Product_ID Product_title Designer_or_Brand Description Price
SalePrice Quantity_in_stock Category Image Image2 Image3 Image4 ProductUrl)
CSV.generate do |csv|
csv << header
listings.each do |listing|
attrs = listing.attributes.with_indifferent_access.values_at(*wanted_columns)
<< listing.image.url << listing.image2.try(:url)
<< listing.image3.try(:url) << listing.image4.try(:url)
<< Rails.application.routes.url_helpers.product_url(listing.Product_ID)
csv << attrs
end
end
end
Actually the only difference is last item of array: Rails.application.routes.url_helpers.product_url(listing.Product_ID), where product_url is your route to product#show
Related
I have an array of objects. I am trying to create CSV data and allow the user to download that file but I get the following error:
Undefined method 'first_name' for Hash:0x007f946fc76590
employee_csv_data.each do |obj|
csv << attributes.map{ |attr| obj.send(attr) }
end
end
end
This is the button that allows a user to download the CSV:
<%= link_to "Download Employee CSV", download_employee_csv_path %>
Controller:
def download_employee_csv
employee_csv_data = []
employees.each do |employee|
employee_csv_data << {
first_name: employee[:first_name],
last_name: employee[:last_name],
email: employee_email,
phone1: employee[:phone1],
gender: employee[:gender],
veteran: employee[:veteran].to_s,
dob: employee[:dob],
core_score: service_score,
performance_rank: rank,
industry_modules_passed: industry_modules_passed
}
end
respond_to do |format|
format.html
format.csv { send_data Employer.to_csv(employee_csv_data), filename: "download_employee_csv.csv" }
end
end
employee_csv_data:
=> [{:first_name=>"Christopher",
:last_name=>"Pelnar",
:email=>"pelnar#gmail.com",
:phone1=>"4072422433",
:gender=>"male",
:veteran=>"true",
:dob=>"1988-09-09",
:core_score=>"No Score",
:performance_rank=>"No Rank",
:industry_modules_passed=>"No Industry Modules Passed"},
{:first_name=>"chris",
:last_name=>"pelnar",
:email=>"chris#gmail.com",
:phone1=>"4072422433",
:gender=>"male",
:veteran=>"true",
:dob=>"1998-09-09",
:core_score=>"729",
:performance_rank=>"Good",
:industry_modules_passed=>"Entry-Service, Entry-Tech"}]
Model:
def self.to_csv(employee_csv_data)
attributes = %w(first_name last_name email phone gender veteran dob core_score performance_rank industry_modules_passed)
CSV.generate(headers: true) do |csv|
csv << attributes
employee_csv_data.each do |obj|
csv << attributes.map{ |attr| obj.send(attr) }
end
end
end
When I click the button, it takes me to the blank HTML page without any problem. When I add .csv to the filename in the URL on that page I get the error.
It looks like it's an array of Hashes. To access properties of a hash in Ruby you need to use brackets. Try updating your code to this:
csv << attributes.map{ |attr| obj.send([], attr) }
or more concisely:
csv << attributes.map{ |attr| obj[attr] }
One more thing, in the example you provided, the keys in the hash are symbols which means you may need to convert your attributes to symbols when trying to access them, like this:
csv << attributes.map{ |attr| obj[attr.to_sym] }
I adapted #Ctpelnar1988's answer to determine the attributes dynamically and allow each array item to have different columns:
def array_of_hashes_to_csv(array)
array_keys = array.map(&:keys).flatten.uniq
CSV.generate(headers: true) do |csv|
csv << array_keys
array.each do |obj|
csv << array_keys.map{ |attr| obj[attr] }
end
end
end
Example:
puts array_of_hashes_to_csv([
{attr_a: 1, attr_b: 2},
{attr_a: 3, attr_c: 4}
])
attr_a,attr_b,attr_c
1,2,
3,,4
In the more specific "employee_csv_data" context, I think it'd look like this:
def self.to_csv(employee_csv_data)
attributes = employee_csv_data.map(&:keys).flatten.uniq
CSV.generate(headers: true) do |csv|
csv << attributes
employee_csv_data.each do |obj|
csv << attributes.map { |attr| obj[attr] }
end
end
end
I'm having some trouble with my named scope.
def self.by_status(status)
arr = status.split(',').map{ |s| s }
logger.debug "RESULT: #{arr.inspect}"
where(status: arr)
end
When I call this scope with more than one value, the result of arr = ["New", "Open"]
This does not return any results, while it should. If I try this command in the console: Shipment.where(status: ['New', 'Open']) I get the results that I'm expecting.
Am I missing something here?
Edit (added the call of the class method ):
def self.to_csv(options = {}, vendor_id, status)
CSV.generate(options) do |csv|
csv << column_names
if !vendor_id.blank? && status.blank?
by_vendor_id(vendor_id).each do |product|
csv << product.attributes.values_at(*column_names)
end
elsif !vendor_id.blank? && !status.blank?
by_vendor_id(vendor_id).by_status(status).each do |product|
csv << product.attributes.values_at(*column_names)
end
elsif vendor_id.blank? && !status.blank?
logger.debug "by_status result: #{by_status(status).inspect}"
by_status(status).each do |product|
csv << product.attributes.values_at(*column_names)
end
else
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
end
Try this in your model:
scope :by_status, ->(*statuses) { where(status: statuses) }
Then in your code you can call:
Shipment.by_status('New', 'Open')
This has the flexibility to just take one argument, too:
Shipment.by_status('New')
I built a working shopping cart system the other day using Ruby on Rails, and with the guidance of following a tutorial. Now I would like to modify the existing shopping cart item to incorporate the size of a particular item being placed in the cart.
I created a migration file to add a size column to the Products table, and then I started modifying CartItem class, and the Cart class files respectively.
class CartItem
attr_reader :product_id, :quantity, :size
def initialize product_id, quantity = 1, size
#product_id = product_id
#quantity = quantity
#size = size
end
def increment
#quantity = #quantity + 1
end
def product
Product.find product_id
end
def total_price
# puts "Hello cart_item"
product.price * quantity
end
end
class Cart
attr_reader :items
def self.build_from_hash hash
items = if hash["cart"] then
hash["cart"]["items"].map do |item_data|
CartItem.new item_data["product_id"], item_data["quantity"], item_data["size"]
end
else
[]
end
new items
end
def initialize items = []
#items = items
end
def add_item product_id, size
item = #items.find { |item| item.product_id == product_id
item.size == size }
if item
item.increment
else
#items << CartItem.new(product_id, size)
end
end
def empty?
#items.empty?
end
def count
#items.length
end
def serialize
items = #items.map do |item|
{
"product_id" => item.product_id,
"quantity" => item.quantity,
"size" => item.size
}
end
{
"items" => items
}
end
def total_price(shipping_price = 0)
# puts "Hello cart"
#items.inject(0) { |sum, item| sum + item.total_price } + shipping_price
end
end
However, I'm getting the following error,
Because your add_item method should be into two parameter, but your params is hash can't use params[:id, :size] replace params[:id], params[:size] it work.
Good morning! My english is not the best.
I'm trying to import some data from csv file using this model.
class Recibo < ActiveRecord::Base
attr_accessible :id,
:caja_id,
:doctor_id,
:numero_recibo,
:paciente,
:total,
:total_porcentaje_doctor,
:total_porcentaje_clinica,
:total_porcentaje_laboratorio,
:servicio_ids,
:created_at,
:updated_at
belongs_to :caja
belongs_to :doctor
has_many :atencions
has_many :servicios, :through => :atencions
before_save do
servicio_by_id = Servicio.where(:id => servicio_ids)
self.total = servicio_by_id.sum(&:precio)
self.total_porcentaje_doctor = servicio_by_id.sum ('porcentaje_doctor / 100.0 * precio')
self.total_porcentaje_clinica = servicio_by_id.sum ('porcentaje_clinica / 100.0 * precio')
self.total_porcentaje_laboratorio = servicio_by_id.sum ('porcentaje_laboratorio / 100.0 * precio')
end
def self.to_csv
CSV.generate do |csv|
csv << ["id", "caja_id", "doctor_id", "numero_recibo", "paciente", "total", "total_porcentaje_laboratorio",
"total_porcentaje_clinica", "total_porcentaje_doctor", "created_at", "updated_at", "servicio_ids" ]
all.each do |recibo|
recibo.atencions.map(&:servicio_id)
csv << [recibo.id, recibo.caja_id, recibo.doctor_id, recibo.numero_recibo,
recibo.paciente, recibo.total, recibo.total_porcentaje_laboratorio, recibo.total_porcentaje_clinica,
recibo.total_porcentaje_doctor, recibo.created_at, recibo.updated_at, recibo.servicio_ids]
end
end
end
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
recibo = find_by_id(row["id"]) || new
recibo.attributes = row.to_hash.slice(*accessible_attributes)
recibo.save!
end
end
end
my csv file contain data like this:
id,caja_id,doctor_id,numero_recibo,paciente,total,total_porcentaje_laboratorio,total_porcentaje_clinica,total_porcentaje_doctor,created_at,updated_at,servicio_ids
1,2,3,,Nombre,8,0,4,4,2014-04-21 15:45:29 -0500,2014-05-27 18:58:54 -0500,[1]
2,2,1,,Nombre2,11,0,5.5,5.5,2014-04-21 16:38:32 -0500,2014-05-27 19:28:20 -0500,[1, 8]
The self.import(file) suppose to add the records servicio_ids in the table atencion but it dosen't. I don't know what to do.
Thanks for everything!
When generating your .csv file, instead of:
CSV.generate do |csv|
do:
CSV.generate(force_quotes: true) do |csv|
By default CSV adds commas between the values, which messes up the parsing in your case, because of the commas inside the array elements.
I would like to create one CSV file containing two models with comma gem in my ruby 3.2.8 application. Maybe the answer to the question is trivial, but this is the first time I use this gem. I know how create the file it based on a model, but I don' t know how make matches of two.
I have a views\participants\index with :
<%= link_to 'Download CSV', '/participants.csv' %>
the controller :
def index
#participants = Participant.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #participants }
format.csv { send_data #participants.to_comma }
end
end
participant Model:
require 'comma'
class Participant < ActiveRecord::Base
comma do
id
token
end
end
and field Model:
require 'comma'
class Field < ActiveRecord::Base
comma do
name
value
id_participant
end
end
in the db i have:
Participant1 = ["id" => 1 , "token" => "a"]
Participant2 = ["id" => 2 , "token" => "b"]
Field1= ["id_participant" => 1, "name" => "p1_exams1", "value" =>5]
Field2= ["id_participant" => 1, "name" => "p1_exams2", "value" =>3]
Field3= ["id_participant" => 2, "name" => "p2_exams1", "value" =>2]
Field4= ["id_participant" => 2, "name" => "p2_exams2", "value" =>3]
I would like to have a file like this:
id token
1 a
id_p name value
1 p1_c1_exams1 5
1 p1_c1_exams2 3
id token
2 b
id_p name value
2 p1_c1_exams1 2
2 p1_c1_exams2 3
I tried with this in controller:
def index
#participants = Participant.all
#fields = Field.all
require 'csv'
csv_string = CSV.generate do |csv|
#participants.each do |p|
csv << ["id","token","last_ip_address","start_date","last_transition_date","completion_date","completed","total_time_survey","created_at"]
csv << [ p.id, p.token , p.last_ip_address, p.start_date, p.last_transition_date, p.completion_date, p.completed, p.total_time_survey, p.created_at]
#fields.each do |f|
if String(f.id_participant) == String(p.id)
csv << ["id","name","value","id_participant","id_survey","created_at"]
csv << [f.id,f.name, f.insert_value, f.id_participant, f.id_survey, f.created_at]
end
end
end
end
respond_to do |format|
format.html # index.html.erb
format.json { render json: #participants }
format.csv { send_data csv_string,
:type => "text/csv; charset=iso-8859-1; header=present",
:disposition => "attachment; filename=Database.csv" }
end
end
You can also use the fastercsv for this
i think this will help u what i am understanding that u have has many relationship between Participant and Field regarding this i have write some piece of code u can customize it as ur need
#participants = Participant.all
csv_string = FasterCSV.generate do |csv|
#participants.each do |i|
csv << ["id","token"]
csv << [ i.id, i.token ]
i.fields.each do |j|
csv << ["id_p","name", "value"]
csv << [i.id,j.name, j.value]
end
end
end
send_data csv_string,
:type => "text/csv; charset=iso-8859-1; header=present",
:disposition => "attachment; filename=anyName.csv"