Railsで日時を取得したいのに、2000年1月1日ばかり保存されてしまう件について

#はじめに
現在、Ruby on Railsにて睡眠を記録するアプリを作成しています。form_withを使用して日時を記録する際に、登録したデータの日付が2000年1月1日としか保存されませんでした。非常に単純な原因でしたので、備忘録として投稿します。

## 開発環境

DBにはMySQLを使用しています。

* Mac OS Big Sur 11.6
* VSCode
* Ruby 3.0.2p107
* Rails 6.1.4.1
* MySQL 8.0.27 for macos11.6 on arm64 (Homebrew)

#原因

* **migrationファイルでDateTime型とすべきところをTime型にしていた。**
* MySQLについて調べるべきなのに、Rubyのことを調べていた。

## 背後要因

> 日付だけでなく時刻も扱える Date のサブクラスです。
> DateTime は deprecated とされているため、 Timeを使うことを推奨します。
> [Ruby 3.0.0 リファレンスマニュアル class DateTime(要約)](https://docs.ruby-lang.org/ja/latest/class/DateTime.html)

RubyのTime型について調べたときに、リファレンスを見て「なるほど、**Ruby3.0以降はDateTimeは非推奨**なのか。ならば**migrationを作るときもTime型を使えばいいのか！**」と安易な考えに至ってしまいました。


# やりたかったこと
やりたかったことは、「フォームを利用して、就寝した時間と起床した時間をDBに保存できるかを確かめるための試験的な機能を作る」ことでした。

* 登録画面で就寝および起床時間を選択
* 登録ボタンを押したらデータをDBに保存
* 正常に保存できたら睡眠データの一覧ページにリダイレクト

このような機能を目指して作成していました。

## migration
すでに生成してあるUserモデルと関連を持たせています。UserとSleepLogは、1対多の関係になります。

```***_create_sleep_logs.rb
class CreateSleepLogs < ActiveRecord::Migration[6.1]
  def change
    create_table :sleep_logs do |t|
      t.references :user, foreign_key: true
      t.time :sleep_at
      t.time :wake_at

      t.timestamps
    end
    add_index :sleep_logs, %i[user_id created_at]
  end
end
```
なお、ここで問題の原因となったのがsleep_atカラムとwake_atカラムです。**本来はここでdatetime型とすべきところを、time型としてしまいました。**

## routes
ルーティングはresourcesを利用してindex以外のアクションを生成しています。

```routes.rb
  resources :sleep_logs, except: :index
```

これでsleep_logs_pathにPOSTすればsleep_logs_controllerのcreateアクションが利用できるようになりました。

```
$ rails routes | grep sleep_logs
sleep_logs  POST    /sleep_logs(.:format)   sleep_logs#create
```

## controller
データを正常に保存できれば、ユーザーの個別ページで睡眠データ一覧を表示するようにしています。

```sleep_logs_controller.rb
class SleepLogsController < ApplicationController
  def new
  end

  def create
    sleep_log = current_user.sleep_logs.build(sleep_log_params)
    if sleep_log.save
      flash.now[:success] = "記録が保存されました"
      redirect_to @current_user
    else
      flash[:invalid] = "エラーが発生しました"
      render 'new'
    end
  end

    (省略)

  private

  def sleep_log_params
    params.require(:sleep_log).permit(:sleep_at, :wake_at)
  end
end
```


## view
登録用フォームのviewです。form_withのtime_selelctを利用して、時間を選択できるようにしています。とりあえず試験的なデータを保存できればいいのでdefaultオプションを設定しています。

```new.html.erb
<%= form_with(url: sleep_logs_path, scope: :sleep_log, local: true) do |f| %>
  <%= f.label :sleep_at, "就寝時間" %>
  <%= f.time_select :sleep_at, default: Time.zone.now - 8.hours %>

  <%= f.label :wake_at, "起床時間" %>
  <%= f.time_select :wake_at, default: Time.zone.now %>

  <%= f.submit "記録する" %>
<% end %>
```
特にcssなどは当てていないため、chrome上ではこのように表示されます。

![スクリーンショット 2021-12-03 15.38.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246920/d5aecf9b-349b-e71a-604b-957387b4fe56.png)

睡眠データ一覧ページのviewについては省略させていただきます。


# 実際に登録してみる
とりあえず準備が整ったので、睡眠データを保存してみます。

## 登録前
データが1件もないため、メッセージが表示されています。
![スクリーンショット 2021-12-03 15.44.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246920/79f89f0a-0989-1b97-ad04-b570494e46f8.png)

## 登録後
登録したデータ自体のid、関連付けしてあるuserのuser_id、日付、就寝時間、起床時間の順に表示されています。
![スクリーンショット 2021-12-03 15.44.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246920/b06bb0ed-cee4-32e2-cbc8-efe39e1ea2ea.png)

投稿内容を編集している現在（2021/12/03）の日付を表示したいのですが、01/02となってしまっています。

## どんな日付が入っているのか？
rails consoleを利用して、睡眠データの中身を確認してみます。sleep_atとwake_atは2000年の1月、timestamp（created_atとupdated_at）は正しく時間が表示されています。

```
[#<SleepLog:0x000000012ef4dc90
  id: 1,
  user_id: 1,
  sleep_at: Sun, 02 Jan 2000 07:50:00.000000000 JST +09:00,
  wake_at: Sat, 01 Jan 2000 15:50:00.000000000 JST +09:00,
  created_at: Fri, 03 Dec 2021 15:50:41.277532000 JST +09:00,
  updated_at: Fri, 03 Dec 2021 15:50:41.277532000 JST +09:00>]
```

## paramsの内容を調べてみる
原因を探るため、まずは「paramsに正しいデータが保存されているか」を調べてみることにしました。controllerにraiseを追加して、わざと例外を発生させてみます。

```sleep_logs_controller.rb

  def create
    sleep_log = current_user.sleep_logs.build(sleep_log_params)
    raise
    if sleep_log.save
      flash.now[:success] = "記録が保存されました"
      redirect_to @current_user
    else
      flash[:invalid] = "エラーが発生しました"
      render 'new'
    end
  end
```

### paramsの中身は...?
このようになっていました。どうやらparams[:sleep_log]内には、sleep_atとwake_atそれぞれの年、月、日、時、分が格納されているようです。年には2021、月には12, 日には3が入っています。paramsには問題なさそうです。

![スクリーンショット 2021-12-03 15.55.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246920/4bbb455c-ce72-8805-02b2-dc2fe1486ab7.png)


# 問題の解決
いろいろと検索してみた結果、「DBの型がおかしいのかもしれない」と勘付きました。RubyのTime型では日付も扱うことができますが、MySQLのTime型では時間を取り扱うだけで日付は保存できないようですね…

## migrationの修正
sleep_atをwake_atをdatetime型に変換し、rails db:migrate:resetを実行します。

```***_create_sleep_logs.rb
t.datetime :sleep_at
t.datetime :wake_at
```
##再度、登録してみる
フォームを利用して再度データを登録してみます。
![スクリーンショット 2021-12-03 16.37.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246920/3ff50c02-a468-85d6-c2fd-db6116caeae4.png)
DateTime型に変更した結果、日付も正しく保存されるようになりました！！


# 最後に
問題が発生したときは、何が原因かを探る前に「何を取り扱っているのか」を考えるべきだと感じました。今回で言えば、MySQLのTime型とDateTime型の違いについて調べればすぐに判明したのに、Rubyのことばかり調べて数時間を無駄にしてしまったからです。

これからも何度も壁にぶつかると思いますが、がんばって乗り越えて行こうと思います。