背景
現在アプリを作っているのですが、そのサーバー側をrails5 の apimodeで書いている。アプリはinstagramライク。そのため、jsonから、画像や文字列を同時にアップロードする必要があって、四苦八苦していた。
POSTMANを使おうと思ったんだけど、そこまで複雑なPOSTはできないっぽかったのでcurlで戦うことにした。この選択が3時間に渡る悲劇を招くとはこの時はまだ思ってもいなかった。。 と言ってもscratchから3時間なので対した時間じゃないんだけど。。
日本語記事のrails CURLを用いて画像アップロード系は全て読んだけど該当記事は一個もなかった。そもそもこのアプローチが違うのかなぁ。海外のこの記事が唯一。感謝。
rails api mode, carrierwave全てdefault設定なので、基本的にcurlと必要部分だけ載せていく。
成功したcurlコマンド
画像ファイルとテキストを一緒に送りたいような時は以下のようにすると良い。このままだと叩けないので、改行消してスペース入れて1行にしてください。
curl localhost:3000/posters
-F "poster[title]=twitterの写真"
-F "poster[image]=@twitter.png;type=image/png"
これを送ったときにサーバーが受け取るパラメータはこんな感じ。それっぽい(というか正しい)
{
"poster" => {
"title"=>"twitterの写真",
"image"=>#<ActionDispatch::Http::UploadedFile:0x007fb1ce00aaa0
@tempfile=#<Tempfile:/var/folders/yq/260398vn3bxb0xxq3vk071mm0000gn/T/RackMultipart20161229-11537-7cw56y.png>,
@original_filename="twitter.png",
@content_type="image/png",
@headers="Content-Disposition: form-data; name=\"poster[image]\"; filename=\"twitter.png\"\r\nContent-Type: image/png\r\n"
>
}
}
controller側の処理はほぼscaffoldでこんな感じ。追加するのは1行のみ。
def create
@poster = Poster.new(poster_params)
@poster.image = poster_params[:image] # 追加した部分
if @poster.save
render json: @poster, status: :created, location: @poster
else
render json: @poster.errors, status: :unprocessable_entity
end
end
private
def poster_params
params.fetch(:poster, {}).permit(
:title,
:image,
)
end
おすすめ参考記事
Handling file upload using Ruby on Rails 5 API
これのみ!日本語ソースは全てよくわからなかった。
四苦八苦の過程
ここからは学んだことを記して置くだけなので、動かしたいだけの人は見なくて良いかと。
- 画像のアップロードには@を付ければいいと勘違い
- POSTする時の別オプション発見。-F
- -F "poster[A]=B" -F "poster[C]=D" と羅列しているだけでは、strong_parameterも通過!
1. 画像アップロード=@ (勘違い)
curl コマンド 使い方メモ
curlのオプション勉強したのでまとめ
-d オプションでポストの時のHTTPリクエストのボディに入れるデータを指定できると知る。content-typeがjsonだと指定してあげないと正しく判断されないので、-H オプションでヘッダーに指定してあげたが上手くいかない。
curl -H "Content-Type: application/json"
-d '{"poster":
{
"title": "twitterの写真です",
"image": "@twitter.png",
}
}' http://localhost:3000/posters
このブログによると、ファイルを指定する場合は、@マークをつけるんだとか。これで上手く行っている人が一定数いるんだけど、自分の場合は失敗。
調べてみると、画像ファイルじゃなくて普通に文字列がPOSTされてきてた。その結果、carrierwaveがちゃんと保存できないので、
- strong_parameterは通過
- carrierwaveが画像じゃないと認識して、NULLを保存しようとする(ここで画像じゃないよって、エラー吐いてほしい怒)
理由はわからないけどボツ。エラーを吐くべきだと思うんだけど、直してPR送ろっかな。
2. -Fの発見
別のPOSTの仕方を発見。dオプションだとどうしてもstringになっちゃうことから、こっちで試すことに。
curl http://localhost:3000/posters
-H "Content-Type: multipart/form-data"
-F "title=twitterの写真です",
-F "image=@twitter.png"
渡ってくるデータは以下のとおり。
{
"title"=>"twitterの写真です,",
"image"=>
#<ActionDispatch::Http::UploadedFile:0x007fb1cadf5f88
@tempfile=#<Tempfile:/var/folders/yq/260398vn3bxb0xxq3vk071mm0000gn/T/RackMultipart20161229-11537-r0qaxz.png>,
@original_filename="twitter.png",
@content_type="application/octet-stream",
@headers="Content-Disposition: form-data; name=\"image\"; filename=\"twitter.png\"\r\nContent-Type: application/octet-stream\r\n">
}
惜しい!! これが {poster: {以下略}}
ってくれば、、、!!
3. -F 'poster[aaa]=bbb' とすると、ハッシュをネストさせることができると気づく!
ここで割とつまずいた。。 日本語ソースではこんな感じでネストする方法を出してくれる記事はなかった。
これで完成形を見つけられる。万歳。