既存データの流し込み

データ構造が確定したので、古いデータをデータベースに流し込みます。今回のシステムは閲覧専用なので、データの補充はこの作業のみとなります。

旧システムは key-value 形式で書かれたテキストを読み込み、更新された情報に関わるページを自動的に作成するようになっていました。この時、データブロックごとに最終更新日のデータを保持し、最後にページセットを構築した日付と比較する形で更新確認を行っていました。

今回、データを移行するにあたり、元のデータを大幅に作り直すのは面倒です。そこで、テーブルへの流し込みについては同様に処理を行いました。Rails では、初期データの流し込みについて、seed という仕組みがあるので、それを利用します。また、テーブルごとに処理をしたいので、db/seeds の下に各テーブルごとのスクリプトを記述して行きます。これができるのは、db/seeds.rb に以下のように書かれているからです。

Dir.glob(File.join(Rails.root, 'db', 'seeds', '*.rb')) do |file|
  load(file)
end

アルバムを例にすると以下のようなスクリプト(db/seeds/albums.rb)が書いています。例のごとく適度にコメントを入れてみます。

# vim:set fileencoding=utf-8 filetype=ruby:

# key-value テキストは文字列として str に入れておく
# key: value の形式。データの区切りは空行
str = <<"EOF"
keyword: aNewSeason.html
type: Album
j_title: NEW SEASON
singer: iChisatoMoritaka.html
number: 1st
date: 1987/7/25
minutes: 42
seconds: 25
code: CD WPCL-502 second
company: Warner Music Japan
price: 2,400
code2: LP K-12533 first x
company2: Warner-Pioneer
price2: ????
code3: KT LKF-8176 first x
company3: Warner-Pioneer
price3: ????
code4: CD 32XL-216 first x
company4: Warner-Pioneer
price4: 3,200 3,008
jcomment: デビューアルバム
ecomment: Debut Album
songlist: COMMON
MODIFY: 1995/12/13

中略


keyword: aTheSingles.html
type: Album
j_title: ザ・シングルス
e_title: The Singles
singer: iChisatoMoritaka.html
number: 20th
date: 2012/8/8
minutes: 195
seconds: 00
code: CD WPCL-11128/30 first
company: WARNER MUSIC JAPAN
price: 4,280
songlist: Disk1 Disk2 Disk3
MODIFY: 2012/8/8

-
EOF

include MiscMethod

# 一まとまりの key value データを入れておく hash
hash = {}
sort_order = 0;
# 改行で分割し、一行ずつ処理
str.split("\n").each do |line|
  # 空行だったら、一つ分のデータがすべて hash にまとまっているはず
  if line == ""
    キーワードを取得し、キーワードがある時だけ処理
    if k = hash[:keyword]
      sort_order += 1
      # 既存の keyword が k のアルバムを取得、なければ作成
      album = Album.keyword_is(k).first || Album.new(keyword:k)
      # device_type はスペース区切りで複数選択可能。
      album.device_type = 0
      hash[:type].split(/ /).each do |k|
        album.device_type |= Album::AlbumStr2Num[k.intern]
      end
      # 各値はハッシュの対応する key のものを適切に設定
      album.j_title = hash[:j_title]
      album.e_title = hash[:e_title]
      album.number = hash[:number]
      album.date = hash[:date] && Date.parse(hash[:date])
      album.minutes = hash[:minutes]
      album.seconds = hash[:seconds]
      # すでに Individual テーブルに singer が登録されていれば関連を登録
      album.singer = hash[:singer] && Individual.keyword_is(hash[:singer]).first
      # year のキーか、アルバムの日付から年の数値を取得
      year_num = hash[:year] || (album.date && album.date.year)
      if year_num
        # 年の数値が取得できていれば、数値から年のオブジェクトを取得もしくは作成
        year = Year.year_from_year_or_create(year_num)
        # アルバムに年の関連を設定
        album.year = year
      end
      album.sort_order = sort_order
      # アルバムの media(わかりにくいけど medium の複数形)に取得もしくは作成したメディアを登録
      album.media << Medium.medium_from_data(hash, :code, :company, :price, 0) if hash[:code]
      album.media << Medium.medium_from_data(hash, :code2, :company2, :price2, 1) if hash[:code2]
      album.media << Medium.medium_from_data(hash, :code3, :company3, :price3, 2) if hash[:code3]
      album.media << Medium.medium_from_data(hash, :code4, :company4, :price4, 3) if hash[:code4]
      album.media << Medium.medium_from_data(hash, :code5, :company5, :price5, 4) if hash[:code5]
      album.jcomment = hash[:jcomment]
      album.ecomment = hash[:ecomment]
      # アルバムを保存する
      if album.save
        # 保存に成功し、データが更新されていればメッセージを表示
        # Rails では save されても、オブジェクトの内容が変わってなければ updated_at は更新されない
        print "update album: #{k}\n" if Time.now - album.updated_at < 10
      else
        # データベースへの登録が失敗した時にはエラーメッセージを表示する
        p album
        print "save error!\n"
      end
      # アルバムに関連づけられた song_lists を取得し、keyword を key にした hash に登録
      sl_hash = array_to_hash(album.song_lists) { |sl| [ sl.keyword, sl ] }
      if hash[:songlist]
        # songlist key にはスペース区切りで複数のキーワードが記述されている
        hash[:songlist].split(' ').each do |sl|
          # sl_hash に登録されていない場合には、アルバムに関連づけられた song_list を新規に作成する
          unless sl_hash[sl]
            song_list = album.song_lists.create(keyword:sl, sort_order:sl_hash.keys.count + 1)
            sl_hash[sl] = song_list
            print "create song_list: #{sl}\n"
          end
        end
      end
      
      hash = {}
    end
  else
    # 空行でない場合には、登録されたキーの値のみを hash に登録する
    k, v = line.split(": ")
    ks = k.intern
    case ks
    when :keyword, :type, :j_title, :e_title, :number, :date, :year, :singer, :minutes, :seconds,
      :code, :code2, :code3, :code4, :code5,
      :company, :company2, :company3, :company4, :company5,
      :price, :price2, :price3, :price4, :price5,
      :jcomment, :ecomment, :songlist
      hash[ks] = v
    end
  end
end

基本的にはstr の中身を追記し、rake db:seed をすれば追加分だけがデータベースに流し込まれることになります。ただし、依存関係があるので数回行わなければならないことがあります(更新メッセージが表示されなくなるまで)。更新はそう頻繁ではないので許されるかと思っています。