0
0

RSpecで日付の値が有効ではないとHTMLから警告されて詰まる(一応は解決)

Last updated at Posted at 2024-07-22

結論

  • HTML5ではinput要素の入力値に対してのバリデーションが働くため
    • 'email'など高頻度で使われるであろう形式にも機能

【編集時】

  • Railsのフォームヘルパー form.datetime_field で生成されるのは <input type="datetime-local">
  • YYYY-MM-DDThh:mm という書式で値を扱っている点に注意
    • その関係で"文字列"であれば問題なくテストできた
"編集"の場合
# OKパターン
fill_in 'Deadline', with: '2001-02-03T04:50'
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50).strftime("%m%d%Y\t%I%M%P")

# ERRORパターン
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50) 
fill_in 'Deadline', with: DateTime.new(2001, 2, 3, 4, 50) 
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50).to_s
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50).strftime("%Y-%m-%dT%T")
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50)
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50, 0, "+09:00")

【新規登録時】

  • オブジェクト生成で入力したい日付を指定する際には ナノ秒(%N)を含めない ように注意する必要がある
    • この際"秒(%S)"は問題ない
    • 現時点でDateTimeオブジェクトは非推奨なので基本使わない方が良い
"新規登録"の場合
# OKパターン
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45)
fill_in 'Deadline', with: DateTime.new(2000, 1, 2, 3, 45) # 注)DateTime が非推奨
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).strftime("%m%d%Y\t%I%M%P")
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45, 0, "+09:00") 

# ERRORパターン
fill_in 'Deadline', with: '2000-01-02T03:45'
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).to_s
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).strftime("%Y-%m-%dT%T")
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45, 0, "+09:00")

本件の状況・詳細について

エラー1(編集時)

1. テスト箇所

  • 「"作業日時"など作業についての情報を入力するフォーム」のテスト
    • このフォームで作業の新規登録・編集を行う
app/views/tasks/_form.html.erb
<div class="field">
    <%= form.label :date %>
    <%= form.datetime_field :date %>
</div>

<div class="actions">
    <%= form.submit %>
</div>

2. テストの流れ

  • 以下のようなシンプルな流れのテストを実施しようとした
    1. いくつかの入力値を入力(ここに主題の'日付値'を含む)
    2. 登録および編集完了ボタンを押下
    3. 詳細画面へ画面遷移

以下、『【編集】(日付入力 → ボタン押下)』のRSpecの内容

spec/system/works_spec.rb
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移

3. テスト結果

  • テストを実施した所、「編集時に」どうやら 2. で止まっていることがスクリーンショット(画像1)から確認できた
    • (画像1:何かしらの警告が確認できるスクリーンショット)
      image.png

[画像から分かる事]

  • 値自体は入力されている
    • その上でどうやら「値が有効ではない」という警告がされている
    • 有効値も併せて提示されている
警告文
please enter a valid value. the two nearest valid values are ~, ~ and ~

解決策の模索

1. 一旦値を空にしてから入力(失敗)
⇒ 同じ警告が表示され変化なし

1. 一旦値を空にしてから入力
fill_in 'date', with: nil # Add
fill_in 'date', with: DataTime.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移

2. DataTimeが非推奨と分かったのでTimeに変更(失敗)
⇒ 同じ警告が表示され変化なし

2. Time オブジェクトに変更
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移

A.やはり「入力している値が好ましくないのが原因」らしいと断定

エラー2(新規登録時)

1. テスト箇所

  • 先述の編集時と同様なので割愛

2. テストの流れ

  • 先述の編集時と同様
  • ただ、コードの統一感を持たせようと編集時のエラー1の解決策であるYYYY-MM-DDThh:mmの書式をこちらにも記述した

以下、『【編集】(日付入力 → ボタン押下)』のRSpecの内容

spec/system/works_spec.rb
# 変更前
# fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45)

# 変更後
fill_in 'Deadline', with: '2000-01-02T03:45'
click_button 'Update' # 画面遷移

3. テスト結果

  • テストを実施した所、「編集時に」どうやら 2. で止まっていることがスクリーンショット(画像2)から確認できた
    • (画像2:画像1の動揺に警告が確認できるスクリーンショット)
      image.png

[画像から分かる事]

  • 値自体は入力されているが、エラー1とは違い「入力内容が不完全だからちゃんと入力しろ」という指摘
  • なにやら"AM・PM"が入っていないから不完全という指摘?
警告文
Please enter a valid value. The field is incomplete or has an invalid date.

解決策の模索

1. 文字列ではなくTime オブジェクトで付与(成功)
⇒ これはオーソドックスな記述なので想定内

1. 文字列ではなくオブジェクト
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Update' # 画面遷移

2. 改めて書式を整えて文字列として付与(失敗)
⇒ 同じ警告が表示され変化なし
  値の過不足は依然として解消されず

2. 改めて書式を整えて文字列として付与
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).strftime("%Y-%m-%dT%T") # 日付入力
click_button 'Update' # 画面遷移

A.やはり「入力値の不足が原因」らしいと断定

調査

警告の存在について

他気になる事項

  • 原因を突き止めたものの原因が不明
  • 解決策も何で正解なのかが分からない
  1. "AM""PM"表記があるのはブラウザによるものなのか、JS によるものなのか
  2. タイムゾーンなど直接入力値に含めていない情報も明示することが必要なのか

(最後に) - 残念ながらはっきりとして原理が自分には分からなかった。原理を抑えられればもっと有益な記事にできたはず - 編集、新規登録時の違いで生じる差分が謎 - 「入力値が既にあるかないか」の違いだけなのにこうも違いが出てくるのが意味不明 - `datetime_local`のフォームに入力するのだから、どちらとも`YYYY-MM-DDThh:mm`の書式に倣って入力すれば丸く収まると思ったのに - あとなぜ自分が扱っている題材には"AM""PM"表記があるのが分からない - これが悪さしている気もするし、この記事が参考にならない方が多そう

【追記】おそらく原因特定

あれから根気よくリサーチした結果、原因らしき特徴を発見

form.datetime_filed<input type="datetime_local" />)は『入力された値に応じてフォームの表示が変化する』のではないかという点

「"AM""PM"表記があるのはブラウザによるものなのか、JS によるものなのか」に関して

A.どちらでもなかった。HTMLの type="datetime_local" の作用と思われる

原因・調査結果など

  • そもそもなんで新規登録時と編集時で違いが出てしまうのかが謎だった
    • どちらも datetime_local のフォームに入力するの同じ要領で Time.new(~) で入力すれば問題ないのでは?
  • JS も特に実装していないので JS が原因というのも考えづらい

⇒推論の通りだった

新規登録と編集の違いは "FacroryBot で作成したデータを参照しているか" の違いがあった

  1. 新規登録時のテストにおいては Time.new() で日付を入力していた(この時点ではフォームは当然空欄)
  2. page.save_screenshot で確認してみた所、編集時のテストにおいては「FacroryBot で生成したテストデータを参照してテスト」をしていた(初期値あり)
    • その FacroryBot では次のようにテストデータを作成していた
      • 画像4、画像5にてどのように日付が入力前後の値を確認したところ、"秒数(%S)"まで入力されていたためこれがどうも怪しいと考えた
spec/factories/work.rb
FactoryBot.define do
  factory :work do

#(中略)

    date { 1.week.from_now }
  end
end
date { 1.week.from_now } によって生成される値
irb(#<RSpec::___>):001:0> 1.week.from_now
=> Wed, 31 Jul 2024 05:18:47.310641803 UTC +00:00

(画像4:入力前フォーム)
image.png

(画像5:入力後フォーム)
image.png

初期値の有無が影響しているのではないか?

  1. 書式としては YYYY-MM-DD HH:MM:SS な値が入力されたことになる
  2. 先述の通り、"datetime_local" の書式は %Y-%m-%dT%TYYYY-MM-DDThh:mm) である
  • しかし下記ドキュメントより、デフォルト値は書式を『strftime メソッドを呼んで %Y-%m-%dT%T 』としている

The default value is generated by trying to call strftime with “%Y-%m-%dT%T” on the object’s value, which makes it behave as expected for instances of DateTime and ActiveSupport::TimeWithZone.

訳)デフォルト値は、オブジェクトの値に「%Y-%m-%dT%T」を指定して strftime を呼び出すことによって生成されます。これにより、DateTime および ActiveSupport::TimeWithZone のインスタンスに対して期待どおりに動作します。

引用 : api.rubyonrails.org

デフォルト値に対して適応される'datetime_local'の書式のサンプルコード
t = Time.new(2001, 2, 3, 4, 50)
p t.strftime("%Y-%m-%dT%T") # => "2001-02-03T04:50:00"
  • ドキュメントの指す "デフォルト値" というのは "value" と思われる
  • 特に指定しない場合は %Y-%m-%dT%T%Y-%m-%dT%H:%M:%S と同義) となる仕様

Q.じゃあこの書式が変わってしまう場合はあるのか?

  • デフォルト値である value 属性に書式を指定することで可能

A.よって初期値 value での書式設定が "datetime_field" の表示に関係してくると思われる
 ⇒この value での書式設定をしているというのは先述のドキュメントがやっている事と同じでは…

  • よって具体的には以下2点が関係しているのではと推測
    1. strftimeメソッドが呼ばれるため返り値が"String"」
    2. 「書式が%Y-%m-%dT%Tに設定されている」

・1. については fill_in に「入力したい値」を指定するのにデータ型が関係しているとは思えなかった
・2. については書式に沿った適した「入力したい値」を指定しているかを改めて調査した

出力結果の違いについてのサンプル
t1 = Time.now
p t1       # => 2024-07-24 10:58:45.694187938 +0000 
p t1.class # => Time

t2 = Time.now.strftime("%Y-%m-%dT%T")
p t2       # => "2024-07-24T10:58:45"
p t2.class # => String

# Railsの`1.week.from_now`と同義
t3 = Time.now + (7 * 24 * 60 * 60)
p t3       # => 2024-07-24 10:58:45.694187938 +0000 
p t3.class # => Time

このサンプルから「Time インスタンスだと 'ナノ秒'までも渡している のが良くないのでは?」と考えて修正を試みた

修正

  • 私の場合は FacroryBot で「秒数を含む Time 型」である 1.week.from_now で入力されたテストデータを作成していたため、「ナノ秒を含まない Time 型の値」を生成する必要があった

強引にナノ秒を含まない書式に変更 → Time 型にキャスト

  • strftime の対となるメソッド parse を発見したので、これがあれば Time.now で生成されるナノ秒を排除できると考え次のように実装した

time ライブラリにより parse メソッドが拡張されるので、paiza.io での検証の際は読み込んでいます。

ナノ秒を含まない Time 型の値
require 'time' # paiza.ioでは必須

Time.parse(Time.now.strftime("%Y-%m-%d %H:%m"))
# => 2024-07-24 12:07:00 +0000

◎ナノ秒を含まない Time 型の値を取得することに成功した

RSpecでの実証

ここで試しに次のように実装し RSpec に用いた

※Rails においては require 'time' がなくとも parse が使えた。おそらく事前にライブラリを読み込んでくれている。

spec/factories/work.rb
FactoryBot.define do
  factory :work do

# ---(中略)---

    # date { 1.week.from_now }  # 修正前

    # 修正後
    # `1.week.from_now` と `Time.now + (7 * 24 * 60 * 60)` は同義
    date { Time.parse((Time.now + (7 * 24 * 60 * 60)).strftime("%Y-%m-%d %H:%m")) }
  end
end
spec/system/works_spec.rb(新規作成時のテスト)
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
spec/system/works_spec.rb(編集時のテスト)
fill_in 'date', with: Time.new(2000, 1, 2, 3, 45)
click_button 'Update' # 画面遷移

【結果】本記事で問題提起した「新規作成時・編集時の付与する入力値の違い」は解消された

追記分のまとめ

  1. 新規登録時はそもそも初期値である value が Rails によって付与されないため、fill_in で付与する入力値はオブジェクトでも問題ない
    • ただし valueに設定される%Y-%m-%dT%T書式に合わせて入力すると余計な "T" が含まれてしまうため今回のような入力値として不適切という警告が表示される
      例1
      # NGパターン
      Time.now.strftime("%Y-%m-%dT%T") # => "2024-07-24T13:04:47"
      
  2. 編集時は初期値が strftime("%Y-%m-%dT%T") の書式で付与されるため、その書式に合った入力値が求められる
    • なので先述の 例1 はこちらではOKパターンになる
    • NGパターンとして「ナノ秒(%N)を含む」「"-"、"T"を含まないなど書式に沿わない」場合が該当
      例1
      # NGパターン
      Time.now # => "2024-07-24T13:04:47"  # => 2024-07-24 13:15:14.993104276 +0000
      Time.new(2001, 2, 3, 4, 50) # => 2001-02-03 04:50:00 +0000
      
  3. Time.new()Time.now() は Time 型インスタンスを生成する点で同じだが、後者は「ナノ秒が含まれる」点に注意が必要
    • 前者は引数で任意に決められる
    • 後者でナノ秒を含めない場合は strftime を使用して書式を強制変更するしかない
  4. ビューヘルパーである datetime_field の作用として『初期値があれば strftime("%Y-%m-%dT%T") を施して value に設定』される
    • この記事にある通り書式は任意に変更可能
    • ただし、書式を特に設定しない場合は本記事の問題になったように「入力値もデフォルトの書式に強制される」ため注意が必要
      (画像6:初期値が無い場合)
      create_non_value.png

      (画像7:初期値がある場合)
      editing_with_value.png

参考サイト・参考記事など

以下、参考にさせていただいたサイト群になります。数が多いため折り畳みにさせていただいております。ご了承ください。

【参考サイト】
  1. https://docs.ruby-lang.org/ja/latest/class/DateTime.html
  2. https://romantist.jp/blog/html5-form-validation-balloon/
  3. https://www.htmq.com/htmls/input_type_datetime-local.shtml
  4. https://aaronsaray.com/2015/error-validating-seconds-in-html5-time-input/
  5. https://stackoverflow.com/questions/linked/19011861?lq=1
  6. https://stackoverflow.com/questions/19284193/invalid-value-when-setting-default-value-in-html5-datetime-local-input
  7. https://stackoverflow.com/questions/22332520/how-can-i-use-datetime-local-without-am-pm
  8. https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html
  9. https://jp.cybozu.help/k/ja/user/app_settings/form/autocalc/date_format.html
  10. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local
  11. https://phrase.com/blog/posts/date-time-localization/
  12. https://qiita.com/nakanaka444/items/a699393da1e197333243
  13. https://teratail.com/questions/301872
  14. https://github.com/teamcapybara/capybara/blob/master/History.md#changed-4:~:text=Selenium%20driver%20supports%20Date/Time%20when%20filling%20in%20date/time/datetime%2Dlocal%20inputs
  15. https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
  16. https://www.aki--dev.com/entry/2022/04/10/004608
  17. https://stackoverflow.com/questions/49148017/how-to-fill-in-a-datetime-field-with-capybara

[後日更新時の関連サイト]

  1. https://www.aki--dev.com/entry/2022/04/10/004608
  2. https://github.com/dotnet/aspnetcore/issues/9727
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0