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になっている。)
<%= 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の定義は以下のようになっている。
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"=>#@tempfile=#,
@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 発見.
Action Dispatchは webリクエスト/ユーザーによってルーティングされた情報を解析する。そして、HTTPと関連する MIME-type のようなネゴシエーションを行い、POST/PUTのbodyをデコードする。HTTPのキャッシュを扱うロジックはcookiesやsessionである。だそうだ。
詳細は全く記述されていなかったため、ソースコードを読んでみる。
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データのみを与えて上げると、バグは修正されました。