2012年7月5日木曜日

CSVのアップロード

業務系のシステム開発であれば必要となることが多いCSVのアップロード。
それを作ってみました!
環境は
Ruby 1.9.3
Rails 3.2.5
DB mysql2

さぁ作り始めます!
まずはCSVを作成します。
できればテストでも使いたいので"spec"下に"data"フォルダでも作って置いておきましょう!

モデルから作ります。
cattr_accessor :rejected_rows, instance_reader: false
@@rejected_rows = []#エラーメッセージを表示します。

 class << self
    def create_from_csv_data(data)
      self.rejected_rows.clear#エラーメッセージをクリアにします。
      data = NKF::nkf('-S -w', data)
      csv = CSV.new(data)
      inserted = 0#新規登録件数
      updated = 0#更新件数
      transaction do
        csv.each_with_index do |arr, idx|
          next if arr[0] == "登録日時" #CSVの一行目の一つ目が登録日時であってはなりません
          next if arr.all? { |value| value.empty? } #valueが空は飛ばします。
          user = find_by_number(arr[1]) #userのnumberカラムを探します。
          already_number = ( arr[1] != User.find_by_number(arr[1]))#既に登録されているかどうか調べます。
          #登録済みのユーザー番号の場合
          if user#userのユーザー番号が存在した場合
            user.assign_attributes(values_for(arr), without_protection: true)#更新します。
            if user.changed?#変更されている場合はsaveします。
              if user.save
                updated += 1#更新件数に1追加します。
              else
                self.rejected_rows << [ idx, user ]#エラーの場合は行数とエラーメッセージを挿入します。
              end
            end
            #ユーザー番号が空もしくは登録したいユーザー番号が入っている場合
          elsif already_number || arr[1].blank?
            user = self.new(values_for(arr), without_protection: true)#新規登録します。
            if user.save
              inserted += 1#新規登録件数に追加します。
            else
              self.rejected_rows << [ idx, user ]#エラーメッセージを入れます。
            end
          else
          end
        end

        raise ActiveRecord::Rollback if rejected_rows.present?#エラーの場合ロールバックしてエラーメッセージを返します。
      end
      return inserted, updated#無事登録出来た場合は件数を返します。
    end
 
    private
    def values_for(arr)#ユーザーの内容をここに追加します。(privateにしないと長いので)
      {
        number: arr[1],#ユーザー番号
        family_name: arr[2],#名字
        kana_family_name: arr[3],#名字フリガナ
        given_name: arr[4],#名前
        kana_given_name: arr[5],#名前フリガナ
        sex: arr[6],#性別
        birth: arr[7],#誕生日
        email: arr[8],#メール
        zip_code: arr[9],#郵便番号
        prefecture: arr[10],#都道府県
        address1: arr[11],#住所
        address2: arr[12],#住所2
        phone: arr[13]#電話番号
      }
    end
  end
次にルーティングを指定します。
get 'users/import', controller: 'users', action: 'import'
post 'users/csv_import', controller: 'users', action: "csv_import"
次にコントローラーです。
def import
   User.rejected_rows.clear#エラーをクリアします。
end

def csv_import
  if params[:file].present?
    begin#トランザクション開始です。
      inserted, updated = User.create_from_csv_data(params[:file].read)#先ほど作りましたメソッドです。
      if User.rejected_rows.empty?#エラーがないなら?
        flash[:notice] = I18n.t(:notice, scope: [ :users, :csv_import], inserted: inserted, updated: updated)#i18nを使って更新と新規登録の文章をflashで出しています。
        redirect_to action: :import
      else
        render 'import'
      end
    rescue => e
      flash.now.alert = e.message#エラーメッセージを出しています。
      render 'import'
    end
  else
    flash.now.alert = "ファイルを選択して下さい。"
    render 'import'
  end
end
やっとビューにいけました
#フォームです。ここは簡単ですね。
<%= form_tag([:users, :csv_import], :multipart => true) do %>
  <table>
    <th>
      <%= label_tag :file, t('.file') %>
    </th>
    <td>
      <%= file_field_tag(:file) %>
    </td>
  </table>
  <div class="submit">
    <%= submit_tag t('.upload') %>
  </div>
<% end %>
#以下はエラー文
<% if User.rejected_rows.present? %>
<p class="alert">CSVファイルに以下の問題が見つかりました。修正して再びアップロードしてください。</p>

<table class="errors">
    <tr>
      <th>行番号</th>
      <th>エラーの説明</th>
    </tr>
    <% User.rejected_rows.each do |row| %>
      <% idx = row[0]; user = row[1] %>
      <tr>
        <td><%= idx + 1 %></td>
        <td class="error_content">
          <ul>
            <% user.errors.full_messages.each do |msg| %>
              <li><%= msg %></li>
            <% end %>
          </ul>
        </td>
      </tr>
    <% end %>
  </table>
<% end %>

なるべくコントローラーを軽くして動きが分かりやすいようにします。
モデルで書けるものはモデルに書くことをオススメします。

0 件のコメント:

コメントを投稿