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

初めてのRailsアプリ(その3)

railsubject_03

初めてのRailsアプリ(その3)

railsのモデルって本当にモデル?ただのORマッパじゃね?

はじめに

今回はファイルをアップロードを題材に、共通モジュールとトランザクションに関して勉強する

4. ファイルアップロード

ファイルを適当なところにアップします。ポイントは、以下の2点。

  • ファイルを保存する機能をくくりだし、共有モジュール化する
  • ファイル自体はファイルシステムとして保存し、ファイルの情報はDBに保存する

ER図

今回はここ

f:id:naotooncajon:20130723002601p:image

変更点

前回より以下の点を変更する。

モデル

変更なし

ビュー

viewヘルパをfile_fieldに変更する

f:id:naotooncajon:20130723002611p:image

モジュール

概要

ファイルを保存する共有モジュールを作成する。提供するメソッドは以下

  • check_file
    • ファイルの拡張子とサイズをチェックする
    • チェック条件はデフォルトを用意しているが、変更も可能
    • NGの場合は例外を発生させる
  • upload_file
    • ファイルを保存する
    • 保存先のディレクトリを指定できる
    • 指定したディレクトリがない場合は作成する
実装
#ファイルアップロードの部品を提供する共通モジュール
module UploadModule
    # 定数
    PATH = "/Users/7010oncajon/Documents/workspace/railsubject/public/files/"
    PERMS = [".md", "txt"]
    LIMIT = 200.megabyte

    # エラーメッセージ
    PERMS_ERROR = "ファイルの種別が想定外です。"
    LIMIT_ERROR = "ファイルのサイズが想定外です。"
    IO_ERROR = "ファイルの書き込みに失敗しました。"

    # 入力されたファイルのチェックを行う
    def check_file(file, perms=PERMS, limit=LIMIT)
        # ファイル情報の取得
        file_name = file.original_filename
        file_size = file.size

        # ファイルのチェック
        if !perms.include?(File.extname(file_name).downcase)
            raise PERMS_ERROR
        elsif file_size > limit
            raise LIMIT_ERROR
        end
    end

    # ファイルを保存する
    def upload_file(file, dir)
        begin
            #
            dir = PATH + dir
            path = dir + file.original_filename 

            # サブディレクトリの作成
            FileUtils.mkdir_p(dir)

            # ファイルの書込み
            File.open(path, "wb"){|f| f.write(file.read)}
        rescue => e
            Rails.logger.error(e.message)
            raise IO_ERROR
        end
    end

    module_function :upload_file
    module_function :check_file
end

配置

このモジュールは"(アプリケーションルート)/lib/upload_module.rb"に保管し、このディレクトリをautoloadで切るようにしておく。

f:id:naotooncajon:20130723002608p:image

コントローラ

ファイルをrequireして、モジュールをincludeする。いわゆるひとつのMix-in。

f:id:naotooncajon:20130723002557p:image

upload_controllerの新規作成時(create)の処置を以下に示す。(結構大幅に変わったので、差分ではなく全文を表示)

def create
    @upload = @subject.uploads.build(params[:upload])

    # ファイル取得
    file = @upload.path

    # トランザクション処理(ファイルの保存とDB更新を同一のトランザクションで行う)
    Upload.transaction do
      # ファイルのチェック
      check_file(file)

      # idを取得するために一旦モデルを保存(もっといい方法ありませんか?)
      @upload.save!

      # ファイルの保存先pathを作成し、モデルを保存
      dir = @subject.id.to_s + "/" + @upload.id.to_s + "/"
      @upload.path = dir + file.original_filename
      @upload.save!

      # ファイルを保存
      upload_file(file, dir)
    end
      # 正常終了の処理
      redirect_to [@subject, @upload], :notice => 'Upload was successfully created.'
    rescue => e
      # 例外発生時の処理
      redirect_to new_subject_upload_url(@subject, @upload), :notice => e.message
  end

完成形

f:id:naotooncajon:20130723002605p:image

f:id:naotooncajon:20130723002553p:image

なお、ファイルチェックやファイル保存時に例外が発生した場合は、DBへのinsertもロールバックされているはずです。

終わりに

次にやりたいことは以下

  • groupでsubjectを選択する際のUI高度化
  • mailでメールを送る
  • has_many thoughアソシエーションをさんすくみでやる