👌
gitlabリポジトリでのCSVエクスポートのやりかた。
headers_to_value_hash
でCSVヘッダとbodyのデータ変換マッピングを用意する。
def header_to_value_hash
{
'Date' => ->(historical_datum) { historical_datum.recorded_at.utc.to_fs(:csv) },
'Billable User Count' => 'active_user_count'
}
end
CSVBuilder
でheader_to_value_hashを利用して変換する。
def row(object)
attributes.map do |attribute|
data = if attribute.respond_to?(:call)
attribute.call(object)
elsif object.is_a?(Hash)
object[attribute]
else
object.public_send(attribute) # rubocop:disable GitlabSecurity/PublicSend -- Not user input
end
検証用スクリプト
# csv.rb
require 'active_record'
require 'sqlite3'
# ロギング
ActiveRecord::Base.logger = Logger.new($stdout)
# データベース接続の設定
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: 'db/development.sqlite3'
)
# テーブルの作成
ActiveRecord::Schema.define do
drop_table :historical_data, if_exists: true
end
ActiveRecord::Schema.define do
create_table :historical_data do |t|
t.integer :active_user_count
t.datetime :recorded_at
t.timestamps
end
end
class HistoricalData < ActiveRecord::Base
end
require 'csv'
class CsvBuilder
def initialize(records, header_to_value_hash)
@records = records
@header_to_value_hash = header_to_value_hash
end
def render
CSV.generate do |csv|
csv << @header_to_value_hash.keys
@records.each do |record|
csv << @header_to_value_hash.values.map do |attribute|
if attribute.is_a?(Proc)
attribute.call(record)
else
record.send(attribute)
end
end
end
end
end
end
module HistoricalUserData
class CsvService
def initialize(historical_data_relation)
@historical_data_relation = historical_data_relation
end
def generate
csv_builder.render
end
def csv_builder
CsvBuilder.new(@historical_data_relation, header_to_value_hash)
end
def header_to_value_hash
{
'Date' => ->(historical_datum) { historical_datum.recorded_at.utc.to_fs(:csv) },
'Billable User Count' => 'active_user_count'
}
end
end
end
HistoricalData.create(active_user_count: 1, recorded_at: "2024-01-01 00:00:00")
HistoricalData.create(active_user_count: 2, recorded_at: "2024-01-02 00:00:00")
puts HistoricalUserData::CsvService.new(HistoricalData.all).generate
Date,Billable User Count
2024-01-01 00:00:00 UTC,1
2024-01-02 00:00:00 UTC,2
header_to_value_hashを変更することで柔軟にCSVフォーマットを変更することが可能。
たとえばCustomDateを追加する場合は以下。
def header_to_value_hash
{
'Date' => ->(historical_datum) { historical_datum.recorded_at.utc.to_fs(:csv) },
'CustomDate' => ->(historical_datum) { custom_recorded_at(historical_datum) },
'Billable User Count' => 'active_user_count'
}
end
def custom_recorded_at(historical_datum)
historical_datum.recorded_at.utc.strftime('%Y年%m月%d日')
end
Date,CustomDate,Billable User Count
2024-01-01 00:00:00 UTC,2024年01月01日,1
2024-01-02 00:00:00 UTC,2024年01月02日,2
まとめ
gitlab内の内部Gem CSVBuilderを利用してCSV出力処理をしている。
参考
Ruby: CSVでヘッダとボディを同時に定義するやり方も参照
CSVBuilderではpublic_sendも利用してCSV出力処理をしている違いがある。
Discussion