2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rrailsにcsvファイルを簡単に読み込めるgemを作る

Posted at

背景

とあるサービスをrailsで作っていたが、初期データが多くてseedにすべて書くのは大変&エンジニア以外にも手伝ってもらいたかったので、csvファイルでseedデータを作ることにしました。
csvファイルをseed.rbで読み込む方法はすでに存在しますが、csvファイル1つずつ読み込んだり、一括で読み込むものもvalidationを無視するものが多いです。かといって、validationを守りつつcsvファイルを読み込むためには、読み込む順番を制御しなければなりません。
コードを書いてると60行ぐらいになり、rubyのメタプログラミング的手法(カッコイイ)も使ったので、練習も兼ねてgem化することにしました。

成果物

https://github.com/aitaro/csv_seeder
(よかったら使ってね)

他のcsv読み込み系qiita

無限にあったので検索結果から探してください。

準備

RubyGemsの作り方を参考に初期設定をしていきます。
ちなみにgem名はcsv_seederにしました。

設計

まず、想定するcsvファイルは下のようなものです。(csvにidまで含めているのは、relationで指定することができるからです。)

posts.csv
id,name,status
1,プログラミングの書き方,release
2,rails基礎,draft
comments.csv
id,post_id,body,rate
1,1,よく分かった,5
2,1,少し分かった,4
3,2,わからなかった,3

基本的な設計方針として、csvファイルを順番に読み込みつつ、依存関係(belongs_to等)によりvalidationに引っかかるcsvファイルを後回しにします。

例えば以下のようなモデルのとき、

class Post
  has_many :comments
end

class Comment
  belongs_to :post
end

先にCommentを読み込むと、指定したPostがないよと怒られます。なのでPostを先に読み込んでからComnmentを読まないいけません。

このように"validationに引っかかるもの"のほとんどはbelongs_toによるものです。

実装

基本ループの実装です。

def initialize(folder_path, orders)
  @folder_path = folder_path
  @orders = orders
  @dirs = Dir.glob(folder_path + "/*").shuffle 
end

def dirs_loop!
  while !@dirs.empty?
    if invalid_order?
      postpone!
      next
    end
    if has_relations?
      postpone!
      next
    end
    /db\/seeds\/(\w*)\.csv/ =~ @dirs[0]
    model = $1.classify
    CSV.foreach(@dirs[0], headers: true) do |row|
      Object.const_get(model).create!(**row.to_hash)
    rescue => e
      p "Error! During #{model}"
      p e
    end
    p "#{model} saved!"
    @dirs.shift
  end    
end

@dirsにcsvのpath一覧が入っています。これからinvalid_order?has_relations?等の条件を適合したものだけをcreateしています。
CSV.foreachによりcsvファイルの読み込み、Object.const_get(model)により動的にクラスを呼び出しています。

has_relations?の実装です。親クラスがまだ読み込まれていない場合は後回しにします。

def has_relations?
  CSV.open(@dirs[0], &:readline).any? do |header| 
    id_list = @dirs.map do |dir|
      dir_to_plural(dir).singularize + '_id'
    end
    id_list.include? header
  end
end

invalid_order?の実装です。@ordersはhas_relations?で補足できない依存関係を、ユーザーからのインプットパラメータとして受け取ったものです。@ordersに禁止するものを後回しにしています。

def invalid_order?
  @orders.each do |order|
    index = order.index(dir_to_plural(@dirs[0]).to_sym)
    next if !index || index == 0
    return true if @dirs.any?{|dir| order[0..(index-1)].map(&:to_s).include? dir_to_plural(dir)}
  end
  false
end

所管

簡単なgemですが、公開するとやりきった感があってたのしいです。なにか提案等あればこの記事のコメントかissueかに書いてください。(反応できるかわからないですが、、)

TODO

個人的メモです。ここまで書いて力尽きました。以下のことはまた今度やります。

  • gem使い方の説明を書く
  • 依存関係をはっきりさせる。
  • gemのtestコードを書く
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?