web-dev-qa-db-fra.com

Comment convertir un tableau de modèles ActiveRecord au format CSV?

J'ai un tableau de modèles ActiveRecord que je souhaite convertir en CSV. J'ai essayé de rechercher des pierres précieuses comme FasterCSV, mais elles semblent fonctionner uniquement avec des chaînes et des tableaux, pas avec des modèles ActiveRecord.

En bref, je veux convertir:

user1 = User.first
user2 = User.last
a = [user1, user2]

À:

   id,username,bio,email
    1,user1,user 1 bio,user1 email
    1,user2,user 2 bio,user2 email

Existe-t-il un moyen simple de faire cela?

36
Henley Chiu

Les éléments suivants écrivent les attributs de tous les utilisateurs dans un fichier:

CSV.open("path/to/file.csv", "wb") do |csv|
  csv << User.attribute_names
  User.find_each do |user|
    csv << user.attributes.values
  end
end

De même, vous pouvez créer une chaîne CSV:

csv_string = CSV.generate do |csv|
  csv << User.attribute_names
  User.find_each do |user|
    csv << user.attributes.values
  end
end
90
rudolph9

La réponse de @ rudolph9 est vraiment géniale. Je veux juste laisser un mot aux personnes qui doivent effectuer cette tâche périodiquement: le faire comme une tâche de commission serait une bonne idée!

lib/tasks/users_to_csv.rake

# usage:
# rake csv:users:all => export all users to ./user.csv
# rake csv:users:range start=1757 offset=1957 => export users whose id are between 1757 and 1957
# rake csv:users:last number=3   => export last 3 users
require 'csv' # according to your settings, you may or may not need this line

namespace :csv do
  namespace :users do
    desc "export all users to a csv file"
    task :all => :environment do
      export_to_csv User.all
    end

    desc "export users whose id are within a range to a csv file"
    task :range => :environment do |task, args|
      export_to_csv User.where("id >= ? and id < ?", ENV['start'], ENV['offset'])
    end

    desc "export last #number users to a csv file"
    task :last => :environment do |task, arg|
      export_to_csv User.last(ENV['number'].to_i)
    end

    def export_to_csv(users)
      CSV.open("./user.csv", "wb") do |csv|
        csv << User.attribute_names
        users.each do |user|
          csv << user.attributes.values
        end
      end
    end
  end
end
11
Brian

Si vous avez besoin de quelque chose de rapide et de sale, pas tant pour la production que pour récupérer des données pour un utilisateur non technique, vous pouvez le coller dans la console:

require 'csv'
class ActiveRecord::Relation
  def to_csv
    ::CSV.generate do |csv|
      csv << self.model.attribute_names
      self.each do |record|
        csv << record.attributes.values
      end
    end
  end
end

Alors faites: User.select(:id,:name).all.to_csv

Si vous deviez passer à la production, je transformerais probablement cela en décorateur autour d'ActiveRecord :: Relation et plus précisément en veillant à l'ordre des champs/attributs.

2
Mario Olivio Flores

Encore une autre réponse similaire, mais voici ce que je fais habituellement.

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def self.to_csv
    CSV.generate do |csv|
      csv << column_names
      all.find_each do |model|
        csv << model.attributes.values_at(*column_names)
      end
    end
  end
end

Au lieu de pirater le module existant, je mettrais généralement ce code dans la classe ApplicationRecord, la classe de base de tous les modèles (généralement).

Si des détails supplémentaires sont nécessaires, j'ajouterais un paramètre nommé à la méthode to_csv et gérerais ces fonctionnalités autant que possible dans cette classe.

De cette façon, la méthode to_csv sera disponible à la fois pour Model et sa relation. Par exemple.

User.where(role: :customer).to_csv
# => gets the csv string of user whose role is :customer
1
Yuki Inoue

avec Julia_builder vous pouvez configurer assez facilement une exportation CSV.

class UserCsv < Julia::Builder
  # specify column's header and value
  column 'Birthday', :dob
  # header equals 'Birthday' and the value will be on `user.dbo`

  # when header and value are the same, no need to duplicate it.
  column :name
  # header equals 'name', value will be `user.name`

  # when you need to do some extra work on the value you can pass a proc.
  column 'Full name', -> { "#{ name.capitalize } #{ last_name.capitalize }" }

  # or you can pass a block
  column 'Type' do |user|
    user.class.name
  end
end

et alors

users = User.all
UserCsv.build(users)
1
Steven Barragán

On peut aussi utiliser le moteur SQL pour cela. Par exemple. pour sqlite3:

cat << EOF > lib/tasks/export-submissions.sql
.mode      csv
.separator ',' "\n"
.header    on


.once "submissions.csv"

select
  *
from submissions
;
EOF

sqlite3 -init lib/tasks/export-submissions.sql db/development.sqlite3 .exit

Si vous êtes sur CentOS 7, il est livré avec sqlite sorti en 2013. Cette version ne connaissait pas encore separator et once. Vous devrez donc peut-être télécharger le dernier binaire à partir du site Web: https://sqlite.org/download.html installez-le localement et utilisez le chemin complet de l'installation locale:

~/.local/bin/sqlite3 -init lib/tasks/export-submissions.sql db/development.sqlite3 .exit
0
Adobe