カテゴリー: FrontEnd

[Rails + Materialize] DateTimePickerがなかったので…

はじめに

こんにちは!
Materializeには便利なコンポーネントがたくさんあります。
DatePickerやTimePickerもその一つです。

Pickers

ただ、現状DateTimePickerはないようです。
Railsで日付+時間で操作したいこともあるかと思います。

たまたま要件として、フォームの中でDateTimeを扱う必要がでました。
私が考えた選択肢としては、

  1. DateTimePickerを作る or 別のプラグインを入れる
  2. RailsのFormヘルパーを使用する(その部分だけデザインが変わることを許容する)
  3. DatePickerとTimePickerをそれぞれ使用し、DateTimeに変換する

今回は極力Materializeのフレームワークに載ったかたちで進めたい、ということもあったので、「3」を選択しました。
その他の選択肢もあるかと思います。
今回は参考になるかと思い、記事にまとめたいと思います。

前提条件

今回はイベントを管理するeventsテーブルに、開始日時を保存するstarted_atがあるとします。
started_atdatetimeでDBに保存するものとします。

ビューのイメージ

上述の通り、MaterializeにDateTimePickerがないので、下記のように入力フォームを分けたいと思います。

モデルにゲッターとセッターを追加

started_atとは別で、started_at_hというメソッドを追加します。
このstarted_at_hに値が入ったら、started_atにDateTimeでセットします。

class Event < ApplicationRecord

  # ...

  def started_at_h=(values)
    self.started_at = if values.is_a?(Hash) && values.key?('date') && values.key?('time')
                        "#{values['date']} #{values['time']}"
                      else
                        Time.zone.now
                      end
  end

  def started_at_h
    if started_at.present?
      { 'date' => started_at.strftime('%Y-%m-%d'),
        'time' => started_at.strftime('%H-%M') }
    else
      { 'date' => '', 'time' => '' }
    end
  end
end

started_at_hにはハッシュでdatetimeを渡し、その値を使ってstarted_atを更新します。
逆に取り出す時は、started_atの値を操作します。

入力フォームに追加

JSファイルにて使用宣言

MaterializeのDatePickerとTimePickerを使用するためには、JSにて宣言する必要があります。

// DatePicker
var elem = document.querySelector('.datepicker');
var instance = M.Datepicker.init(elem, options);
// TimePicker
var elem = document.querySelector('.timepicker');
var instance = M.Timepicker.init(elem, options);

JQueryを使用している場合は、下記のようになります。

$(document).ready(function(){
    $('.datepicker').datepicker();
    $('.timepicker').timepicker();
});

HTML

フォームの方では、strated_at_hのnameで渡します。
nameはstarted_at_h[date]といったかたちで書くことで、submitした後ハッシュでパラメータを投げてくれます。

<%= form_with(model: @event, local: true) do |form| %>

  <!-- ... -->

  <div class="row">
    <div class="col s6 m6">
      <%= form.label :started_at, '開始日' %>
      <%= form.text_field 'started_at_h[date]', { class: 'datepicker',
                                                  value: @event.started_at_h['date'] } %>
    </div>
    <div class="col s6 m6">
      <%= form.label :started_at, '開始時間', class: 'required' %>
      <%= form.text_field 'started_at_h[time]', { class: 'timepicker',
                                                  value: @event.started_at_h['time']} %>
    </div>
  </div>

  <div class="row">
    <%= form.submit %>
  </div>
<% end %>

コントローラーのストロングパラメータを修正

フォームから値を受け取る時は、スタロングパラメータを使用することが多いと思います。
受け取る際には、started_atではなく、started_at_hにし、ハッシュを指定します。

def event_params
  params.fetch(:event, {}).permit(
    # ...
    { started_at_h: [] },
  )
end

テストコードも書いてみる

モデルのstarted_at_hについてテストコードも書きました。
こちらもこれで良いのかよく分からないので参考程度にご覧ください。

require 'rails_helper'

RSpec.describe Event, type: :model do
  describe '#started_at_h' do
    let(:event) { FactoryGirl.build(:event) }
    context 'when set params' do
      it 'ハッシュが渡されればその日付のDateTimeが返る' do
        params = { 'date' => '2018-01-14', 'time' => '10:06' }
        event.started_at_h = params
        expect(event.started_at.strftime('%Y-%m-%d %H-%M')).to eq DateTime.new(2018, 1, 14, 10, 6).strftime('%Y-%m-%d %H-%M')
      end

      it 'ハッシュが渡されなければ本日のDateTimeが返る' do
        params = {}
        event.started_at_h = params
        # 時間がずれてテストがコケる可能性があるので日付だけ比較
        expect(event.started_at.strftime('%Y-%m-%d')).to eq Time.zone.now.strftime('%Y-%m-%d')
      end
    end

    context 'when get params' do
      it '対象の日付が空であれば、空で返る' do
        event.started_at = ''
        expect_param = { 'date' => '', 'time' => '' }
        expect(event.started_at_h).to eq expect_param
      end

      it 'すでに値がセットされていれば、その日付のハッシュが返る' do
        time = Time.zone.now
        event.started_at = time
        expect_param = { 'date' => time.strftime('%Y-%m-%d'), 'time' => time.strftime('%H-%M') }
        expect(event.started_at_h).to eq expect_param
      end
    end
  end
end

さいごに

今回の件に対する良い対応なのかは分かりませんが、一例としてご覧いただければ嬉しく思います。
また、もし他に良い方法があればご教授いただきたく思います。

naoki85

シェア
執筆者:
naoki85

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前