rails5 Api-modeのcarriewaveを使っているwebアプリに対して、curlで画像とテキストを一緒にpostしたい

## 背景

現在アプリを作っているのですが、そのサーバー側をrails5 の apimodeで書いている。アプリはinstagramライク。そのため、jsonから、画像や文字列を同時にアップロードする必要があって、四苦八苦していた。

POSTMANを使おうと思ったんだけど、そこまで複雑なPOSTはできないっぽかったのでcurlで戦うことにした。この選択が3時間に渡る悲劇を招くとはこの時はまだ思ってもいなかった。。 と言ってもscratchから3時間なので対した時間じゃないんだけど。。

日本語記事のrails CURLを用いて画像アップロード系は全て読んだけど該当記事は一個もなかった。そもそもこのアプローチが違うのかなぁ。[海外のこの記事](https://www.pluralsight.com/guides/ruby-ruby-on-rails/handling-file-upload-using-ruby-on-rails-5-api)が唯一。感謝。

rails api mode, carrierwave全てdefault設定なので、基本的にcurlと必要部分だけ載せていく。



### 成功したcurlコマンド

画像ファイルとテキストを一緒に送りたいような時は以下のようにすると良い。このままだと叩けないので、改行消してスペース入れて１行にしてください。

```
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でこんな感じ。追加するのは１行のみ。

```posters_controller.rb

  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](https://www.pluralsight.com/guides/ruby-ruby-on-rails/handling-file-upload-using-ruby-on-rails-5-api)

これのみ！日本語ソースは全てよくわからなかった。

## 四苦八苦の過程

ここからは学んだことを記して置くだけなので、動かしたいだけの人は見なくて良いかと。

1. 画像のアップロードには@を付ければいいと勘違い
1. POSTする時の別オプション発見。-F
1. -F "poster[A]=B" -F "poster[C]=D" と羅列しているだけでは、strong_parameterも通過！


### 1. 画像アップロード＝@ (勘違い)


[curl コマンド 使い方メモ](http://qiita.com/yasuhiroki/items/a569d3371a66e365316f)
[curlのオプション勉強したのでまとめ](http://hogem.hatenablog.com/entry/20091122/1258863440)

-d オプションでポストの時のHTTPリクエストのボディに入れるデータを指定できると知る。content-typeがjsonだと指定してあげないと正しく判断されないので、-H オプションでヘッダーに指定してあげたが上手くいかない。

```
curl -H "Content-Type: application/json"
-d '{"poster":
  {
    "title": "twitterの写真です",
    "image": "@twitter.png",
  }
}' http://localhost:3000/posters
```

[このブログ](https://hydrocul.github.io/wiki/commands/curl.html)によると、ファイルを指定する場合は、@マークをつけるんだとか。[これで上手く行っている人が一定数いる](https://blog.hello-world.jp.net/ruby/2289/)んだけど、自分の場合は失敗。

調べてみると、画像ファイルじゃなくて普通に文字列が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' とすると、ハッシュをネストさせることができると気づく！

ここで割とつまずいた。。　日本語ソースではこんな感じでネストする方法を出してくれる記事はなかった。

これで完成形を見つけられる。万歳。