apollo + rails(graphqlサーバー)でファイルをアップロードするmutationを作る方法

# 目標
```rb:mutation.rb

resolve ->(obj, args, ctx) {
  # argsとしてアップロードファイルをそのまま受け取りたい
  YourModel.find(args[:id]).update avatar: args[:file]
}

```

# 問題

graphqlを使ってファイルアップロードをする時、どのようにmutateを書けば良いのか分からない

# 解決方法

1. https://github.com/jaydenseric/apollo-upload-client をクライアント側で使う
2. https://github.com/rmosolgo/graphql-ruby を使う
3. `GraphqlController`等での`execute`([参照](https://rmosolgo.github.io/graphql-ruby/queries/executing_queries))に渡すパラーメーターを準備する
4. Fileスカラーを作る
5. Fileスカラーを`input_field`とするMutationを作る
6. クライアント側のアップロードをするmutateを書く

## 1 apollo-upload-client

yarn add apollo-upload-client

`import {createNetworkInterface} from 'apollo-upload-client'`
で、通常の`createNetworkInterface `と入れ替える


## 2 graphql-ruby

```rb:Gemfile
gem "graphql"
```


## 3 GraphqlController等でのexecuteに渡すパラメーター

https://rmosolgo.github.io/graphql-ruby/schema/generators
`rails generate graphql:install`
で作られた状態のままの`GraphqlController`では`apollo-upload-client`から送られるデータ形式と違う

ファイルアップロード時は`operations`と言うパラメーターが増えているのでこれを使う

```rb:graphql_controller.rb
  def execute
    context = {
      # Query context goes here, for example:
      # current_user: current_user,
    }

    if params[:operations].present?
      # この部分で、必要となる query と variables を設定する
      operations = ensure_hash(params[:operations])
      variables = {
        "input" => operations[:variables].
                    merge({"file" => params["variables.file"]})
      }
      query     = operations[:query]
    else
      variables = ensure_hash(params[:variables])
      query     = params[:query]
    end

    result = RailsAppNameSchema.execute(query, variables: variables, context: context)
    render json: result
  end

```

## 4 Fileスカラー
```rb:app/graphql/types/scalars/file_type.rb

# autoload_pathsを使っても良いけど、フォルダとそれに対応するようにmoduleを
# 作っていけばautoload_pathsを設定しなくても自動に読み込んでくれる
# https://railsguides.jp/autoloading_and_reloading_constants.html
module Types
  module Scalars
    FileType = GraphQL::ScalarType.define do
      name "File"
      description "ActionDispatch::Http::UploadedFile"

      coerce_input ->(action_dispatch_uploaded_file, ctx) {
        # graphql_controller.rb で渡した params["variables.file"] は
        # Railsで普通の ActionDispatch::Http::UploadedFile
        # http://api.rubyonrails.org/classes/ActionDispatch/Http/UploadedFile.html
        action_dispatch_uploaded_file
      }
    end
  end
end
```

## 5 Mutation

```rb:app/graphql/mutations/your_model/edit.rb

# ここも同じでautoload_pathsを使わなくても、フォルダとmoduleを対応させて作っている
module Mutations
  module YourModel
    # ここではEditとした
    Edit = GraphQL::Relay::Mutation.define do
      name "YourModelEdit"

      input_field :id,        !types.ID
                              # moduleで階層を作っているので
                              # autoload_pathsを使ってるのであれば
                              # 参照できる定数名で指定
      input_field :file,      Types::Scalars::FileType
      return_field :results,  types.Boolean

      resolve ->(obj, args, ctx) {
        YourModel.find(args[:id]).update avatar: args[:file]

        {results: true}
      }
    end
  end
end
```

## 6 アップロードをするmutate

```js
// ここで言うtargetは<input type="file">の実体
// <input type="file" ref={ (input) => this.fileInput } />
// などで参照できるようにしておいて
// this.fileInput.files[0] を渡すなどする
this.props.mutate({
    variables: {
      id,
      avatar: target.files[0]
    }
  }).then(({data}) => {
    console.log(data)
  })
}     
```