LoginSignup
0
0

More than 3 years have passed since last update.

Rails でTimezoneを動的 に保存、表示する実装方針

Last updated at Posted at 2020-10-11

背景と要件

Meetingなどのイベントの時間をあらゆるタイムゾーンを考慮して保存したいという要求がありました。

たとえば、
Meeting1 は Tokyo で 11:00 〜開催される。
Meeting2 は UTC で 10:00〜開催される。

のようなことを表現したい。

実装手順

  • RailsのAPIサーバーのdefaultのTimezoneを固定する。
  • DBにTimezoneを保存する
  • Timezoneを動的に変更するロジックをどこに挿入するかを決める。
  • timzoneを考慮して、DBにtime型が保存されるか確認
  • 保存する際に、timezone情報が無いstringが渡されないようにvalidation

RailsのAPIサーバーのdefaultのTimezoneを固定する。

まずRailsは考慮するタイムゾーンが複数存在します。

今回は、混乱をさけるため一旦以下すべてのTimezoneをUTCにしました。

  • database
  • activereocord
  • system

DBにTimezoneを保存する

timezoneを変更する基準となる変数を保存します。
timezoneは ActiveSupport経由で変更するので、validな値が保存されるように以下のようなvalidationを書けます。

# tz :string
class Meeting < ApplicationRecord 

validates :tz, inclusion: { in: ActiveSupport::TimeZone::MAPPING.values }, presence: true

end

APIでGET時に、timezoneをdynamicに変更するロジックをどこに挿入するかを決める。

APIで値を返すときに、動的にTimezoneを変更して時間を返すロジックをいれます。
今回の場合はSerialzerにくみこみました。このロジックは表示に関わるロジックなので、結果的にViewに近いLayerでいれることになりました。


#  tz :string
class Meeting < ApplicationRecord 
  ~ 省略 ~

  def timezone
    ActiveSupport::TimeZone.new(tz)
  end

  def use_timezone
    Time.use_zone(timezone) do
      yield
    end
  end
end

class MeetingSerializer

  attribute :start_time do |record|
    record.use_timezone do
      record.start_time
    end
  end

end

timzoneを考慮して、DBにtime型が保存されるか確認

保存はtimezone情報がついている、stringを渡せば、ActiveRecordがいい感じにやってくれます。

# actieve_record のtimezone は UTC

[47] pry(main)> meeting
=> #<Meeting:0x00007ff2c7ad4618
 id: "162b1ed7-5502-42f8-8bed-592d2dae7db1",
 start_at: Mon, 05 Oct 2020 16:00:00 UTC +00:00,
 created_at: Tue, 06 Oct 2020 06:28:39 UTC +00:00,
 updated_at: Thu, 08 Oct 2020 02:11:54 UTC +00:00>
[48] pry(main)> meeting.start_at="2020-10-06T01:00:00+0900"
=> "2020-10-06T01:00:00+0900"
[49] pry(main)> meeting.start_at
=> Mon, 05 Oct 2020 16:00:00 UTC +00:00

保存する際に、timezone情報が無いstringが渡されないようにvalidation

以下のような、controllerのconcern moduleを作成し、before_action でvalidationをかませるようにしました。


module ValidateTimeFormat
  class TimeFormatError < StandardError
  end

  extend ActiveSupport::Concern

  included do
    rescue_from ValidateTimeFormat::TimeFormatError, with: :render_time_format_error
  end

  TIME_FORMAT = '%FT%T%z'

  def render_time_format_error(msg)
    render json: { message: msg }, status: :bad_request
  end

  def validate_time_format(time_str)
    DateTime.strptime(time_str, TIME_FORMAT).zone
  rescue Date::Error
    raise ValidateTimeFormat::TimeFormatError, :time_format_is_invalid
  end
end

まとめ

RailsはTime系は考慮するポイントが多いイメージがありました。
今回実装したことで、完璧に理解しました()

twitterもフォローよろしくおねがいします。 !

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