フォームのバリデーションとニコ連携の画面変更 (#090) #355

マージ済み
みてるぞ が 11 個のコミットを feature/090 から main へマージ 2026-06-05 01:59:46 +09:00
6個のファイルの変更49行の追加41行の削除
コミット 6f4b388284 の変更だけを表示してゐます - すべてのコミットを表示
+2
ファイルの表示
@@ -69,3 +69,5 @@ gem 'discard'
gem "rspec-rails", "~> 8.0", :groups => [:development, :test] gem "rspec-rails", "~> 8.0", :groups => [:development, :test]
gem 'aws-sdk-s3', require: false gem 'aws-sdk-s3', require: false
gem 'rails-i18n', '~> 8.0.0'
+4
ファイルの表示
@@ -306,6 +306,9 @@ GEM
rails-html-sanitizer (1.6.2) rails-html-sanitizer (1.6.2)
loofah (~> 2.21) loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails-i18n (8.0.2)
i18n (>= 0.7, < 2)
railties (>= 8.0.0, < 9)
railties (8.0.2) railties (8.0.2)
actionpack (= 8.0.2) actionpack (= 8.0.2)
activesupport (= 8.0.2) activesupport (= 8.0.2)
@@ -477,6 +480,7 @@ DEPENDENCIES
puma (>= 5.0) puma (>= 5.0)
rack-cors rack-cors
rails (~> 8.0.2) rails (~> 8.0.2)
rails-i18n (~> 8.0.0)
rspec-rails (~> 8.0) rspec-rails (~> 8.0)
rubocop-rails-omakase rubocop-rails-omakase
sprockets-rails sprockets-rails
+1 -1
ファイルの表示
@@ -453,7 +453,7 @@ class PostsController < ApplicationController
if missing_ids.present? if missing_ids.present?
post.errors.add :parent_post_ids, post.errors.add :parent_post_ids,
"存在しない親投稿 ID があります: #{ missing_ids.join(' ') }" "存在しない親投稿 Id. があります: #{ missing_ids.join(' ') }"
raise ActiveRecord::RecordInvalid, post raise ActiveRecord::RecordInvalid, post
end end
+1 -1
ファイルの表示
@@ -46,7 +46,7 @@ describe ('extractValidationError', () => {
}) })
expect (validationError?.fieldErrors).toEqual ({ expect (validationError?.fieldErrors).toEqual ({
'deerjikists.0.platform': ['プラットフォームを入力してください.'], 'deerjikists0Platform': ['プラットフォームを入力してください.'],
}) })
}) })
+8 -6
ファイルの表示
@@ -1,3 +1,5 @@
import toCamel from 'camelcase-keys'
import { isApiError } from '@/lib/api' import { isApiError } from '@/lib/api'
export type FieldErrors<T extends string = string> = Partial<Record<T, string[]>> export type FieldErrors<T extends string = string> = Partial<Record<T, string[]>>
@@ -17,13 +19,13 @@ export const extractValidationError = <T extends string = string> (err: unknown)
if (!(isApiError (err)) || err.response?.status !== 422) if (!(isApiError (err)) || err.response?.status !== 422)
return null return null
const rawData = (err.response.data ?? { }) as Record<string, unknown> const rawData = toCamel ((err.response.data ?? { }) as Record<string, unknown>,
{ deep: true }) as RawValidationError
const data: RawValidationError = { const data: RawValidationError = {
type: rawData.type as string | undefined, type: rawData.type as string | undefined,
message: rawData.message as string | undefined, message: rawData.message as string | undefined,
errors: rawData.errors as Record<string, string[]> | undefined, errors: rawData.errors as Record<string, string[]> | undefined,
baseErrors: rawData.base_errors as string[] | undefined, baseErrors: rawData.baseErrors as string[] | undefined }
}
if (data.type !== 'validation_error' && !(data.errors)) if (data.type !== 'validation_error' && !(data.errors))
return null return null
+33 -33
ファイルの表示
@@ -20,7 +20,7 @@ import type { FC, FormEvent } from 'react'
import type { Deerjikist, Platform } from '@/types' import type { Deerjikist, Platform } from '@/types'
type DeerjikistFormField = type DeerjikistFormField =
'deerjikists' | `deerjikists.${ number }.platform` | `deerjikists.${ number }.code` 'deerjikists' | `deerjikists${ number }Platform` | `deerjikists${ number }Code`
const DeerjikistDetailPage: FC = () => { const DeerjikistDetailPage: FC = () => {
@@ -105,45 +105,45 @@ const DeerjikistDetailPage: FC = () => {
{/* プラットフォーム */} {/* プラットフォーム */}
<FormField <FormField
label="プラットフォーム" label="プラットフォーム"
messages={fieldErrors[`deerjikists.${ i }.platform`]}> messages={fieldErrors[`deerjikists${ i }Platform`]}>
{({ describedBy, invalid }) => ( {({ describedBy, invalid }) => (
<select <select
disabled={disabled} disabled={disabled}
value={datum.platform ?? ''} value={datum.platform ?? ''}
aria-describedby={describedBy} aria-describedby={describedBy}
aria-invalid={invalid} aria-invalid={invalid}
className={inputClass (invalid)} className={inputClass (invalid)}
onChange={e => setData (prev => { onChange={e => setData (prev => {
const rtn = [...prev] const rtn = [...prev]
rtn[i] = { ...rtn[i], rtn[i] = { ...rtn[i],
platform: (e.target.value || null) as Platform | null } platform: (e.target.value || null) as Platform | null }
return rtn return rtn
})}> })}>
<option value="">&nbsp;</option> <option value="">&nbsp;</option>
{PLATFORMS.map (p => ( {PLATFORMS.map (p => (
<option key={p} value={p}> <option key={p} value={p}>
{PLATFORM_NAMES[p]} {PLATFORM_NAMES[p]}
</option>))} </option>))}
</select>)} </select>)}
</FormField> </FormField>
{/* コード */} {/* コード */}
<FormField <FormField
label="コード" label="コード"
messages={fieldErrors[`deerjikists.${ i }.code`]}> messages={fieldErrors[`deerjikists${ i }Code`]}>
{({ describedBy, invalid }) => ( {({ describedBy, invalid }) => (
<input <input
type="text" type="text"
disabled={disabled} disabled={disabled}
value={datum.code} value={datum.code}
aria-describedby={describedBy} aria-describedby={describedBy}
aria-invalid={invalid} aria-invalid={invalid}
className={inputClass (invalid)} className={inputClass (invalid)}
onChange={e => setData (prev => { onChange={e => setData (prev => {
const rtn = [...prev] const rtn = [...prev]
rtn[i] = { ...rtn[i], code: e.target.value } rtn[i] = { ...rtn[i], code: e.target.value }
return rtn return rtn
})}/>)} })}/>)}
</FormField> </FormField>
</fieldset> </fieldset>
))} ))}