[Rails] csvファイルを使って駅のデータをデータベースに保存する方法

##はじめに
作成しているwebサービスで駅のデータが必要だったので色々調べたところ、データベースに保存できました。せっかくなので記事に残しておきます。

※筆者は初学者です。文章に誤りがあったらコメント等で教えてください。

4/18加筆　lonとlatの型を間違っていたため`:integer`を`:decimal`に変更しました。

##筆者の環境
Rails 5.1.7
ruby 2.5.7

※他に必要な情報ありましたら教えて下さい。

##csvファイルをダウンロードしたサイト
無料のものだったり有料のものだったりありますが、
私は[このページ](https://ekidata.jp/dl/?p=1)でダウンロードしました(この記事と同じようにしたい方は`駅データの2020-03-16`をダウンロードしてください)。
一部の情報は有料ですが、その情報も4,400円ってすごいですね。駅データの仕様書とかも同じサイトにあるので詳しくはそこで見てね。

他のデータ配布サイトも知りたいって方は[この記事](https://qiita.com/kou/items/e0d3fab106e4c52a7f26)にまとめられてます。

######ここでダウンロードしたファイルはdb配下にcsvディレクトリを作成後、ファイル名をstation_data.csvに変えて、アプリケーションからの相対パスが`db/csv/station_data.csv`となるように保存してください。

##Railsでcsvファイルをインポート
###データを入れるデータベースを準備する
まずモデルを作成します。

```terminal
$ rails g model station_data #dataはdatumの複数形なので名前のつけ方が悪いかも...
```

次にこの時に作成されたマイグレーションファイルを駅データの型に合わせて次のように編集します。いらないと思った情報は含めないようにしているので、必要な方はそれらも追加してください。(例： 駅がある場所の郵便番号が欲しいなら`t.string :post`を追加するなど)


```db/migrate/...create_station_data.rb
class CreateStationData < ActiveRecord::Migration[5.1]
  def change
    create_table :station_data do |t|
      t.integer :station_g_cd
      t.string :station_name
      t.integer :line_cd
      t.integer :pref_cd
      t.string :address
      t.decimal :lon
      t.decimal :lat

      t.timestamps
    end
  end
end
```

変更をデータベースに反映させます。

``` 
$ rails db:migrate
```

###csvファイルをインポートするためのコードを書く

`lib/tasks/import.rake`(新しく作成)に次のコードを記入します。

```lib/tasks/import.rake
require 'csv'

# rake import:station_data
namespace :import do
  #このdescはdescribeのdesc
  desc "Import station_data from csv"

  task station_data: :environment do
    path = File.join Rails.root, "db/csv/station_data.csv"
    puts "path: #{path}"
    list = []
    CSV.foreach(path, headers: true) do |row|
      list << {
          station_g_cd: row["station_g_cd"],
          station_name: row["station_name"],
          line_cd: row["line_cd"],
          pref_cd: row["pref_cd"],
          address: row["address"],
          lon: row["lon"],
          lat: row["lat"]
      }
    end
    puts "start to create station data"
    begin
      StationDatum.create!(list) #クラス名注意
      puts "completed!!"
    rescue ActiveModel::UnknownAttributeError => invalid
      puts "raised error : unKnown attribute "
    end
  end
end
```

軽くコードの解説をしますと`CSV.foreach`で`path`にあるcsvファイルから１行ずつ読み込んでいます。読み込まれた行は`list`という空の配列に`{station_g_cd: 1110110, station_name: 函館,....}`のような形のハッシュを追加していっています。

補足: `headers: true`には１行目をヘッダ行として無視する役割があるそうです。

コードを書けたらターミナルで次の一文を出力して終わりです！

```
$ rake import:station_data 
```

これで`db/development.sqlite3`を特定のツールで確認してみると駅のデータが反映されていると思います。お疲れ様でした。

##おまけ(重複したデータの削除)
今回使用した駅データは、同じ場所の駅でも路線が違ったりすると別のレコードで記録されています(どの駅データでもそうなっていると思いますが)。私はユーザーと最寄り駅を紐付けたかったのですが、一人のユーザーが複数の駅データと紐ずくとまずいと思ったので、重複したデータを１つだけ残して他は削除しました。

コードの説明などは参考にしたサイトからお願いします。次の２つのコードできれいに削除できます。

```
$rails c

irb(main):001:0> hash = StationDatum.group(:station_g_cd).having('count(*) >= 2').minimum(:id)

irb(main):001:0> StationDatum.where(station_g_cd: hash.keys).where.not(id: hash.values).destroy_all

```
※ `:station_g_cd`は一つの駅ごとに一つだけ与えられているコードであることを利用しています。
##終わりに
ここまで読んでくださってありがとうございました。

https://qiita.com/kou/items/e0d3fab106e4c52a7f26
このサイトでも紹介されてる「駅すぱあとAPI」とかを理解が進むと使ってみたいです。使って理解できたらまたまとめると思います。
##参考にしたサイト
インポートのために書いたコードの意味の理解で用いました。
http://www.code-magagine.com/?p=8004

全てはこの記事と言っても過言ではない。本当に感謝。
https://qiita.com/yoshito410kam/items/40b675760bd8a1f8e728

重複したデータの削除についてはここ。
https://qiita.com/wakasa51/items/efe2b4b3554a50d18c32