I''m in the process to write a fairly generic facility for exporting to CSV. A commented excerpt of the code is below. There are a few things I have noticed and a few open questions. The result set I''m exporting has 4000 rows. * The database query is practically instantaneous when run directly from psql (commandline interface to PgSQL). * The find method is slow, it takes 10 to 15s. I don''t know exactly where the time is spend, probably in instantiating the objects. * Appending to the CSV writer is slow. Even considering the time it takes to access attributes of the objects involved, most time vanishes inside of the CSV library. * With some browsers (Konqueror) each export request actually results in two exports. The first time, the browser only peeks for the content-type, shows a "Save, Open, Cancel" dialog, and then retrieves the data again. * Surprisingly, it''s faster to have CSV append to a string than to an array that is latter joined. * My most pertinent question is, no doubt, how to speed this process up. * Is there a sensible way to determine the user''s OS and convert data to the native charset and end-of-line chars? Michael def export_csv(options = {}) { :charset => charset, :field_separator => field_separator, :record_separator => record_separator, :filename => ''export.csv'' }.update(options) # A ListView (bad name, it''s going to be improved) captures # meta data for list views and exports. # Among them is a path array, e.g. ["lastname"], ["company","name"] # From the meta data, ListView derives pieces for the query. column_specs = list_view_for(:action => ''list'') order = column_specs.build_order_clause conditions = column_specs.build_condition_clause includes = column_specs.includes objects = column_specs.klass.find( :all, :order => order, :conditions => conditions, :include => includes) data = '''' CSV::Writer.generate(data, options[:field_separator], options[:record_separator]) do |csv| csv << csv_head_row(column_specs) objects.each do |object| csv << csv_row(column_specs, object) end end end def csv_head_row(column_specs) column_specs.map do |column_spec| column_spec[:title] || title_from_path(column_spec[:path]) end end def csv_row(column_specs, object) column_specs.map do |column_spec| path = column_spec[:path] if path.length > 1 # get_at_path is defined in a mixin to AR::Base # it allows to access the value of an attribute along a path object.get_at_path(column_spec[:path], :default => nil) else # optimize the most common case object.send(path[0]) end end end -- Michael Schuerig The Fifth Rider of the Apocalypse mailto:michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org is a programmer. http://www.schuerig.de/michael/