【Rails】ActiveRecordによるBiTemporalDataModel

# はじめに

RailsでBitemporalDataModelを扱いたかったのですがactiverecord-bitemporalという良いgemがあったので、
いろいろ触ってみた結果を書いていきたいと思います。
今回の記事はactiverecord-bitemporalの紹介です。

##BitemporalDataModelとは何か

そもそもBiTemporalとはどういう意味でしょうか。
「Bi」は接頭辞で2つのという意味を表します、Bicycleとか言いますよね。
「Temporal」は時間のという意味の形容詞です。
つまりBiTemporalで二つの時間のという意味になり、
BiTemporalDataModelは二つの時間のデータモデルになります。

二つの時間は何かと言いますと、
**「システム上の時間」**と**「事実情報としての時間」**です。

詳しくは説明すると長くなってしまうので、
[こちら](https://speakerdeck.com/f440/implementing-command-history-and-temporal-access)のスライドが参考になると思います。(特に33ページ目以降)


##使い方

###レコードの作成、更新

ではactiverecord-bitemporalを使ってレコードを作成したり更新したりをしていきたいと思います。

まずは準備です。詳しくは[こちら](https://github.com/kufu/activerecord-bitemporal)

テーブルを用意しましょう

~~~db/schema.rb
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の読み込みの設定をします。

~~~app/models/emoloyee.rb
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日田中さんが平社員として入社

~~~.rb
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日 課長に昇進

~~~.rb
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日 課長に更新したと思ったが、間違えて家長と入力していたので課長に修正

前の部分では課長として更新しましたが誤字のため家長と入力し、それを修正していく場合の時を考えます。

~~~.rb
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|


もしこのとき、論理削除をして新しいレコードを作成するのではなく、強制的に上書きしたい場合は

~~~.rb
Employee.find_by(bitemporal_id: 1, position: "家長").force_update do |employee| 
  employee.update_column(position: "課長") 
end
~~~
としましょう。


###レコードの検索

*後日、執筆予定*