ActionDispatch::Http::UploadedFileを読む (デバッグメモ)

Ruby on Rail の勉強用に、画像アップロードのwebアプリを作りました。
その際に詰まった部分 ActionDispatch::Http::UploadedFile についてメモ。

### 結論
ActionDispatch::Http::UploadedFile
Httpにて、画像データをuploadする際のライブラリ。

画像をwebとやりとりする形式
@original_filename : fileの名前 (UTF-8に強制的にエンコードされる)
@header: ヘッダー (height, width, サンプル精度など) 
@tempfile : 画像データのリファレンス? 
@content_type: 画像の形式 (jpgなど)

ruby on railsのサーバーで画像をbinaryデータとして保存する場合、
@tempfile.read メソッドを使ってバイナリデータを取得する必要がある。

### 画像データアップロードページの作成
手順1. scaffoldで簡単に土台を作成
rails generate scaffold post comment:string image:binary

手順2. 画像ファイルをアップロード出来るようにviewを書き換え (デフォルトではtext_fieldになっている。)

```ruby:app/views/posts/_form.html.erb
<%= f.file_field :picture_file %>
```

### アップロードされるデータの確認
とりあえずここまでで、どのようなデータがアップロードされているかを確認してみる。適当な画像をアップロードしてみると、次のようなエラー。

>  # POST /posts.json
>  def create
>    @post = Post.new(post_params)　←エラー部分

>undefined method `encoding' for #
><ActionDispatch::Http::UploadedFile:0x007f9f74e26f38>

post_paramsの定義は以下のようになっている。

```ruby:posts_controller.rb
    def post_params
      params.require(:post).permit(:comment,:image, :delete_time,:longitude, :latitude,:user_id)
    end
```

また、送られているparameterも以下のようになっている。

>{"utf8"=>"✓",
>"authenticity_token"=>"Nqdh3xtNz+Xn7C1u/HwE1dMvSpDOSXHb89M5W4m2xPQ=",
> "post"=>{"comment"=>"debug_test",
> "image"=>#<ActionDispatch::Http::UploadedFile:0x007f9f74e26f38 >@tempfile=#<Tempfile:/var/folders/yy/k_9r50m10v11nhs4_ymy7jqm0000gn/T/RackMultipart20130520-783-ann4je>,
> @original_filename="350px-Japanese-Akita-Inu.jpg",
> @content_type="image/jpeg",
> @headers="Content-Disposition: form-data; name=\"post[image]\"; >filename=\"350px-Japanese-Akita-Inu.jpg\"\r\nContent-Type: image/jpeg\r\n">},
> "commit"=>"Create Post"} ` 

jpg画像の読み込みフォーマットに問題がありそう。
そこでまず、ActionDispatch::Http::UploadedFileについて検索。
githubにて read me 発見.

https://github.com/rails/rails/blob/master/actionpack/README.rdoc

Action Dispatchは webリクエスト/ユーザーによってルーティングされた情報を解析する。そして、HTTPと関連する MIME-type のようなネゴシエーションを行い、POST/PUTのbodyをデコードする。HTTPのキャッシュを扱うロジックはcookiesやsessionである。だそうだ。

詳細は全く記述されていなかったため、ソースコードを読んでみる。

```ruby:upload.rb
module ActionDispatch
  module Http
    class UploadedFile
      attr_accessor :original_filename, :content_type, :tempfile, :headers

      def initialize(hash) #
        @original_filename = encode_filename(hash[:filename]) 
        @content_type      = hash[:type]     #画像の保存形式
        @headers           = hash[:head]       # header 情報
        @tempfile          = hash[:tempfile]  # 画像データのリファレンス?
        raise(ArgumentError, ':tempfile is required') unless @tempfile
      end

      def read(*args)
        @tempfile.read(*args)
      end
  
      # Delegate these methods to the tempfile.
      # つまり、tempfileについて、size, path, open, などなどが使えるということ
      [:open, :path, :rewind, :size].each do |method|
        class_eval "def #{method}; @tempfile.#{method}; end"
      end

      private
      #画像のファイルネームを取り込む部分
      #強制的にUTF-8に変換
      def encode_filename(filename) 
        # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8
        if "ruby".encoding_aware? && filename
          filename.force_encoding("UTF-8").encode!
        else
          filename
        end
      end
    end

    module Upload
      # Convert nested Hash to HashWithIndifferentAccess and replace
      # file upload hash with UploadedFile objects
      def normalize_parameters(value)
        if Hash === value && value.has_key?(:tempfile)
          UploadedFile.new(value)
        else
          super
        end
      end
      private :normalize_parameters
    end
  end
end
```

おそらく、imageが binaryデータを求めているのに、jpgファイル全てを入力したことが
最初のバグの原因でしょう。imageにbinaryデータのみを与えて上げると、バグは修正されました。