Skip to content

Commit

Permalink
Merge pull request kufu#38 from osyo-manga/add-use-transaction_at
Browse files Browse the repository at this point in the history
Change created_at and deleted_at to transaction_from and transaction_to
  • Loading branch information
osyo-manga authored Nov 5, 2020
2 parents e0ad1f9 + 962d3a9 commit 061ac59
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 24 deletions.
10 changes: 8 additions & 2 deletions lib/activerecord-bitemporal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
module ActiveRecord::Bitemporal
DEFAULT_VALID_FROM = Time.utc(1900, 12, 31).in_time_zone.freeze
DEFAULT_VALID_TO = Time.utc(9999, 12, 31).in_time_zone.freeze
DEFAULT_TRANSACTION_FROM = Time.utc(1900, 12, 31).in_time_zone.freeze
DEFAULT_TRANSACTION_TO = Time.utc(9999, 12, 31).in_time_zone.freeze

extend ActiveSupport::Concern
included do
Expand All @@ -33,8 +35,10 @@ module ClassMethods
include ActiveRecord::Bitemporal::Relation::Finder

DEFAULT_ATTRIBUTES = {
valid_from: ActiveRecord::Bitemporal::DEFAULT_VALID_FROM,
valid_to: ActiveRecord::Bitemporal::DEFAULT_VALID_TO
valid_from: ActiveRecord::Bitemporal::DEFAULT_VALID_FROM,
valid_to: ActiveRecord::Bitemporal::DEFAULT_VALID_TO,
transaction_from: ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_FROM,
transaction_to: ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_TO
}.freeze

def bitemporal_id_key
Expand Down Expand Up @@ -124,6 +128,8 @@ def bitemporalize(enable_strict_by_validates_bitemporal_id: false)
# validations
validates :valid_from, presence: true
validates :valid_to, presence: true
validates :transaction_from, presence: true
validates :transaction_to, presence: true
validate :valid_from_cannot_be_greater_equal_than_valid_to
validate :created_at_cannot_be_greater_equal_than_deleted_at

Expand Down
47 changes: 37 additions & 10 deletions lib/activerecord-bitemporal/bitemporal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,18 @@ def build_new_instance
}
}
end

def has_column?(name)
self.class.column_names.include? name.to_s
end

def update_transaction_to(value)
if has_column?(:deleted_at)
update_columns(transaction_to: value, deleted_at: value)
else
update_columns(transaction_to: value)
end
end
end

refine Object do
Expand Down Expand Up @@ -356,8 +368,22 @@ def each_association(
using EachAssociation

def _create_record(attribute_names = self.attribute_names)
current_time = Time.current

# 自身の `valid_from` を設定
self.valid_from = valid_datetime || Time.current if self.valid_from == ActiveRecord::Bitemporal::DEFAULT_VALID_FROM
self.valid_from = valid_datetime || current_time if self.valid_from == ActiveRecord::Bitemporal::DEFAULT_VALID_FROM

self.transaction_from = current_time if self.transaction_from == ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_FROM

# Assign only if defined created_at and deleted_at
if has_column?(:created_at)
self.transaction_from = self.created_at if changes.key?("created_at")
self.created_at = self.transaction_from
end
if has_column?(:deleted_at)
self.transaction_to = self.deleted_at if changes.key?("deleted_at")
self.deleted_at = self.transaction_to == ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_TO ? nil : self.transaction_to
end

# アソシエーションの子に対して `valid_from` を設定
# MEMO: cache が存在しない場合、 public_send(reflection.name) のタイミングで新しくアソシエーションオブジェクトが生成されるが
Expand Down Expand Up @@ -407,27 +433,27 @@ def _update_row(attribute_names, attempted_action = 'update')
# force_update の場合は既存のレコードを論理削除した上で新しいレコードを生成する
if current_valid_record.present? && force_update?
# 有効なレコードは論理削除する
current_valid_record.update_columns(deleted_at: current_time)
current_valid_record.update_transaction_to(current_time)
# 以降の履歴データはそのまま保存
after_instance.created_at = current_time
after_instance.transaction_from = current_time
after_instance.save!(validate: false)

# 有効なレコードがある場合
elsif current_valid_record.present?
# 有効なレコードは論理削除する
current_valid_record.update_columns(deleted_at: current_time)
current_valid_record.update_transaction_to(current_time)

# 以前の履歴データは valid_to を詰めて保存
before_instance.valid_to = target_datetime
raise ActiveRecord::Rollback if before_instance.valid_from_cannot_be_greater_equal_than_valid_to
before_instance.created_at = current_time
before_instance.transaction_from = current_time
before_instance.save!(validate: false)

# 以降の履歴データは valid_from と valid_to を調整して保存する
after_instance.valid_from = target_datetime
after_instance.valid_to = current_valid_record.valid_to
raise ActiveRecord::Rollback if after_instance.valid_from_cannot_be_greater_equal_than_valid_to
after_instance.created_at = current_time
after_instance.transaction_from = current_time
after_instance.save!(validate: false)

# 有効なレコードがない場合
Expand All @@ -438,13 +464,14 @@ def _update_row(attribute_names, attempted_action = 'update')
# valid_from と valid_to を調整して保存する
after_instance.valid_from = target_datetime
after_instance.valid_to = nearest_instance.valid_from
after_instance.transaction_from = current_time
after_instance.save!(validate: false)
end
# update 後に新しく生成したインスタンスのデータを移行する
@_swapped_id = after_instance.swapped_id
self.valid_from = after_instance.valid_from

return 1
1
# MEMO: Must return false instead of nil, if `#_update_row` failure.
end || false
end
Expand All @@ -460,11 +487,11 @@ def destroy(force_delete: false)

@destroyed = false
_run_destroy_callbacks {
@destroyed = update_columns(deleted_at: current_time)
@destroyed = update_transaction_to(current_time)

# 削除時の状態を履歴レコードとして保存する
duplicated_instance.valid_to = target_datetime
duplicated_instance.created_at = current_time
duplicated_instance.transaction_from = current_time
duplicated_instance.save!(validate: false)
}
raise ActiveRecord::Rollback unless @destroyed
Expand Down Expand Up @@ -533,7 +560,7 @@ def scope_relation(record, relation)
# レコードを更新する時に valid_datetime が valid_from ~ valid_to の範囲外だった場合、
# 一番近い未来の履歴レコードを参照して更新する
# という仕様があるため、それを考慮して valid_to を設定する
if (record.valid_datetime && (record.valid_from..record.valid_to).include?(record.valid_datetime)) == false && (record.persisted?)
if (record.valid_datetime && (record.valid_from..record.valid_to).cover?(record.valid_datetime)) == false && (record.persisted?)
finder_class.where(bitemporal_id: record.bitemporal_id).bitemporal_where_bind("valid_from", :gt, target_datetime).ignore_valid_datetime.order(valid_from: :asc).first.valid_from
else
valid_to
Expand Down
25 changes: 23 additions & 2 deletions spec/activerecord-bitemporal/bitemporal_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
end

describe ".create" do
let(:attributes) { {} }

context "creating" do
let(:attributes) { {} }
subject { -> { Employee.create!(name: "Tom", **attributes) } }
it { is_expected.to change(Employee, :call_after_save_count).by(1) }
end

context "created" do
let(:attributes) { {} }
subject { Employee.create!(name: "Tom", **attributes) }

context "with `bitemporal_id`" do
Expand Down Expand Up @@ -58,6 +58,27 @@
it { is_expected.to have_attributes(valid_from: time, valid_to: Time.utc(9999, 12, 31).in_time_zone) }
end
end

context "with transaction_from, created_at" do
context "only transaction_from" do
let(:transaction_from) { "2019/01/01".to_time }
subject { Employee.create(transaction_from: transaction_from) }
it { is_expected.to have_attributes(transaction_from: transaction_from, created_at: transaction_from) }
end

context "only created_at" do
let(:created_at) { "2019/01/01".to_time }
subject { Employee.create(created_at: created_at) }
it { is_expected.to have_attributes(created_at: created_at, transaction_from: created_at) }
end

context "created_at and transaction_from" do
let(:created_at) { "2019/01/01".to_time }
let(:transaction_from) { "2019/08/01".to_time }
subject { Employee.create(transaction_from: transaction_from, created_at: created_at) }
it { is_expected.to have_attributes(created_at: created_at, transaction_from: created_at) }
end
end
end

describe ".find" do
Expand Down
2 changes: 2 additions & 0 deletions spec/activerecord-bitemporal/polymorphic_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
t.datetime :valid_from
t.datetime :valid_to
t.datetime :deleted_at
t.datetime :transaction_from
t.datetime :transaction_to

t.timestamps
end
Expand Down
6 changes: 6 additions & 0 deletions spec/activerecord-bitemporal/through_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
t.datetime :valid_from
t.datetime :valid_to
t.datetime :deleted_at
t.datetime :transaction_from
t.datetime :transaction_to

t.timestamps
end
Expand All @@ -22,6 +24,8 @@
t.datetime :valid_from
t.datetime :valid_to
t.datetime :deleted_at
t.datetime :transaction_from
t.datetime :transaction_to

t.timestamps
end
Expand All @@ -36,6 +40,8 @@
t.datetime :valid_from
t.datetime :valid_to
t.datetime :deleted_at
t.datetime :transaction_from
t.datetime :transaction_to

t.timestamps
end
Expand Down
Loading

0 comments on commit 061ac59

Please sign in to comment.