Rails and FormData
RailsとFormDataについての記事です。英語と日本語で両方書きます。
This post talks about using WebAPI's FormData with Rails.
最近RailsのFormからAPIを基にしたシステムに交換しました。つまり、Formの代わりにXHRのリクエスト(axios)でバックエンドと連携しました。
We recently migrated from using Rails forms to an API based app where data is sent to the backend using XHR requests (in our case, using axios).
この記事で典型的なユーザモデルを使用します。
For the following example I will be using a generic User model.
問題・Problem
RailsのFormを使用・Using Rails Form
<%= form_for @user, url: {action: "create"} do |f| %>
<%= f.text_field :name %> # "Yokoyama"
<%= f.text_field :role %> # "Developer"
<%= f.submit "Create" %>
<% end %>
RailsのFormを利用したら、controllerに以下のparamsがあります。
If we were using Rails forms then we'd normally get:
def create
# params = <ActionController::Parameters
# {
# "user" => {
# "name"=>"Yokoyama",
# "role"=>"Developer"
# },
# "controller"=>"...",
# "action"=>"create",
# }
# permitted: false>
...
end
FormDataを使用・Using FormData
リクエストボディーを作るとFormDataを利用しました。
When packaging a body for a POST/PATCH request we used FormData.
const formData = new FormData();
formData.append('name','Yokoyama');
formData.append('role','Developer');
const url = 'user create link';
axios.post(url, formData, {
headers: {
'content-type': 'multipart/form-data',
},
})
...
RailsのControllerに下記レスポンスがあります。
On the Rails backend we get:
def create
# params = <ActionController::Parameters
# {
# "name"=>"Yokoyama",
# "role"=>"Developer",
# "controller"=>"...",
# "action"=>"create",
# }
# permitted: false>
...
end
キーポイント:
- 属性はトップレベルのため、同名前の問題が起こる可能性があります。
- Railsのパタンではありません。
As you can see:
- All attributes are scoped to the top level, which will cause issues if there are name conflicts
- It's also just against the typical Rails pattern
解決・Solution
もともと@jugtuttle on Mediumが書いた解決ですが、モデルの名前をキーに含まれたら成功です。
@jugtuttle on Medium pointed out that you can simply use a key that has the model name. So in our case:
const formData = new FormData();
formData.append('user[name]','Yokoyama');
formData.append('user[role]','Developer');
axios.post('insert user path here', formData, {
headers: {
'content-type': 'multipart/form-data',
},
})
...
より簡単にするために:
To make it even easier:
// rails_form_data.js
class RailsFormData extends FormData {
constructor(model) {
super();
this.model = model;
}
append(key, value) {
super.append(`${this.model}[${key}]`, value);
}
}
export default RailsFormData;
import RailsFormData from '...'
const formData = new RailsFormData('user');
formData.append('name','Yokoyama');
formData.append('role','Developer');
axios.post('insert user path here', formData, {
headers: {
'content-type': 'multipart/form-data',
},
})
...
結果・Result
Railsのcontrollerには以下状態があります。
Then on the Rails backend side you'll get:
def create
# params = <ActionController::Parameters
# {
# "user" => {
# "name"=>"Yokoyama",
# "role"=>"Developer"
# },
# "controller"=>"...",
# "action"=>"create",
# }
# permitted: false>
...
end