当サイトの記事を別媒体に流用する際は必ず問い合わせページからご連絡下さい

ReactからRails経由でS3へファイルをアップロードする方法

やっはろー、へっぽこだよ。
Reactでファイルのアップロードをする機能を作ったら案外手こずったから記事にしてみました。

バックエンドはRailsでS3にファイルを格納してます。

ReactからファイルデータをRailsへ飛ばす

まずファイルを添付するボタンを作ります。
色々ありますが、今回はMaterial-UIを採用しました。
おしゃれかつ痒い所まで手が届く機能を備えてます。

まずはMaterial-UIをインストールします。

yarn add @mui/material @emotion/react @emotion/styled

次に以下のコードをコピペしてください。

import React,{useMemo} from "react";
import Input from "@mui/material/Input";

export default function APP() {
  const [file, setFile] = React.useState();
  const handleChangeFile = useMemo(
    () => (e: any) => {
      const file = e.target.files[0];
      setFile(file);
    },
    [file]
  );

  return (
     <label htmlFor="contained-button-file">
     <Input id="contained-button-file" type="file" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { handleChangeFile(e); }} />
     </label>
  );
}

コードサンドボックス:サンプル
そうすると↑を見て貰えば分かる通り、ファイル添付ボタンが現れたと思います。

仕組みを説明すると、添付ファイルを添付すると

onChange={(e: React.ChangeEvent) => {
  handleChangeFile(e);
}}

↑が発火し

const handleChangeFile = useMemo(
  () => (e: any) => {
    const file = e.target.files[0];
    setFile(file);
  },
  [file]
);

↑が実行され、選択した一番目のファイルがsetFileによってfileに格納されます。
それでfileをRailsに飛ばしておkって具合でいけばいいのですが、このままだとRailsがファイルを認識しません。
fileをファイル形式にコンバートしないといけないのです。

以下のコードを追加します。

import React,{useMemo} from "react";
import Input from "@mui/material/Input";

export default function APP() {
  const [file, setFile] = React.useState();
  const handleChangeFile = useMemo(
    () => (e: any) => {
      const file = e.target.files[0];
      setFile(file);
    },
    [file]
  );

+  const createFormData = () => {
+    const formData = new FormData();
+    formData.append("user[file]", file!);
+    return formData;
+  };

  return (
  );
}

formData.appendによってfile形式にコンバートされます。

formData.appendの引数は

formData.append("Railsのテーブル名[カラム名]", ファイルが格納された変数);

にします。
テーブル名はs抜きなので注意してください。

次にaxiosでRailsにデータを飛ばします。

yarn add axios

axiosをインストール。

以下のコードを追加してください。

import React, { useMemo } from "react";
import Input from "@mui/material/Input";
+ import Button from "@mui/material/Button";
+ import axios from "axios";

export default function APP() {
  const [file, setFile] = React.useState();
  const handleChangeFile = useMemo(
    () => (e: any) => {
      const file = e.target.files[0];
      setFile(file);
    },
    [file]
  );

  const createFormData = () => {
    const formData = new FormData();
    formData.append("user[file]", file!);
    return formData;
  };

+   const postFile = async () => {
+     const data = createFormData();
+     await axios
+       .post(`http://localhost:3000/post_file/`, data)
+       .then((response) => {
+         //成功したときの処理
+       })
+       .catch(() => {
+         //失敗したときの処理
+       });
+   };

  return (
    <label htmlFor="contained-button-file"> 
       <Input id="contained-button-file" type="file" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { handleChangeFile(e); }} />
       +  <Button +  size="medium" +  onClick={() => { +  postFile(); +  }} +  >    
       +  Railsにファイルを送信 
       +  </Button>
    </label>
  );
}

コードサンドボックス:サンプル
コードを追加したら↑と同じ感じになっていると思います。

ボタンを押したらpostFileが発火して、axiosが動くという具合になっています。

const data = createFormData();

ここでコンバートされたファイルをdataに格納して

.post(`http://localhost:3000/post_file/`, data)

こういう感じでrailsにデータを送信します。
本番の場合はもちろんhttp://localhost:3000の部分が変わります。
ご自身の本番のURLを入れてください。

細かいaxiosの使い方は他記事を参考にしてください。

Railsでデータを受け取る

デフォルトでRailsは別のポートからpostリクエストを受け付けていません。
なので、rack-corsを使って許可します。

■gemfile

+ gem 'rack-cors'

gemfileに↑を追加してbundle installを実行してください。
次にconfig/initializers配下にcors.rbを作成します。

設定ファイルの中身は↓みたいな感じ。

■config/initializers/cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3001'
    resource '*',
             headers: :any,
             expose: %w[access-token expiry token-type uid client],
             methods: %i[get post put patch delete options head],
             credentials: true
  end
end

origins部分はRailsが使用しているポート部分です。
デフォルトは3000ですが、へっぽこはReactを3000にしてるので、Railsは3001です。
本番の場合も同じで、originsにドメインを入れます。

次にcarrierwavefog-awsとをインストールします。

■gemfile

   gem 'rack-cors'
+ gem 'carrierwave'
+ gem 'fog-aws'

carrierwaveはアップロード、ダウンロードなどをよしなににやってくれる、便利なやつです。
bundle exec rails g uploader Fileを実行してapp/uploaders/file_uploader.rbを作成。

app/uploaders/file_uploader.rb

class FileUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  # if Rails.env.production?
  #   storage :fog
  # else
  #   storage :file
  # end

  if Rails.env.development?
    storage :file
  else
    storage :fog
  end

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url(*args)
  #   # For Rails 3.1+ asset pipeline compatibility:
  #   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
  #
  #   "/images/fallback/" + [version_name, "default.png"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process scale: [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
  # version :thumb do
  #   process resize_to_fit: [50, 50]
  # end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  # def extension_whitelist
  #   %w(jpg jpeg gif png)
  # end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end
end

↑みたいな感じにします。

if Rails.env.development?
  storage :file
else
  storage :fog
end

storage :fileだとアップロードしたファイルはpublic/uploads配下に保存されます。
storage :fogだとS3に保存されます、fog-awsのおかげで。
上記はローカルのときはpublic/uploads配下、本番の時はS3に保存されるように設定しています。

envはこんな感じで設定しています。
Railsのルートディレクトリに置くやつですね。

RAILS_ENV = "development"

本番だったらこれをProductionに変えるだけです。

S3に保存するためにconfig/initializers配下にcarrierwave.rbを作成します。

config/initializers/carrierwave.rb

require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'

CarrierWave.configure do |config|
    config.storage :fog
    config.fog_provider = 'fog/aws'
    config.fog_directory  = 'バケット名' # 作成したバケット名を記述
    config.fog_credentials = {
      provider: 'AWS',
      aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], # 環境変数
      aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], # 環境変数
      region: 'ap-northeast-1',   # アジアパシフィック(東京)を選択した場合
      path_style: true
    }
end 

設定は↑みたいな感じ。
詳しいことは
【Rails】 CarrierWaveチュートリアル
をご覧ください。

マイグレーションでUserテーブルとFileカラムを作ります。

rails g migration User
class User < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string 'file'
      t.timestamps
    end
  end
end
rails db:migrate

次にモデルを作成して、CarrierWaveを紐づけます。

rails g model User
■app/models/user.rb
class User < ApplicationRecord
  mount_uploader :file, FileUploader
end

これでfile_uploader.rbに紐づきます。

次にルートを設定します。

config/routes.rb

Rails.application.routes.draw do
  post '/post_file' => 'users#post_file'
end

コントローラーを作成します。

rails g controller user

app/controllers/user_controller.rb

class UsersController < ApplicationController
  def post_file
    User.create(file: params[:user][:file])
    render status: 200
  end 
end

これでaxiosで.post("http://localhost:3000/post_file/", data)でリクエストしたとき、user_controllerpost_fileのアクションを呼び出し、paramsに入ったfileをfileカラムに保存し、Userを作成します。
fileカラムはCarrierWaveに紐づいているのでファイル形式に再度コンバートされ、carrierwave.rbの設定に応じて保存先に保存されるという感じになります。

なんか不備あったらコメント欄で教えてください。
以上へっぽこでした。

ワンクリックでランキングに協力してください(´;ω;`)
※万一記事に関わる個人・団体の方で記事の削除依頼がある場合、本サイトのお問い合わせから連絡ください。内容を確認させていただきます。

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)