使用 React、Ruby on rails 5 和 CarrierWave 多次上传文件

Posted

技术标签:

【中文标题】使用 React、Ruby on rails 5 和 CarrierWave 多次上传文件【英文标题】:Multiple upload file using React, Ruby on rails 5 and CarrierWave 【发布时间】:2019-05-09 04:30:47 【问题描述】:

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

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

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

所以问题是我将数据从 react 发送到我的控制器的那一刻。确实 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 的经典对象,它不会出错,但不会将文件保存在多态模型中...

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

【问题讨论】:

在处理实际上传时,如果可以的话,您可能希望使用ActiveStorage 而不是 CarrierWave - 它非常容易处理文件参数,您可以使用 API 进行直接上传。跨度> 【参考方案1】:

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

  # 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

没有实际意义:

    像人类编译器一样复制哈希键。 RoR 有大量的哈希方法,例如 Hash#sliceHash#except。 手动 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);

【讨论】:

【参考方案2】:

感谢您的快速回答。

我尝试了你告诉我的方式(我就是这样做的,但当它不起作用时,我即兴发挥):

我改变了这样的设置:

params.require(:band_music).permit(:name, :mail, :phone, :style, :comment, :status, youtubes_attributes: [:url], musics_attributes: [:lastMod, :lastModDate, :name, :size, :type])

我在控制器中的创建功能

# 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

我的 submitForm 功能

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,
      
    );

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

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

这里是日志

Started POST "/band_musics" for 127.0.0.1 at 2018-12-07 14:34:36 +0100
Processing by BandMusicsController#create as JSON
  Parameters: "band_music"=>"name"=>"dqsdqsd", "musics_attributes"=>"[object Object]", "mail"=>"qsdqsdqsd", "phone"=>"090909", "style"=>"acid breaks", "comment"=>"dsqdqsdqsd", "youtubes_attributes"=>"[object Object]", "status"=>"pending"
Unpermitted parameters: :musics_attributes, :youtubes_attributes

参数和“[object, Object]”有问题...

【讨论】:

您是否尝试将属性添加到 band_music[musics_attributes][image/whatever][] 中?

以上是关于使用 React、Ruby on rails 5 和 CarrierWave 多次上传文件的主要内容,如果未能解决你的问题,请参考以下文章

使用Ruby on Rails的Babel-Transpiler:入门[关闭]

Ruby on Rails - 在 OSX 上使用 Ruby 2.4.4 而不是 rails 5.1.6 的配置问题/异常

Ruby on Rails全栈课程5.2 项目上线--在云服务器上配置Ruby On Rails环境

在 ruby​​ on rails 5.2.4 中使用谷歌云翻译时出错

Ruby on rails 5.2 - 带有活动存储的背景图像

如何使用 Ruby on Rails 5 将数据从 URL 提取到表单中