はじめに
RailsでBitemporalDataModelを扱いたかったのですがactiverecord-bitemporalという良いgemがあったので、
いろいろ触ってみた結果を書いていきたいと思います。
今回の記事はactiverecord-bitemporalの紹介です。
##BitemporalDataModelとは何か
そもそもBiTemporalとはどういう意味でしょうか。
「Bi」は接頭辞で2つのという意味を表します、Bicycleとか言いますよね。
「Temporal」は時間のという意味の形容詞です。
つまりBiTemporalで二つの時間のという意味になり、
BiTemporalDataModelは二つの時間のデータモデルになります。
二つの時間は何かと言いますと、
**「システム上の時間」と「事実情報としての時間」**です。
詳しくは説明すると長くなってしまうので、
こちらのスライドが参考になると思います。(特に33ページ目以降)
##使い方
###レコードの作成、更新
ではactiverecord-bitemporalを使ってレコードを作成したり更新したりをしていきたいと思います。
まずは準備です。詳しくはこちら
テーブルを用意しましょう
ActiveRecord::Schema.define(version: 1) do
create_table :employees, force: true do |t|
t.string :name #従業員名
t.string :position #役職
# ActiveRecord::BiTemporal に必要なカラムを追加する
t.integer :bitemporal_id
t.datetime :valid_from #適用日時
t.datetime :valid_to #終了日時
t.datetime :deleted_at #削除日時(論理削除)
end
end
ActiveRecord::Bitemporalの読み込みの設定をします。
class Employee < ActiveRecord::Base
include ActiveRecord::Bitemporal
end
#####では、実際に使っていきたいと思います
employeesというテーブルを作って、従業員の情報をDBに保存していくような例を考えます。
具体的には、
2018年1月1日 田中さんが平社員として入社し、2018年1月3日にレコード作成
↓
2020年1月1日 課長に昇進、 2020年1月10日にレコード作成
↓
2020年1月20日 課長に更新したと思ったが、間違えて家長と入力していたので課長に修正
という例を考えます。
rails consoleで操作していきます。
※簡単のために〇〇時〇〇分〇〇秒は省略しています。(実際には秒まで入ります。)
######まずは最初のレコード作成、
2018年1月1日田中さんが平社員として入社
Employee.create(name: "田中", position: "平社員", valid_from: "2018-01-01")
valid_fromには適用日時を指定できます。
何も指定しなければ入力時点現在の時刻となります。
valid_toも指定できますが、今回指定していないので9999年12月31日となっています。
この時DBは以下のようになります。
id | bitemporal_id | name | position | valid_from | valid_to | created_at | deleted_at |
---|---|---|---|---|---|---|---|
1 | 1 | 田中 | 平社員 | 2018-01-01 | 9999-12-31 | 2018-01-3 | NULL |
######次に、
2020年1月1日 課長に昇進
Employee.valid_at("2020-01-01".to_date).find_by(bitemporal_id: 1).update(position: "課長")
レコードの更新をするとき、適用日時を指定したい場合はvalid_atメソッドで適用日時を指定してからレコードを作成します。
valid_atメソッドで時間を指定しなかった場合、valid_fromは入力時点現在の時刻となります。
ちなみにEmployee.find_by(bitemporal_id: 1).update(position: "課長", valid_from: "2020-01-01")
としても意味はなく、valid_fromは入力時点現在の時刻となります。
この辺りの仕組みは時間があればソースコードを確認しましょう。
このときDBは以下のようになります。
id | bitemporal_id | name | position | valid_from | valid_to | created_at | deleted_at |
---|---|---|---|---|---|---|---|
1 | 1 | 田中 | 平社員 | 2018-01-01 | 9999-12-31 | 2018-01-3 | 2020-01-10 |
2 | 1 | 田中 | 平社員 | 2018-01-01 | 2020-01-01 | 2020-01-10 | NULL |
3 | 1 | 田中 | 課長 | 2020-01-01 | 9999-12-31 | 2020-01-10 | NULL |
######次に、
2020年1月20日 課長に更新したと思ったが、間違えて家長と入力していたので課長に修正
前の部分では課長として更新しましたが誤字のため家長と入力し、それを修正していく場合の時を考えます。
Employee.find_by(bitemporal_id: 1, position: "家長").force_update do |employee|
employee.update(position: "課長")
end
この場合は、updateではなく、force_updateを使います。
家長と入力したレコードは論理削除され、課長のレコードが新しく作られます。
このときDBは以下のようになります。
id | bitemporal_id | name | position | valid_from | valid_to | created_at | deleted_at |
---|---|---|---|---|---|---|---|
1 | 1 | 田中 | 平社員 | 2018-01-01 | 9999-12-31 | 2018-01-3 | 2020-01-10 |
2 | 1 | 田中 | 平社員 | 2018-01-01 | 2020-01-01 | 2020-01-10 | NULL |
3 | 1 | 田中 | 家長 | 2020-01-01 | 9999-12-31 | 2020-01-20 | |
4 | 1 | 田中 | 課長 | 2020-01-01 00:00:00 | 9999-12-31 | 2020-01-20 | NULL |
もしこのとき、論理削除をして新しいレコードを作成するのではなく、強制的に上書きしたい場合は
Employee.find_by(bitemporal_id: 1, position: "家長").force_update do |employee|
employee.update_column(position: "課長")
end
としましょう。
###レコードの検索
後日、執筆予定