読者です 読者をやめる 読者になる 読者になる

もた日記

くだらないことを真面目にやる

Railsメモ(16) : seed-fuで多対多のリレーションにデータを追加する

Ruby on Rails

wonderwall.hatenablog.com

多対多のリレーションを作成したのでseed-fuでデータを追加してみる(と言っても使い方はこの前と特に変わらない)。
Songモデルに対しては過去にデータを追加しているので、Artistモデルと中間テーブルのSongArtistに追加するデータをWikipediaからスクレイピングする。
この前と同じようにNokogiriを利用するが、アーティスト名はリンクタグで囲まれているので抽出が可能(一部のアーティストにはリンクがないが、数も少ないし比較的無名なアーティストなので今回は無視)である。

require 'nokogiri'
require 'csv'

array = []

CSV.open("seed_songartists.csv", "w") do |csv|
  for year in 1990..2014 do
    f = File.open("#{year}.html")
    doc = Nokogiri::HTML(f)
    f.close()

    doc.css("table.wikitable tr").each_with_index do |node, index|
      next if index == 0
      ranking = node.css("th").inner_text
      title   = node.css("td:eq(1)").inner_text.gsub(/^"/, "").gsub(/"$/, "")
      artists = node.css("td:eq(2)").css("a")
      artists.each do |artist|
        artist = artist.inner_text
        array.push(artist)
        csv << [title, artist, ranking, year]
      end
    end
  end
end

array = array.uniq.sort_by{|a| a.downcase}
CSV.open("seed_artists.csv", "w") do |csv|
  array.each do |a|
    csv << [a]
  end
end

コードを実行するとアルファベット順にソートされたアーティスト一覧のファイルと、

'N Sync
"10,000 Maniacs"
112
2 Chainz
2 Pistols
 …

1行に曲名とアーティスト名が記述されたファイルが出力される(アーティストが複数いる場合はその数だけ記述)。

 …
Bartender,Lady Antebellum,81,2014
La La La,Naughty Boy,82,2014
La La La,Sam Smith,82,2014
Blurred Lines,Robin Thicke,83,2014
Blurred Lines,T.I.,83,2014
Blurred Lines,Pharrell,83,2014
 …

次にdb/fixtures以下にartists.rbと、

require "csv"

SeedFu.quiet = true

CSV.foreach('db/fixtures/seed_artists.csv') do |row|
  Artist.seed(:name) do |s|
    s.name = row[0]
  end
end

songartists.rbを作成する。

require "csv"

SeedFu.quiet = true

CSV.foreach('db/fixtures/seed_songartists.csv') do |row|
  SongArtist.seed(:song_id, :artist_id) do |s|
    s.song_id   = Song.find_by(title: row[0], ranking: row[2], year: row[3]).id
    s.artist_id = Artist.find_by(name: row[1]).id
  end
end

そして下記順番でrake db:seed_fuを実行すると、それぞれデータが追加される。

$ rake db:seed_fu FILTER=artists
$ rake db:seed_fu FILTER=songartists
[1] pry(main)> Artist.all.select(:id, :name)
+------+----------------------------------+
| id   | name                             |
+------+----------------------------------+
| 1    | 'N Sync                          |
| 2    | 10,000 Maniacs                   |
| 3    | 112                              |
| 4    | 2 Chainz                         |
| 5    | 2 Pistols                        |
 …
[1] pry(main)> SongArtist.all.select(:id, :song_id, :artist_id)
+------+---------+-----------+
| id   | song_id | artist_id |
+------+---------+-----------+
| 1    | 1       | 1102      |
| 2    | 2       | 866       |
| 3    | 3       | 915       |
| 4    | 4       | 99        |
| 5    | 5       | 646       |
 …

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails