Skip to content

Commit

Permalink
feat: Support custom temporal upper bound with Hoardable.on (#48)
Browse files Browse the repository at this point in the history
* feat: Support custom temporal upper bound with Hoardable.on

* fix: Round time in test to prevent flakiness

* fix: Linting

* chore: Renamed .on to .travel_to for more clarity

* Update lib/hoardable/engine.rb

---------

Co-authored-by: justin talbott <[email protected]>
  • Loading branch information
buntine and waymondo authored Oct 21, 2024
1 parent bb353cb commit 8768c2c
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 2 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ version.changes # => { "title"=> ["Title", "New Title"] }
version.hoardable_operation # => "update"
```

### Overriding the temporal range

When calculating the temporal range for a given version, the default upper bound is `Time.now.utc`.

You can, however, use the `Hoardable.travel_to` class method to specify a custom upper bound for the time range. This allows
you to specify the datetime that a particular change should be recorded at by passing a block:

```ruby
Hoardable.travel_to(2.weeks.ago) do
post.destroy!
end
```

Note: If the provided datetime pre-dates the calculated lower bound then an `InvalidTemporalUpperBoundError` will be raised.

### Model Callbacks

Sometimes you might want to do something with a version after it gets inserted to the database. You
Expand Down
9 changes: 8 additions & 1 deletion lib/hoardable/database_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,14 @@ def refreshable_column_names
end

def initialize_temporal_range
((previous_temporal_tsrange_end || hoardable_source_epoch)..Time.now.utc)
upper_bound = Hoardable.instance_variable_get("@travel_to") || Time.now.utc
lower_bound = (previous_temporal_tsrange_end || hoardable_source_epoch)

if upper_bound < lower_bound
raise InvalidTemporalUpperBoundError.new(upper_bound, lower_bound)
end

(lower_bound..upper_bound)
end

def initialize_hoardable_data
Expand Down
10 changes: 10 additions & 0 deletions lib/hoardable/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ def at(datetime)
@at = nil
end

# Allows calling code to set the upper bound for the temporal range for recorded audits.
#
# @param datetime [DateTime] the datetime to temporally record versions at
def travel_to(datetime)
@travel_to = datetime
yield
ensure
@travel_to = nil
end

# @!visibility private
def logger
@logger ||= ActiveSupport::TaggedLogging.new(Logger.new($stdout))
Expand Down
10 changes: 10 additions & 0 deletions lib/hoardable/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,14 @@ def initialize(source_table_name)
LOG
end
end

# An error to be raised when the provided temporal upper bound is before the calcualated lower bound.
class InvalidTemporalUpperBoundError < Error
def initialize(upper, lower)
super(<<~LOG)
'The supplied value to `Hoardable.travel_to` (#{upper}) is before the calculated lower bound (#{lower}).
You must provide a datetime > the lower bound.
LOG
end
end
end
2 changes: 1 addition & 1 deletion lib/hoardable/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Hoardable
VERSION = "0.16.0"
VERSION = "0.16.1"
end
29 changes: 29 additions & 0 deletions test/test_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,35 @@ def create_comments_and_destroy_post
end
end

test "can influence the upper bound of the temporal range with Hoardable.travel_to" do
created_at = Time.now.utc - 10 * 86_400 # 10 days ago
deleted_at = Time.now.utc - 5 * 86_400 # 5 days ago

comment =
post.comments.create!(body: "Comment 1", created_at: created_at, updated_at: created_at)

Hoardable.travel_to(deleted_at) { comment.destroy! }

temporal_range = CommentVersion.where(hoardable_id: comment.id).first._during

assert_equal Comment.all.size, 0
assert_equal temporal_range.max.round, deleted_at.round
end

test "will error if the upper bound of the temporal range with Hoardable.travel_to is less than the lower bound" do
created_at = Time.now.utc - 10 * 86_400 # 10 days ago
deleted_at = Time.now.utc - 12 * 86_400 # 12 days ago

comment =
post.comments.create!(body: "Comment 1", created_at: created_at, updated_at: created_at)

Hoardable.travel_to(deleted_at) do
assert_raises(Hoardable::InvalidTemporalUpperBoundError) { comment.destroy! }
end

assert_equal Comment.all.size, 1
end

test "cannot save a hoardable source record that is actually a version" do
post
datetime = DateTime.now
Expand Down

0 comments on commit 8768c2c

Please sign in to comment.