Help us understand the problem. What is going on with this article?

graphql-ruby×nuxt.js 画像アップロード

走り書きすみません。綺麗にします。

Rails

carrierwaveuploader/carrierwaveを設定した後のお話。
ModelとUploaderを設定してね。

GraphqlController

jaydenseric/apollo-upload-clientコレを使うと、ActionController::Parametersに入ってくる値が変化する。(リクエストを変更している。)

~/app/controllers/graphql_controller.rb
class GraphqlController < ActionController::API
  def execute
    if params[:operations].present?
      param = JSON.parse(params[:operations])
      query = param["query"]
      operation_name = param["operationName"]
      variables = {
        "file" => params["1"],
      }
    else # コレ要らないかも?
      variables = ensure_hash(params[:variables])
      query = params[:query]
      operation_name = params[:operationName]
    end
    context = {
      session: session,
      current_user: current_user,
    }
    result = ApiSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
    render json: result
  rescue JWT::ExpiredSignature, JWT::DecodeError, JWT::VerificationError => e
    render json: { error: { message: e.message } }, status: :unauthorized
  rescue => e
    raise e unless Rails.env.development?
    handle_error_in_development e
  end
end

ImageType

~/app/graphql/scalar_types/image_type.rb
module ScalarTypes
  class ImageType < Types::BaseScalar
    graphql_name 'ImageType'
    description 'ActionDispatch::Http::UploadedFile'

    def coerce_input(file, _context)
      ActionDispatch::Http::UploadedFile.new(
        filename: file.original_filename,
        type: file.content_type,
        head: file.headers,
        tempfile: file.tempfile
      )
    end

    def coerce_result(value, _context)
      I18n.l(value, format: :default)
    end
  end
end

中身の確認

variables["file"].original_filename # => "hogehoge.jpg"
variables["file"].content_type # => "image/jpeg"
variables["file"].headers # => "Content-Disposition: form-data; name=\"1\"; filename=\"hogehoge.jpg\"\r\nContent-Type: image/jpeg\r\n"
variables["file"].tempfile # =>  #<File:/tmp/RackMultipart20191011-1-vtsg19.jpg>

Mutation

~/app/graphql/mutations/user_resource/update_user_profile_image.rb
module Mutations
  module UserResource
    class UpdateUserProfileImage < Mutations::BaseMutation
      null false
      argument :profile_image, ScalarTypes::ImageType, required: true
      field :results, Boolean, null: true

      def resolve(profile_image:)
        ActiveRecord::Base.transaction do
          user = context[:current_user]
          user.remove_profile_image! if user.profile_image
          user.profile_image = profile_image
          user.save ? { results: true } : { results: false }
        end
      end
    end
  end
end
~/app/graphql/object_types/user_type.rb
module ObjectTypes
  class UserType < Types::BaseObject
    field :id, ID, null: false
    field :profile_image, ScalarTypes::ImageType, null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: true
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: true
  end
end

Nuxt

Apollo設定

以下のコードは使いません。

~/apollo/client-configs/default.js
import { HttpLink } from 'apollo-link-http'
export default () => {
  const httpLink = new HttpLink({ uri: 'http://localhost:3000/graphql' })
}

このnode_moduleを使います。jaydenseric/apollo-upload-client

入力フォーム

vue-upload-componentインストールする。

~/apollo/client-configs/default.js
import { ApolloLink } from 'apollo-link'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { createUploadLink } from 'apollo-upload-client'

export default () => {
  const uploadLink = new createUploadLink({ uri: 'http://localhost:3000/graphql' })
  const current_user = JSON.parse(localStorage.getItem('current_user'))
  const middlewareLink = new ApolloLink((operation, forward) => {
    operation.setContext({
      headers: { authorization: !current_user ? '' : current_user.token ? `Bearer ${current_user.token}` : '' }
    })
    return forward(operation)
  })
  const link = ApolloLink.from([
    middlewareLink,
    uploadLink
  ])
  return {
    link,
    cache: new InMemoryCache()
  }
}
~/pages/settings/profile.vue
<template>
  <v-container> 
    <label for="profile_image">Button</label>
      <file-upload extensions='gif,jpg,jpeg,png,webp' accept='image/png,image/gif,image/jpeg,image/webp' name='profile_image' v-model='profileImage' @input-filter='inputFilter' @input-file='inputFile' ref='upload'/>
  </v-container>
</template>
<script>
import UpdateUserProfileImage from '~/apollo/gql/mutations/user_resource/updateUserProfileImage.gql'
import FileUpload from 'vue-upload-component'
export default {
  components: {
    FileUpload
  },
  data: () => ({
    profileImage: []
  }),
  methods: {
    async storeProfileImage(file) {
      await this.$apollo.mutate({
        mutation: UpdateUserProfileImage,
        variables: {
          file: file.file
        }
      }).then(res => {
        console.log(res)
      }).catch(err => {
        console.error(err)
      })
    },
    inputFile(newFile) {
      if (newFile) {
        this.$nextTick(function() {
          this.storeProfileImage(newFile)
        })
      }
    },
    inputFilter(newFile, oldFile, prevent) {
      if (newFile && !oldFile) {
        if (!/\.(gif|jpg|jpeg|png|webp)$/i.test(newFile.name)) {          
          return prevent()
        }
      }
      if (newFile && (!oldFile || newFile.file !== oldFile.file)) {
        newFile.url = ''
        let URL = window.URL || window.webkitURL
        if (URL && URL.createObjectURL) {
          newFile.url = URL.createObjectURL(newFile.file)
        }
      }
    }
  }
}
</script>

GQL

~引数の名前が$fileでなければ、エラーが発生するっぽい。~
関係ないっぽい

jaydenseric/apollo-upload-client

Usage

Use FileList, File, Blob or ReactNativeFile instances anywhere within query or mutation variables to send a > GraphQL multipart request.

~/apollo/gql/mutations/user_resource/updateUserProfileImage.gql
mutation UpdateUserProfileImage($file: ImageType!) {
  updateUserProfileImage(profileImage: $file) {
    results
  } 
}

残った疑問

  • ↓調べる。
ActionController::Parameters
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away