1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-03

#はじめに
現在、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(要約)

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

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

実際に登録してみる

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

登録前

データが1件もないため、メッセージが表示されています。
スクリーンショット 2021-12-03 15.44.37.png

登録後

登録したデータ自体のid、関連付けしてあるuserのuser_id、日付、就寝時間、起床時間の順に表示されています。
スクリーンショット 2021-12-03 15.44.30.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

問題の解決

いろいろと検索してみた結果、「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
DateTime型に変更した結果、日付も正しく保存されるようになりました!!

最後に

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

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

1
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?