首页 > 解决方案 > 使用 React、Ruby on rails 5 和 CarrierWave 多次上传文件

问题描述

我被一个问题困扰了大约 2 天。我可能正在寻找,但我找不到关于它的帖子......

因此,我将 Ruby on Rails 框架与 gem react on rails 一起使用,并尝试使用 CarrierWave gem 进行多次上传。

因此,当只有一个文件时,上传效果很好。但我采用了另一种策略,结果我最终不得不为单个模型上传多个文件。所以我创建了一个多态模型(如果我在其他地方需要它,它更通用)。

所以问题是我将数据从反应发送到我的控制器的那一刻。确实 JSON.stringify 不适用于对象文件,我不明白我该怎么做......

我的控制器

  # POST /band_musics
  # POST /band_musics.json
  def create
    @band_music = BandMusic.new({
      :name => band_music_params[:name],
      :phone => band_music_params[:phone],
      :mail => band_music_params[:mail],
      :style => band_music_params[:style],
      :comment => band_music_params[:comment],
      :status => band_music_params[:status],
      :musics_attributes => JSON.parse(band_music_params[:musics_attributes]),
      :youtubes_attributes => JSON.parse(band_music_params[:youtubes_attributes])
    })
    respond_to do |format|
      if @band_music.save
        format.html { redirect_to root_path, notice: 'Le groupe a bien été enregisté' }
        format.json { render :show, status: :created, location: @band_music }
      else
        format.html { render :new }
        format.json { render json: @band_music.errors, status: :unprocessable_entity }
      end
    end
  end

我的band_music_params

def band_music_params
  params.require(:band_music).permit(:name, :mail, :phone, :style, :comment, :status, :youtubes_attributes, :musics_attributes)
end

我的反应组件

import PropTypes from 'prop-types';
import React from 'react';
import ReactDropzone from 'react-dropzone'

export default class GroupForm extends React.Component {
  static propTypes = {
  };

  /**
   * @param props - Comes from your rails view.
   */
  constructor(props) {
    super(props);
    this.state = {
      files: {},
      loading: false,
      disabled: true,
    };
  }

  submitGroupForm = (event) => {
    event.preventDefault();

    let response_files = {}
    Object.keys(this.state.files).map((file, index) => {
      response_files[index] = {
          'lastMod'    : this.state.files[file].sound.lastModified,
          'lastModDate': this.state.files[file].sound.lastModifiedDate,
          'name'       : this.state.files[file].sound.name,
          'size'       : this.state.files[file].sound.size,
          'type'       : this.state.files[file].sound.type,
      }
    });

    const file_array = JSON.stringify(response_files);

    this.setState({ loading: true });
    let formPayLoad = new FormData();
    formPayLoad.append('band_music[musics_attributes]', file_array);

    fetch(this.props.url, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'X-CSRF-Token': ReactOnRails.authenticityToken(),
      },
      body: formPayLoad,
    }).then((response) => {
      console.log(response)
    });
  }

  onDrop = (files) => {
    if (Object.keys(this.state.files).length === 3) return
    var countFile = Object.keys(this.state.files).length
    console.log(countFile)
    files.forEach(file => {
      let hash = this.state.files
      hash[`${countFile}`] = { sound: file }
      this.setState({
        files: hash
      });
    });
  }

  render() {
    console.log(this.state)
    return (
      <div>
        <form onSubmit={this.submitGroupForm}>
          <div className="form-group">
            <label htmlFor="sound" className="color-primary">
              Fichier(s) Audio(s)
            </label>
            <ReactDropzone
              accept="audio/*"
              onDrop={this.onDrop}
              className="react-drop-zone-css"
            >
              { Object.keys(this.state.files).length > 0 &&
                <div className="fileContainer">
                  {Object.keys(this.state.files).map((file) => (
                    <p className="file">
                      <i className="fa fa-music fa-2x mb-2"></i>
                      {this.state.files[file].sound.name}
                    </p>
                  ))}
                  { Object.keys(this.state.files).length < 1 &&
                    <p className="plus">
                      <i className="fa fa-plus fa-3x mb-2"></i>
                    </p>
                  }
                </div>
              }

              { Object.keys(this.state.files).length === 0 &&
                <div className="d-flex justify-content-center align-items-center w-100 h-100">
                  <p className="mb-0">Cliquez pour importer un fichier audio ! (3 fichiers max)</p>
                </div>
              }
            </ReactDropzone>
          </div>

          <div className="row justify-content-center">
            {
              !!this.state.loading ? (
                <input className="btn btn-lg bg-button color-white mt-3" type="submit" value="Chargement... Cette action peut prendre quelques minutes" />
              ) : (
                <input className={"btn btn-lg bg-button color-white mt-3 " + (!!this.state.disabled ? 'disabled' : '')} disabled={!!this.state.disabled} type="submit" value="S'inscrire au tremplin" />
              )
            }
          </div>
        </form>
      </div>
    );
  }
}

为简单起见,如何在参数中传递文件。在这里,我尝试将文件转换为 stringify 的经典对象,它不会出错,但不会将文件保存在多态模型中......

如果您需要我发布另一个文件以获得更多理解,请在评论中说明,提前谢谢您:)

标签: ruby-on-railsrubyreactjsfile-uploadcarrierwave

解决方案


让我们从修复控制器方法开始:

  # POST /band_musics
  # POST /band_musics.json
  def create
    @band_music = BandMusic.new(band_music_params)
    respond_to do |format|
      if @band_music.save
        format.html { redirect_to root_path, notice: 'Le groupe a bien été enregisté' }
        format.json { render :show, status: :created, location: @band_music }
      else
        format.html { render :new }
        format.json { render json: @band_music.errors, status: :unprocessable_entity }
      end
    end
  end

没有实际意义:

  1. 像人类编译器一样复制哈希键。Hash#sliceRoR 有很多类似的哈希方法Hash#except
  2. 手动 JSON 转换嵌套属性。

相反,您可以通过将哈希传递给#permit.

def band_music_params
  params.require(:band_music)
        .permit(
           :name, :mail, :phone, :style, :comment, :status, 
           youtubes_attributes: [:foo, :bar], 
           musics_attributes: [:lastMod, :lastModDate, :name, :size, :type]
        )
end

这允许具有属性的哈希数组[:lastMod, :lastModDate, :name, :size, :type]。当然,您还应该删除反应代码中将对象转换为 JSON 字符串的行:

// Don't do this.
const file_array = JSON.stringify(response_files);

推荐阅读