27Jan/101
An Excel CSV exporter for ActiveRecord
This is a mix of two blog posts on exporting ActiveRecord data to CSV. This explicitly was designed to export stuff so excel wouldn't freak out.
This strips new lines from any string field, set the BOM and converts the encoding to utf16.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | require "fastercsv" require "iconv" # Excel info from: http://blog.plataformatec.com.br/2009/09/exporting-data-to-csv-and-excel-in-your-rails-app/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+PlataformaBlog+(Plataforma+Blog) # Original post: http://www.brynary.com/2007/4/28/export-activerecords-to-csv # Usage: # Given User is an ActiveRecord model # # Options for Model.to_csv, Array.to_csv, and ActionController::Base#send_csv # # :only - Array, trumps :exclude. Only these attributes will be included instead of all attributes (things listed in :methods will still be sent) # :only => [ :id, :name, :created_at ] # :methods - Array, additional methods to evaluate and add to the CSV response. Note: you can send nested method calls # :methods => [ :to_s, :complex_method, "my.method.on.a.related.object"] # :exclude - Array, attributes to exclude from CSV result # # From ActionController # send_csv User, :only => [:first_name, :email, :created_at] #This will do all records # send_csv User.all(:conditions => ["created_at > ?", some_date]), :only => [:first_name, :email, :created_at] # # From ActiveRecord Model # User.to_csv :only => [ :username, :email, :created_at ], :methods => [ :age, "account.id"] # All users additionally get their related Account#id number # User.all(:limit => 10).to_csv :exclude => [:password, :birthdate] # class ActiveRecord::Base # Shortcut for CSV of whole table def self.to_csv(*args) find(:all).to_csv(*args) end # Get the column headers def self.csv_columns(options={}) tmp_columns = if options[:only] options[:only] #only trumps exclude else self.content_columns.map{|curr_col| curr_col.name } - options[:exclude].map{|curr_col| curr_col.to_s } end tmp_columns + options[:methods].map{|curr_col| curr_col.to_s } end # Record to a row level csv array def to_csv(options={}) self.class.csv_columns(options).map { |curr_col| curr_col = curr_col.to_s #Its a chain of method calls, on intermediary nil, just return nil if !curr_col.index(".") col_val = self.send(curr_col) else col_val = self curr_col.split(".").each {|curr_method| col_val = col_val.send(curr_method) unless col_val.nil?} end col_val.gsub!("\n", " ") if col_val.is_a?(String) # Strip newlines, Appease Excel Gods col_val } end end class Array # Convert an array of objects into a CSV String. def to_csv(options = {}) column_options = { :only => options.delete(:only), :exclude => options.delete(:exclude) || [], :methods => options.delete(:methods) || [] } options = { :col_sep => "\t" # Appease Excel Gods }.merge(options) if all? { |e| e.respond_to?(:to_csv) } header_row = first.class.csv_columns(column_options).to_csv content_rows = map { |e| # Get all the values of non-excluded rows e.to_csv(column_options) }.map{|r| # Call to_csv on the array of row-level values, this will join them into a CSV row r.to_csv(column_options) } ([header_row] + content_rows).join else FasterCSV.generate_line(self, options) end end end # module for extending ActionController module ExcelCSVExporter BOM = "\377\376" #Byte Order Mark, Appease Excel Gods def send_csv(kollection, options={}) filename = options.delete(:filename) || I18n.l(Time.now, :format => :short) + ".csv" content = kollection.to_csv(options) # Appease Excel Gods content = BOM + Iconv.conv("utf-16le", "utf-8", content) send_data content, :filename => filename end end ActionController::Base.send :include, ExcelCSVExporter |
Wanna use it?
1 2 3 4 5 6 7 | class MyCoolController < ActionController::Base def index respond_to {|want| want.csv{ send_csv MyCoolModel.all, :exclude => [:secret_column], :methods => [:complex_method, "related.table.method"]} } end end |