en Rails, cómo devolver registros como un archivo csv
-
01-07-2019 - |
Pregunta
Tengo una tabla de base de datos simple llamada "Entradas":
class CreateEntries < ActiveRecord::Migration
def self.up
create_table :entries do |t|
t.string :firstName
t.string :lastName
#etc.
t.timestamps
end
end
def self.down
drop_table :entries
end
end
¿Cómo escribo un controlador que devolverá el contenido de la tabla Entradas como un archivo CSV (idealmente de manera que se abra automáticamente en Excel)?
class EntriesController < ApplicationController
def getcsv
@entries = Entry.find( :all )
# ??? NOW WHAT ????
end
end
Solución
Hay un complemento llamado FasterCSV que maneja esto maravillosamente.
Otros consejos
CSV más rápido es definitivamente el camino a seguir, pero si desea servirlo directamente desde su aplicación Rails, también querrá configurar algunos encabezados de respuesta.
Mantengo un método para configurar el nombre del archivo y los encabezados necesarios:
def render_csv(filename = nil)
filename ||= params[:action]
filename += '.csv'
if request.env['HTTP_USER_AGENT'] =~ /msie/i
headers['Pragma'] = 'public'
headers["Content-type"] = "text/plain"
headers['Cache-Control'] = 'no-cache, must-revalidate, post-check=0, pre-check=0'
headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
headers['Expires'] = "0"
else
headers["Content-Type"] ||= 'text/csv'
headers["Content-Disposition"] = "attachment; filename=\"#{filename}\""
end
render :layout => false
end
Usar eso hace que sea fácil tener algo como esto en mi controlador:
respond_to do |wants|
wants.csv do
render_csv("users-#{Time.now.strftime("%Y%m%d")}")
end
end
Y tenga una vista que se parece a esta:(generate_csv
es de FasterCSV)
UserID,Email,Password,ActivationURL,Messages
<%= generate_csv do |csv|
@users.each do |user|
csv << [ user[:id], user[:email], user[:password], user[:url], user[:message] ]
end
end %>
Acepté (¡y voté a favor!) la respuesta de @Brian, por indicarme primero FasterCSV.Luego, cuando busqué en Google para encontrar la gema, también encontré un ejemplo bastante completo en esta página wiki.Juntándolos, me decidí por el siguiente código.
Por cierto, el comando para instalar la gema es:Sudo Gem Instale FASTERCSV (todos minúsculas)
require 'fastercsv'
class EntriesController < ApplicationController
def getcsv
entries = Entry.find(:all)
csv_string = FasterCSV.generate do |csv|
csv << ["first","last"]
entries.each do |e|
csv << [e.firstName,e.lastName]
end
end
send_data csv_string, :type => "text/plain",
:filename=>"entries.csv",
:disposition => 'attachment'
end
end
Otra forma de hacer esto sin usar FasterCSV:
Requerir la biblioteca csv de Ruby en un archivo inicializador como config/initializers/dependencies.rb
require "csv"
Como algunos antecedentes, el siguiente código se basa en Formulario de búsqueda avanzada de Ryan Bate que crea un recurso de búsqueda.En mi caso, el método show del recurso de búsqueda devolverá los resultados de una búsqueda previamente guardada.También responde a csv y utiliza una plantilla de vista para formatear la salida deseada.
def show
@advertiser_search = AdvertiserSearch.find(params[:id])
@advertisers = @advertiser_search.search(params[:page])
respond_to do |format|
format.html # show.html.erb
format.csv # show.csv.erb
end
end
El archivo show.csv.erb tiene el siguiente aspecto:
<%- headers = ["Id", "Name", "Account Number", "Publisher", "Product Name", "Status"] -%>
<%= CSV.generate_line headers %>
<%- @advertiser_search.advertisers.each do |advertiser| -%>
<%- advertiser.subscriptions.each do |subscription| -%>
<%- row = [ advertiser.id,
advertiser.name,
advertiser.external_id,
advertiser.publisher.name,
publisher_product_name(subscription),
subscription.state ] -%>
<%= CSV.generate_line row %>
<%- end -%>
<%- end -%>
En la versión HTML de la página del informe tengo un enlace para exportar el informe que el usuario está viendo.El siguiente es el link_to que devuelve la versión csv del informe:
<%= link_to "Export Report", formatted_advertiser_search_path(@advertiser_search, :csv) %>
Échale un vistazo al CSV más rápido joya.
Si todo lo que necesita es soporte de Excel, también puede considerar generar un xls directamente.(Ver Hoja de Cálculo::Excel)
gem install fastercsv
gem install spreadsheet-excel
Estas opciones me parecen buenas para abrir el archivo csv en Windows Excel:
FasterCSV.generate(:col_sep => ";", :row_sep => "\r\n") { |csv| ... }
En cuanto a la parte ActiveRecord, algo como esto funcionaría:
CSV_FIELDS = %w[ title created_at etc ]
FasterCSV.generate do |csv|
Entry.all.map { |r| CSV_FIELDS.map { |m| r.send m } }.each { |row| csv << row }
end
Debe configurar el encabezado Content-Type en su respuesta y luego enviar los datos.Tipo de contenido:application/vnd.ms-excel debería funcionar.
También es posible que desee configurar el encabezado Content-Disposition para que parezca un documento de Excel y el navegador elija un nombre de archivo predeterminado razonable;eso es algo así como disposición de contenido:adjunto;nombre de archivo="#{nombre_sugerido}.xls"
Sugiero usar la gema Ruby más rápida csv para generar su CSV, pero también hay un csv incorporado.El código de muestra de fastcsv (de la documentación de la gema) se ve así:
csv_string = FasterCSV.generate do |csv|
csv << ["row", "of", "CSV", "data"]
csv << ["another", "row"]
# ...
end
El siguiente enfoque funcionó bien en mi caso y hace que el navegador abra la aplicación adecuada para el tipo CSV después de la descarga.
def index
respond_to do |format|
format.csv { return index_csv }
end
end
def index_csv
send_data(
method_that_returns_csv_data(...),
:type => 'text/csv',
:filename => 'export.csv',
:disposition => 'attachment'
)
end
prueba una buena joya para generar CSV desde Railshttps://github.com/crafterm/comma
Eche un vistazo a la gema CSV Shaper.
https://github.com/paulspringett/csv_shaper
Tiene un buen DSL y funciona muy bien con los modelos Rails.También maneja los encabezados de respuesta y permite la personalización del nombre de archivo.
Si simplemente desea obtener la base de datos csv usted mismo desde la consola, puede hacerlo en unas pocas líneas.
tags = [Model.column_names]
rows = tags + Model.all.map(&:attributes).map(&:to_a).map { |m| m.inject([]) { |data, pair| data << pair.last } }
File.open("ss.csv", "w") {|f| f.write(rows.inject([]) { |csv, row| csv << CSV.generate_line(row) }.join(""))}