Ruby on Railsで実装していたViewをVue.jsのComponentに置き換える場合に、form_with
はよしなに行ってくれていたCSRF Tokenの設定やPATCH
等のGET
、POST
以外のmethodをRailsに認識させるためにhiddenで送信したりする処理を独自に実装する必要があります。
form_with options
- :method - The method to use when submitting the form, usually either “get” or “post”. If “patch”, “put”, “delete”, or another verb is used, a hidden input named _method is added to simulate the verb over post.
- :authenticity_token - Authenticity token to use in the form.
https://api.rubyonrails.org/v7.0/classes/ActionView/Helpers/FormHelper.html#method-i-form_with
毎回formを作る度に実装するのは手間なのでいい感じに設定するComponentを作ってみたのでメモ📝
CSRFTokenの設定やmethodを仕込むRailsのform_with
っぽいComponentの実装
以下が実装してみたComponentです。propsで受け取ったrequestMethod
がGET
以外だったらPOST
として送信し、Railsに認識させるためにhiddenで元のrequestMethod
(PATCH
等)を送信するようにしているのと、
rails/ujs
のcsrfToken
相当の処理で取得したTokenをhiddenで仕込んでいます。
<template> <form ref="form" class="common-form" :action="requestPath" accept-charset="UTF-8" :method="formMethod"> <input type="hidden" name="_method" :value="requestMethod" autocomplete="off" /> <input type="hidden" name="authenticity_token" :value="authenticityToken" autocomplete="off" /> <slot /> </form> </template> <script setup lang="ts"> import { computed, ref } from "vue"; type Props = { requestMethod: "get" | "post" | "patch" | "put" | "delete"; requestPath: string; }; // NOTE: rails/ujs の csrfTokenの処理をコピペしている // https://github.com/rails/rails/blob/ccb646244ff3768f796a5d9e0d22b833df3a6af6/actionview/app/assets/javascripts/rails-ujs.js#L47-L50 const csrfToken = () => { const meta = document.querySelector("meta[name=csrf-token]"); return meta && (meta as HTMLMetaElement).content; }; const props = defineProps<Props>(); const authenticityToken = csrfToken(); const formMethod = computed(() => (props.requestMethod === "get" ? "get" : "post")); const form = ref<HTMLFormElement | null>(null); // NOTE: 外部からもsubmitしたいケースを考慮して公開しとく defineExpose({ form }); </script> <style lang="scss" scoped></style>
実際に利用する際には以下のような感じで利用できます。
<template> <common-form request-path="/posts" request-method="post"> <label for="title" >タイトル</label> <input type="text" name="title" /> <button type="submit">登録する</button> </common-form> </template> <script lang="ts" setup> import CommonForm from "@js/components/molecules/CommonForm.vue"; </script>
おわりに
自分で実装してみて改めて思いましたが@rails/ujs
便利ですね🙏