javascript - Using JavaScript to submit an array and a file object to a Rails backend
问题描述
I haven't been able to figure out how to get my JavaScript to send a request in a format that Rails will accept when I try to edit a Game with a File parameter and an array parameter in the same payload.
The Rails controller looks like this (simplified, obviously):
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
So I have JavaScript like so:
// 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
)
The JavaScript runs when I hit the submit button in a form, and is supposed to convert the data into a format Rails' backend will accept. Unfortunately, I'm having trouble getting it to work.
I'm able to use JSON.stringify()
instead of FormData
for submitting the data in the case where there's no image file to submit, like so:
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'
})
This works fine. But I haven't been able to figure out how to use JSON.stringify
when submitting a File object. Alternatively, I can use a FormData
object, which works for simple values, e.g. name
, as well as File objects, but not for array values like an array of IDs.
A successful form submit with just the ID arrays (using JSON.stringify
) looks like this in the Rails console:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "engine_ids"=>[], "genre_ids"=>[13]}, "id"=>"4"}
However, my current code ends up with something more like this:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"18,2,15", "engine_ids"=>"4,2,10"}, "id"=>"4"}
Unpermitted parameters: :genre_ids, :engine_ids
Or, if you also upload a file in the process:
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: My question is, how can I send this payload (a name string, an array of IDs, as well as a game cover image) to Rails using JavaScript? What format will actually be accepted and how do I make that happen?
The Rails app is open source if that'd help at all, you can see the repo here. The specific files mentioned are app/controllers/games_controller.rb
and app/javascript/src/components/game-form.vue
, though I've simplified both significantly for this question.
解决方案
I figured out that I can do this using ActiveStorage's Direct Upload feature.
In my 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'
})
}
I then figured out that, with the way DirectUpload works, I can just send the coverBlob
variable to the Rails application, so it'll just be a string. Super easy.
推荐阅读
- javascript - 仅加载一次 Json 文件,之后我想从加载的文件评估器中获取而不是再次加载
- javascript - Javascript - 使 img 元素从左侧滑入并淡入
- postgresql - postgreql 二维数组文字无法识别
- flutter - Flutter - 带有弓形图标的行
- python - Django 静态文件未加载 ImageField URL
- python - 如何在没有选择的情况下将评论表单绑定到一个锅(Django)
- node.js - 使用 nodejs 将 base64Image 上传到 Azure 存储时出错
- javascript - 在 Vue 3 中使用 Vuex 4 模块和 TypeScript,以及如何修复循环依赖 linting 错误?
- c++ - 是否可以在类定义中使用类对象作为变量?
- dart - 使用 dartz 中的 Either 时出现编译器错误