目標
mutation.rb
resolve ->(obj, args, ctx) {
# argsとしてアップロードファイルをそのまま受け取りたい
YourModel.find(args[:id]).update avatar: args[:file]
}
問題
graphqlを使ってファイルアップロードをする時、どのようにmutateを書けば良いのか分からない
解決方法
- https://github.com/jaydenseric/apollo-upload-client をクライアント側で使う
- https://github.com/rmosolgo/graphql-ruby を使う
-
GraphqlController
等でのexecute
(参照)に渡すパラーメーターを準備する - Fileスカラーを作る
- Fileスカラーを
input_field
とするMutationを作る - クライアント側のアップロードをするmutateを書く
1 apollo-upload-client
yarn add apollo-upload-client
import {createNetworkInterface} from 'apollo-upload-client'
で、通常のcreateNetworkInterface
と入れ替える
2 graphql-ruby
Gemfile
gem "graphql"
3 GraphqlController等でのexecuteに渡すパラメーター
https://rmosolgo.github.io/graphql-ruby/schema/generators
rails generate graphql:install
で作られた状態のままのGraphqlController
ではapollo-upload-client
から送られるデータ形式と違う
ファイルアップロード時はoperations
と言うパラメーターが増えているのでこれを使う
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スカラー
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
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
// ここで言う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)
})
}