From 876e7652bee541156ce8b7b8a9c9abfb12c41791 Mon Sep 17 00:00:00 2001 From: Geremia Taglialatela Date: Thu, 2 Jun 2022 15:54:52 +0200 Subject: [PATCH] Add Safe As Of feature --- lib/chrono_model/time_gate.rb | 1 + lib/chrono_model/time_machine.rb | 3 +++ lib/chrono_model/time_machine/safe_as_of.rb | 15 +++++++++++++++ spec/chrono_model/time_machine/as_of_spec.rb | 2 +- spec/chrono_model/time_machine/history_spec.rb | 2 +- spec/chrono_model/time_machine/keep_cool_spec.rb | 2 +- spec/chrono_model/time_machine/safe_as_of_spec.rb | 14 ++++++++++++++ spec/chrono_model/time_machine/time_query_spec.rb | 4 ++-- spec/support/time_machine/structure.rb | 7 +++++-- 9 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 lib/chrono_model/time_machine/safe_as_of.rb create mode 100644 spec/chrono_model/time_machine/safe_as_of_spec.rb diff --git a/lib/chrono_model/time_gate.rb b/lib/chrono_model/time_gate.rb index 1adce51a..7953ec8b 100644 --- a/lib/chrono_model/time_gate.rb +++ b/lib/chrono_model/time_gate.rb @@ -9,6 +9,7 @@ module TimeGate include ChronoModel::Patches::AsOfTimeHolder module ClassMethods + include ChronoModel::TimeMachine::SafeAsOf include ChronoModel::TimeMachine::Timeline def as_of(time) diff --git a/lib/chrono_model/time_machine.rb b/lib/chrono_model/time_machine.rb index 6649186e..3041b917 100644 --- a/lib/chrono_model/time_machine.rb +++ b/lib/chrono_model/time_machine.rb @@ -1,3 +1,4 @@ +require 'chrono_model/time_machine/safe_as_of' require 'chrono_model/time_machine/time_query' require 'chrono_model/time_machine/timeline' require 'chrono_model/time_machine/history_model' @@ -63,6 +64,8 @@ def self.define_history_model_for(model) end module ClassMethods + include ChronoModel::TimeMachine::SafeAsOf + # Identify this class as the parent, non-history, class. # def history? diff --git a/lib/chrono_model/time_machine/safe_as_of.rb b/lib/chrono_model/time_machine/safe_as_of.rb new file mode 100644 index 00000000..a86df0d8 --- /dev/null +++ b/lib/chrono_model/time_machine/safe_as_of.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ChronoModel + module TimeMachine + module SafeAsOf + def safe_as_of(time) + if time.present? + as_of(time) + else + all + end + end + end + end +end diff --git a/spec/chrono_model/time_machine/as_of_spec.rb b/spec/chrono_model/time_machine/as_of_spec.rb index 9ea3d0bb..8ee2e51b 100644 --- a/spec/chrono_model/time_machine/as_of_spec.rb +++ b/spec/chrono_model/time_machine/as_of_spec.rb @@ -9,7 +9,7 @@ it { expect(Foo.as_of($t.foos[0].ts[0])).to eq [$t.foo, $t.foos[0]] } it { expect(Foo.as_of($t.foos[1].ts[0])).to eq [$t.foo, $t.foos[0], $t.foos[1]] } - it { expect(Foo.as_of(Time.now)).to eq [$t.foo, $t.foos[0], $t.foos[1]] } + it { expect(Foo.as_of(Time.now)).to eq [$t.foo, $t.foos[0], $t.foos[1], $t.goo_foos[0], $t.goo_foos[1]] } it { expect(Bar.as_of($t.foos[1].ts[0])).to eq [$t.bar] } diff --git a/spec/chrono_model/time_machine/history_spec.rb b/spec/chrono_model/time_machine/history_spec.rb index 1c12371a..0ace5bb2 100644 --- a/spec/chrono_model/time_machine/history_spec.rb +++ b/spec/chrono_model/time_machine/history_spec.rb @@ -6,7 +6,7 @@ describe '.history' do let(:foo_history) { - ['foo', 'foo bar', 'new foo', 'foo 0', 'foo 1'] + ['foo', 'foo bar', 'new foo', 'foo 0', 'foo 1', 'goo foo 0', 'goo foo 1'] } let(:bar_history) { diff --git a/spec/chrono_model/time_machine/keep_cool_spec.rb b/spec/chrono_model/time_machine/keep_cool_spec.rb index ab3f8e03..99802d6d 100644 --- a/spec/chrono_model/time_machine/keep_cool_spec.rb +++ b/spec/chrono_model/time_machine/keep_cool_spec.rb @@ -5,7 +5,7 @@ include ChronoTest::TimeMachine::Helpers describe 'does not interfere with AR standard behaviour' do - let(:all_foos) { [$t.foo] + $t.foos } + let(:all_foos) { [$t.foo] + $t.foos + $t.goo_foos } let(:all_bars) { [$t.bar] + $t.bars } it { expect(Foo.count).to eq all_foos.size } diff --git a/spec/chrono_model/time_machine/safe_as_of_spec.rb b/spec/chrono_model/time_machine/safe_as_of_spec.rb new file mode 100644 index 00000000..42ca9f48 --- /dev/null +++ b/spec/chrono_model/time_machine/safe_as_of_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' +require 'support/time_machine/structure' + +describe ChronoModel::TimeMachine do + include ChronoTest::TimeMachine::Helpers + + describe '.safe_as_of' do + it { expect(Foo.safe_as_of($t.foos[0].ts[0])).to eq Foo.as_of($t.foos[0].ts[0]) } + it { expect(Foo.safe_as_of(nil)).to eq Foo.all } + + it { expect(FooGoo.safe_as_of($t.goo_foos[0].ts[0]).first.foos).to eq $t.goo.as_of($t.goo_foos[0].ts[0]).foos } + it { expect(FooGoo.safe_as_of(nil).first.foos).to eq $t.goo.foos } + end +end diff --git a/spec/chrono_model/time_machine/time_query_spec.rb b/spec/chrono_model/time_machine/time_query_spec.rb index 61d9c139..5061311d 100644 --- a/spec/chrono_model/time_machine/time_query_spec.rb +++ b/spec/chrono_model/time_machine/time_query_spec.rb @@ -15,9 +15,9 @@ class ::Event < ActiveRecord::Base # Main timeline quick test # - it { expect(Foo.history.time_query(:after, :now, inclusive: true).count).to eq 3 } + it { expect(Foo.history.time_query(:after, :now, inclusive: true).count).to eq 5 } it { expect(Foo.history.time_query(:after, :now, inclusive: false).count).to eq 0 } - it { expect(Foo.history.time_query(:before, :now, inclusive: true).count).to eq 5 } + it { expect(Foo.history.time_query(:before, :now, inclusive: true).count).to eq 7 } it { expect(Foo.history.time_query(:before, :now, inclusive: false).count).to eq 2 } it { expect(Foo.history.past.size).to eq 2 } diff --git a/spec/support/time_machine/structure.rb b/spec/support/time_machine/structure.rb index 376ec74f..40bbbc6d 100644 --- a/spec/support/time_machine/structure.rb +++ b/spec/support/time_machine/structure.rb @@ -99,7 +99,7 @@ class ::Foo < ActiveRecord::Base class ::FooGoo < ActiveRecord::Base include ChronoModel::TimeGate - has_many :foos, inverse_of: :goo + has_many :foos, inverse_of: :goo, foreign_key: :goo_id end class ::Moo < ActiveRecord::Base @@ -126,7 +126,7 @@ class ::SubSubBar < ActiveRecord::Base # Master timeline, used in multiple specs. It is defined here # as a global variable to be able to be shared across specs. # - $t = Struct.new(:foo, :bar, :baz, :subbar, :foos, :bars, :boos, :moos).new + $t = Struct.new(:foo, :bar, :baz, :subbar, :foos, :bars, :boos, :moos, :goo, :goo_foos).new # Set up associated records, with intertwined updates # @@ -155,4 +155,7 @@ class ::SubSubBar < ActiveRecord::Base $t.moos = Array.new(2) { |i| ts_eval { Moo.create! name: "moo #{i}", boos: $t.boos } } $t.baz = Baz.create! name: 'baz', bar: $t.bar + + $t.goo = FooGoo.create! name: 'goo' + $t.goo_foos = Array.new(2) { |i| ts_eval { Foo.create! name: "goo foo #{i}", goo: $t.goo } } end