【问题标题】:Using JavaScript to submit an array and a file object to a Rails backend使用 JavaScript 向 Rails 后端提交数组和文件对象
【发布时间】:2019-02-18 02:05:58
【问题描述】:

我无法弄清楚如何让我的 JavaScript 以 Rails 可以接受的格式发送请求,当我尝试在同一有效负载中编辑带有文件参数和数组参数的游戏时。

Rails 控制器看起来像这样(显然是简化了):

class GamesController < ApplicationController
  def update
    @game = Game.find(params[:id])
    authorize @game

    respond_to do |format|
      if @game.update(game_params)
        format.html { render html: @game, success: "#{@game.name} was successfully updated." }
        format.json { render json: @game, status: :success, location: @game }
      else
        format.html do
          flash.now[:error] = "Unable to update game."
          render :edit
        end
        format.json { render json: @game.errors, status: :unprocessable_entity }
      end
    end
  end

  private

  def game_params
    params.require(:game).permit(
      :name,
      :cover,
      genre_ids: [],
      engine_ids: []
    )
  end
end

所以我有这样的 JavaScript:

// this.game.genres and this.game.engines come from
// elsewhere, they're both arrays of objects. These two
// lines turn them into an array of integers representing
// their IDs.
let genre_ids = Array.from(this.game.genres, genre => genre.id);
let engine_ids = Array.from(this.game.engines, engine => engine.id);

let submittableData = new FormData();
submittableData.append('game[name]', this.game.name);
submittableData.append('game[genre_ids]', genre_ids);
submittableData.append('game[engine_ids]', engine_ids);
if (this.game.cover) {
  // this.game.cover is a File object
  submittableData.append('game[cover]', this.game.cover, this.game.cover.name);
}

fetch("/games/4", {
  method: 'PUT',
  body: submittableData,
  headers: {
    'X-CSRF-Token': Rails.csrfToken()
  },
  credentials: 'same-origin'
}).then(
  // success/error handling here
)

当我点击表单中的提交按钮时,JavaScript 会运行,并且应该将数据转换为 Rails 后端可以接受的格式。不幸的是,我无法让它工作。

在没有要提交的图像文件的情况下,我可以使用JSON.stringify() 而不是FormData 来提交数据,如下所示:

fetch("/games/4", {
  method: 'PUT',
  body: JSON.stringify({ game: {
    name: this.game.name,
    genre_ids: genre_ids,
    engine_ids: engine_ids
  }}),
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': Rails.csrfToken()
  },
  credentials: 'same-origin'
})

这很好用。但是我一直无法弄清楚在提交 File 对象时如何使用JSON.stringify。或者,我可以使用 FormData 对象,它适用于简单值,例如name,以及 File 对象,但不适用于 ID 数组等数组值。

仅使用 ID 数组(使用 JSON.stringify)成功提交的表单在 Rails 控制台中如下所示:

Parameters: {"game"=>{"name"=>"Pokémon Ruby", "engine_ids"=>[], "genre_ids"=>[13]}, "id"=>"4"}

但是,我当前的代码最终会更像这样:

Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"18,2,15", "engine_ids"=>"4,2,10"}, "id"=>"4"}

Unpermitted parameters: :genre_ids, :engine_ids

或者,如果您在此过程中还上传了文件:

Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"13,3", "engine_ids"=>"5", "cover"=>#<ActionDispatch::Http::UploadedFile:0x00007f9a45d11f78 @tempfile=#<Tempfile:/var/folders/2n/6l8d3x457wq9m5fpry0dltb40000gn/T/RackMultipart20190217-31684-1qmtpx2.png>, @original_filename="Screen Shot 2019-01-27 at 5.26.23 PM.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"game[cover]\"; filename=\"Screen Shot 2019-01-27 at 5.26.23 PM.png\"\r\nContent-Type: image/png\r\n">}, "id"=>"4"}

Unpermitted parameters: :genre_ids, :engine_ids

TL;DR:我的问题是,如何使用 JavaScript 将此有效负载(名称字符串、ID 数组以及游戏封面图片)发送到 Rails?什么格式实际上会被接受,我该如何做到这一点?


Rails 应用程序是开源的,如果有帮助的话,you can see the repo here。提到的具体文件是app/controllers/games_controller.rbapp/javascript/src/components/game-form.vue,尽管我已经为这个问题大大简化了这两个文件。

【问题讨论】:

  • 我应该注意到我确实发现如果你这样做:submittableData.append('game[engine_ids][]', engine_ids); 它会工作,但 Rails 后端收到的东西看起来像 "engine_ids"=&gt;["5,7"]

标签: javascript ruby-on-rails ruby-on-rails-5 form-data rails-activestorage


【解决方案1】:

我发现我可以使用 ActiveStorage's Direct Upload feature 来做到这一点。

在我的 JavaScript 中:

// Import DirectUpload from ActiveStorage somewhere above here.
onChange(file) {
  this.uploadFile(file);
},
uploadFile(file) {
  const url = "/rails/active_storage/direct_uploads";
  const upload = new DirectUpload(file, url);

  upload.create((error, blob) => {
    if (error) {
      // TODO: Handle this error.
      console.log(error);
    } else {
      this.game.coverBlob = blob.signed_id;
    }
  })
},
onSubmit() {
  let genre_ids = Array.from(this.game.genres, genre => genre.id);
  let engine_ids = Array.from(this.game.engines, engine => engine.id);
  let submittableData = { game: {
    name: this.game.name,
    genre_ids: genre_ids,
    engine_ids: engine_ids
  }};

  if (this.game.coverBlob) {
    submittableData['game']['cover'] = this.game.coverBlob;
  }

  fetch(this.submitPath, {
    method: this.create ? 'POST' : 'PUT',
    body: JSON.stringify(submittableData),
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': Rails.csrfToken()
    },
    credentials: 'same-origin'
  })
}

然后我发现,通过 DirectUpload 的工作方式,我可以将 coverBlob 变量发送到 Rails 应用程序,所以它只是一个字符串。超级简单。

【讨论】:

  • 您可能想做的一件事是&lt;%= f.file_field :whatever, direct_upload: true %&gt; 让 Rails 将上传 URL 放入数据属性中,然后在您的 JavaScript 中,您可以将 URL 从 &lt;input type="file"&gt; 中提取出来s data-direct-upload-url 属性。然后,如果您切换到非本地存储(例如 S3),一切都应该继续工作。如果您想在浏览器中进行一些简单的图像编辑而不上传任何中间文件,您也可以将所有这些连接到 a &lt;canvas&gt;
  • @muistooshort 我实际上并没有为此使用
    元素,它都是一个 VueJS 组件。您有什么方法可以在不使用 file_field 帮助器的情况下获取直接上传路线? rails_direct_uploads 路线会起作用吗?编辑:是的,确实如此!我将&lt;%= rails_direct_uploads_path.to_json %&gt; 传递到 Vue 组件中,它无需在 JavaScript 中对字符串进行硬编码即可工作:D
  • 或者一般来说,rails_direct_uploads_url。这就是 Rails 表单助手使用的内容。
【解决方案2】:

您可以将 File 对象转换为 data URL 并将该字符串包含在 JSON 中,请参阅 Upload multiple image using AJAX, PHP and jQuery 处的 processFiles 函数或在 JavaScript Array 上使用 JSON.stringify() 并将其设置为值FormData 对象,而不是将 Array 作为值传递给 FormData

submittableData.append('game[name]', JSON.stringify(this.game.name));
submittableData.append('game[genre_ids]', JSON.stringify(genre_ids));
submittableData.append('game[engine_ids]', JSON.stringify(engine_ids));

【讨论】:

  • 如果我将 JavaScript 更改为使用 JSON.stringify,引擎/流派不会以 Rails 接受的格式发送,因此游戏流派和引擎不会更新。 Rails 应用程序得到的响应是这样的:Parameters: {"game"=&gt;{"name"=&gt;"\"Pokémon Ruby\"", "genre_ids"=&gt;"[13,3]", "engine_ids"=&gt;"[5]"}, "id"=&gt;"4"}
  • 是的,这是有效的转义JSON,对吗? Rails 有解析有效JSON 的方法吗?问题似乎是 JavaScript Arrays 被传递给 FormData,而不是 JSON。将File 对象转换为字符串的唯一问题是什么?
  • 是的,但正如我所说的,问题是当我将图像文件作为 JSON 上传时,我无法正确发送它们,这就是我使用 FormData 的原因。所以,我想一个有效的解决方案是找到一种方法让 Rails 在提交为 JSON 时接受图像文件,这样就不需要 FormData。
  • @ConnorShea:如果您使用direct uploads with Active Storage,则文件部分会像您一样通过向控制器发送签名的 ID 字符串而消失。你需要做更多的工作来处理错误处理和清理。
  • @muistooshort 谢谢,签名的 ID 字符串提示帮助很大!我发布了我的解决方案:)
猜你喜欢
  • 1970-01-01
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-16
  • 1970-01-01
  • 1970-01-01
  • 2016-11-15
相关资源
最近更新 更多