2008-09-18 12 views
52

Mam proste tabeli bazy danych o nazwie „Wpisy”:w szynach, jak wrócić rekordy w postaci pliku csv

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 

Jak napisać procedurę obsługi, która zwróci zawartość tabeli z wpisami jako CSV plik (najlepiej w taki sposób, aby automatycznie otwierał się w Excelu)?

class EntriesController < ApplicationController 

    def getcsv 
    @entries = Entry.find(:all) 

    # ??? NOW WHAT ???? 

    end 

end 
+0

przynajmniej w nowszych wersjach Rails, można również użyć 'Entry. wszystko zamiast tego. –

Odpowiedz

22

Istnieje wtyczka o nazwie FasterCSV, która doskonale sobie z tym radzi.

+29

Zauważ, że ruby> = 1.9, FasterCSV jest teraz standardową biblioteką CSV i nazywa się po prostu CSV. – kdt

+0

Klucz "wymaganie" csv'', zobacz https://ruby-doc.org/stdlib/libdoc/csv/rdoc/CSV.html – mb21

7

Zapoznaj się z klejnotem FasterCSV.

Jeśli potrzebujesz tylko wsparcia Excela, możesz również bezpośrednio wygenerować xls. (Patrz arkusza kalkulacyjnego Excel ::)

gem install fastercsv 
gem install spreadsheet-excel 

znajdę te opcje dobre dla otwierania pliku csv w Windows Excel:

FasterCSV.generate(:col_sep => ";", :row_sep => "\r\n") { |csv| ... } 

jak dla części ActiveRecord, coś jak to zrobi:

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 
2

Musisz ustawić nagłówek Content-Type w swojej odpowiedzi, a następnie wysłać dane. Content_Type: application/vnd.ms-excel powinno wystarczyć.

Możesz także ustawić nagłówek Content-Disposition tak, aby wyglądał jak dokument Excel, a przeglądarka wybiera rozsądną domyślną nazwę pliku; to coś takiego jak Content-Disposition: załącznik; filename = "# {proponowana_nazwę} .xls"

Proponuję użyć klejnotu ruby ​​szybciejsc do wygenerowania pliku CSV, ale jest też wbudowany plik CSV. Przykładowy kod fastercsv (z dokumentacją gem) wygląda następująco:

csv_string = FasterCSV.generate do |csv| 
    csv << ["row", "of", "CSV", "data"] 
    csv << ["another", "row"] 
# ... 
end 
+0

Dzięki za ten niejasny Content-Type! Nie jestem pewien, czy to w końcu będzie Excel, ale to dobrze wiedzieć. – Eric

87

FasterCSV jest zdecydowanie do zrobienia, ale jeśli chcesz to służyć bezpośrednio z aplikacji Rails, będziemy chcieli, aby skonfigurować niektóre również nagłówki odpowiedzi.

Trzymam metodę wokół skonfigurować nazwę i niezbędne nagłówki:

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 

Korzystanie który ułatwia mieć coś takiego w moim kontrolera:

respond_to do |wants| 
    wants.csv do 
    render_csv("users-#{Time.now.strftime("%Y%m%d")}") 
    end 
end 

I mają widok to wygląda tak: (generate_csv pochodzi z 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 %> 
+0

Hmm, myślałem, że skończyłem, dopóki nie zobaczyłem twojej odpowiedzi! Dziękuję za szczegóły nagłówka, zapamiętam je na wypadek, gdy wpadnę w kłopoty z tym, czego używam do tej pory. – Eric

+8

Jak wspomniano powyżej, FasterCSV jest po prostu CSV w ruby ​​1.9 i wyżej. Metoda gererate_csv ma ​​teraz wartość CSV.generate. –

+4

Dodanie pliku .html_safe na końcu bloku generate_csv/CSV.generate spowoduje, że wszystkie przecinki w danych będą poprawnie obsługiwane. Bez tego wywołania mój plik csv miał w sobie cały zestaw " ". – stcorbett

24

Przyjąłem (i głosowałem w górę!) @ Odpowiedź Briana, ponieważ najpierw skierowałem mnie do FasterCSV. Potem, gdy szukałem go w Google, by znaleźć ten klejnot, znalazłem dość kompletny przykład na this wiki page. Łącząc je, zdecydowałem się na następujący kod.

Nawiasem mówiąc, polecenie, aby zainstalować gem jest: sudo gem install fastercsv (małymi literami)

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 
+5

Proponuję przenieść generację CSV do widoku: to więcej logiki prezentacji, niż normalnie chcesz w kontrolerze. –

+0

Możesz ustawić typ na "text/csv". W przeciwnym razie safari zapisze go jako "entries.csv.txt" – silasjmatson

24

Innym sposobem, aby to zrobić bez użycia FasterCSV:

Wymagaj csv Rubiego biblioteka w pliku inicjalizacyjnym, takim jak config/initializers/dependencies.rb

require "csv" 

Jako tło uzupełnia się następujący kod w oparciu o numer Ryan Bate's Advanced Search Form, który tworzy zasób wyszukiwania. W moim przypadku metoda show zasobu wyszukiwania zwróci wyniki wcześniej zapisanego wyszukiwania. Odpowiada również na CSV i używa szablonu widoku do sformatowania pożądanego wyniku.

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 

Plik show.csv.erb wygląda następująco:

<%- 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 -%> 

W wersji html strony raportu mam link do eksportowania raportu, że użytkownik ogląda. Poniżej znajduje się link_to że zwraca wersję csv raportu:

<%= link_to "Export Report", formatted_advertiser_search_path(@advertiser_search, :csv) %> 
+5

Nie zapomnij użyć '<% = CSV.generate_line (wiersz) .html_safe%>' jeśli używasz szyn 3, aby uniknąć znaków uciekających. – dombesz

+6

To rozwiązanie działa dobrze, ale musiałem zmienić ustawienie wywołania CSV.generate_line: row_sep na zero. Ta zmiana usuwa niechciane puste wiersze z odpowiedzi. Kod: '<% = CSV.generate_line wiersz, {: row_sep => zero}%>' –

+2

Aby dodać do komentarza Wilsona, Heroku (cedrowy stos -beta) wstawi puste wiersze do plików CSV, chyba że wyraźnie powiesz, że nie z ': row_sep => zero'. – nslocum

2

Poniższy zbliżył pracował dobrze dla mojego przypadku i powoduje przeglądarkę, aby otworzyć odpowiednią aplikację dla danego typu CSV po pobraniu.

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 
1

spróbować piękny klejnot, aby wygenerować plik CSV z Rails https://github.com/crafterm/comma

+1

Dobra odpowiedź, ale żeby była świetna, może mógłbyś wyjaśnić, dlaczego ten klejnot (w przeciwieństwie do innych) –

0

Jeśli jesteś po prostu chcąc dostać się baza danych CSV się z konsoli można to zrobić w ciągu kilku liniach

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(""))}