Rails4でサイトを構築する – CSV出力機能編

「Rails4でサイトを構築する」シリーズ目次

上記を毎週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 

結果

tech15

複数のモデルから特定の条件で取得したデータをCSVとして出力する

単一のテーブルのレコード全てを一覧出力するよりも、特定条件で取得したデータを必要な項目だけ抽出してCSV出力するケースのほうが多いと思います。
いくつか方法はあると思うのですが、私がよく書くやり方を紹介します。
※もっといい方法がありそうなので、こういうやり方のほうがよさそう等あれば、コメントください。

想定要件

  • 1か月1以内に作成された記事(Articleモデル)を「著者名」、「タイトル」、「作成日」の項目で一覧表示する。

想定ER

tech16

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

結果

tech17

自動でlib以下を読み込むようにする

Nokogiri編参照

まとめ

2つ方法を紹介しましたが、経験上、1つのモデルのデータを出力するような機能を作ったことはほとんどないです。

2つ目の方法はよく利用していて、用途に応じてカスタマイズして使っています。

もっといい方法がありそうな気がするので、よい方法があれば教えていただけると嬉しいです。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ