[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つ目の方法はよく利用していて、用途に応じてカスタマイズして使っています。
もっといい方法がありそうな気がするので、よい方法があれば教えていただけると嬉しいです。