Как преобразовать массив моделей ActiveRecord в CSV?

Я получил массив моделей ActiveRecord, которые я хочу преобразовать в CSV. Я пытался исследовать драгоценные камни, такие как FasterCSV, но, похоже, они работают только со строками и массивами, а не с моделями ActiveRecord.

Короче хочу конвертировать:

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

TO:

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

Есть ли простой способ Rails сделать это?

9 ответов

Решение

Следующее запишет атрибуты всех пользователей в файл:

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

Точно так же вы можете создать строку CSV:

csv_string = CSV.generate do |csv|
  csv << User.attribute_names
  User.all.each do |user|
    csv << user.attributes.values
  end
end

Ответ @rudolph9 действительно потрясающий. Я просто хочу оставить записку для людей, которым нужно периодически выполнять эту задачу: было бы неплохо сделать это заданием с граблями!

Библиотека / Задачи /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

Это может быть от первоначального вопроса, но решить проблему. Если вы планируете сделать так, чтобы все или некоторые из ваших моделей Active Record могли быть преобразованы в CSV, вы можете использовать функцию ActiveRecord. Пример показан ниже

module Csvable
  extend ActiveSupport::Concern 

  class_methods do
    def to_csv(*attributes)
      CSV.generate(headers: true) do |csv| 
        csv << attributes 

        all.each do |record| 
          csv << attributes.map { |attr| record.send(attr)
        end 
      end
    end
  end
end

Предоставленный атрибут будет использоваться в качестве заголовка для CSV, и ожидается, что этот атрибут соответствует имени метода во включенном классе. Затем вы можете включить его в любой класс ActiveRecord по вашему выбору, в данном случае класс User

class User 
  include Csvable 

end

использование

User.where(id: [1, 2, 4]).to_csv(:id, :name, :age)

Примечание: это работает только для отношения ActiveRecord, а не для массивов

Если вам нужно что-то быстрое и грязное, не столько для производства, сколько просто для сбора данных для нетехнического пользователя, вы можете вставить это в консоль:

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

Затем сделайте: User.select(:id,:name).all.to_csv

Если бы вы собирались в производство, я бы, вероятно, превратил это в декоратор вокруг ActiveRecord::Relation и, более точно, обеспечил бы порядок ваших полей / атрибутов.

С помощью julia_builder вы можете легко настроить экспорт в 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

а потом

users = User.all
UserCsv.build(users)

Еще один аналогичный ответ, но вот что я обычно делаю.

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

Вместо того, чтобы взламывать существующий модуль, я обычно помещал этот код в ApplicationRecord класс, базовый класс всех моделей (обычно).

Если потребуется дальнейшая разработка, я бы добавил именованный параметр в to_csv метод, и обрабатывать эти функции как можно больше в этом классе.

Таким образом, to_csv метод будет доступен как для модели, так и для ее связи. Например

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

У меня была такая же проблема, и я объединил пару этих ответов, чтобы я мог вызвать to_csv для модели или отношения, затем ввести имя файла и создать файл csv.

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

  def self.to_csv
    require 'csv'

    p "What is the name of your file? (don't forget .csv at the end)"

    file_name = gets.chomp

    CSV.open("#{file_name}", "wb") do |csv|
      csv << column_names
      all.find_each do |model|
        csv << model.attributes.values_at(*column_names)
      end
    end
  end
end

Теперь с консоли можно позвонить .to_csv для любой модели, любого запроса к базе данных или отношения activerecord.

Я сделал метод в ApplicationRecord, который позволяет вам сделать csv-укус из любой модели, он также использует только заголовки из выбора.

      class ApplicationRecord < ActiveRecord::Base
  class << self
    def to_csv_string
      CSV.generate do |csv|
        csv << self.first.as_json.keys
        self.find_each do |selfie|
          csv << selfie.attributes.values
        end
      end
    end
  end
end
# User.select(:id, :name, :email).to_csv_string
# => "id,fname,lname\n1,Chet,Corey\n"

Надеюсь, это полезно

Для этого также можно использовать движок sql. Например, для 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

Если вы используете CentOS 7 - он поставляется с sqlite, выпущенным в 2013 году. Та версия не знала separator а также once еще. Поэтому вам может потребоваться загрузить последний двоичный файл с веб-сайта: https://sqlite.org/download.html установить его локально и использовать полный путь к локальной установке:

~/.local/bin/sqlite3 -init lib/tasks/export-submissions.sql db/development.sqlite3 .exit
Другие вопросы по тегам