「Rails4でサイトを構築する」シリーズ目次
- Rails環境構築編
- Scaffold利用編
- Bootstrap導入編
- WYSIWYG導入編
- CSV出力機能編
- スクレイピング機能編(nokogiri)
- 非同期処理導入編(delayed_job)
- デプロイ環境構築編(capistrano3)
上記を毎週1つずつ出す予定
今回はDBに保存されているデータをCSV形式で出力する機能を作ってみたいと思います。
対象モデルのデータを全て出力する
今回は、Scaffold利用編で作ったArticleモデルのレコード全てをCSVで出力するプログラムを書いてみます。
想定要件
- 記事のデータ全てを一覧出力する
- articlesテーブルのカラムすべてを出力する
app/models/article.rb
#-*- coding: utf-8 -*-
require 'csv'
class Article < ActiveRecord::Base
# to_csvメソッドを追加する
def self.to_csv
CSV.generate do |csv|
csv << column_names
all.each do |book|
# SJISで出す必要がなければmapはいらない
csv << book.attributes.values_at(*column_names).map{|v| v.to_s.encode('Shift_JIS', undef: :replace, replace: '')}
end
end
end
end
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
# /articles.csvでCSV出力できるようにする。
def index
@articles = Article.all
respond_to do |format|
format.html
format.csv { send_data Article.to_csv }
end
end
end
結果
複数のモデルから特定の条件で取得したデータをCSVとして出力する
単一のテーブルのレコード全てを一覧出力するよりも、特定条件で取得したデータを必要な項目だけ抽出してCSV出力するケースのほうが多いと思います。
いくつか方法はあると思うのですが、私がよく書くやり方を紹介します。
※もっといい方法がありそうなので、こういうやり方のほうがよさそう等あれば、コメントください。
想定要件
- 1か月1以内に作成された記事(Articleモデル)を「著者名」、「タイトル」、「作成日」の項目で一覧表示する。
想定ER
config/routes.rb
resources :article do
get :csv_download
end
app/controllers/article.rb
class ArticlesController < ApplicatoinController
# 追加
def csv_download
articles = Article.where(created_at: Date.today..Date.today.next_month)
csv = ArticleCsvBuilder::ArticleCsvOutput.new
send_data(csv.output(articles), filename: 'article.csv', disposition: 'attachment')
end
end
lib/base_csv_builder.rb
# CSV作成の基となるモジュール
module BaseCsvBuilder
# 出力ベース
class BaseCsvOutput
def initialize(filename=nil)
@base_date = Date.today
@filename = filename
@records = []
@type = 'text/csv'
end
end
# レコードベース
class BaseRecord
def self.header(klass = nil)
self::FIELDS.map { |field| field[1] || klass.human_attribute_name(field.first) }.join(',')
end
def to_csv
('"' << self.class::FIELDS.map { |field| eval("self.#{field.first}").to_s.gsub(/"/, '""') }.join('","') << '"')
end
def self.define_field_attr_accessor
self::FIELDS.each do |field|
(class << self;
self;
end).class_eval { attr_accessor field.first }
attr_accessor field.first
end
end
end
end
lib/article_csv_builder.rb
#-*- coding: utf-8 -*-
module ArticleCsvBuilder
class ArticleRecord < BaseCsvBuilder::BaseRecord
FIELDS = [
[:author_name, "著者名"],
[:title, "タイトル"],
[:created_at, "作成日"]
]
define_field_attr_accessor
def initialize
@author_name = ""
@title = ""
@created_at = ""
end
end
class ArticleCsvOutput < BaseCsvBuilder::BaseCsvOutput
def output(articles)
@records << ArticleRecord.header
articles.each do |article|
record = ArticleRecord.new
record.author_name = article.author.name
record.title = article.title
record.created_at = article.created_at.strftime("%Y/%m/%d %H:%M")
@records << record.to_csv
end
@records.join("\n")
end
end
end
結果
自動でlib以下を読み込むようにする
まとめ
2つ方法を紹介しましたが、経験上、1つのモデルのデータを出力するような機能を作ったことはほとんどないです。
2つ目の方法はよく利用していて、用途に応じてカスタマイズして使っています。
もっといい方法がありそうな気がするので、よい方法があれば教えていただけると嬉しいです。