module Hoodoo::ActiveRecord::Dated
Support
mixin for models subclassed from ActiveRecord::Base
providing as-per-API-standard dating support.
The facilities provided here are powerful but relatively complex, so please read through this documentation section in full to understand everything you need to do.
Overview¶ ↑
This mixin adds finder methods to the model it is applied to (see Hoodoo::ActiveRecord::Dated::ClassMethods#dated
and Hoodoo::ActiveRecord::Dated::ClassMethods#dated_at
). These finders require two database tables in order to function correctly - the primary table (the model table) and a history table. When a record is updated it should be moved to the history table and a new record inserted with the new values. When a record is deleted it should be moved to the history table. This can be done manually with application code, or by things like SQL triggers (see later).
Dating is only enabled if the including class explicitly calls the Hoodoo::ActiveRecord::Dated::ClassMethods#dating_enabled
method.
Database table requirements¶ ↑
In all related tables, all date-time values must be stored as UTC.
The primary table must have a unique column named id
and two timestamp columns named updated_at
and created_at
which both need to be set by the application code (the ActiveRecord
timestamps
macro in a migration file defines appropriate columns).
The history table requires the same columns as the primary table with two differences:
-
The history table’s
id
column must be populated with any unique value whilst the history table’suuid
column must be populated with the primary table’sid
value. -
The history table must have two additional columns,
effective_start
andeffective_end
. Theeffective_start
column determines when the history entry becomes effective (inclusive) whilst theeffective_end
determines when the history entry was effective to (exclusive). A record is considered to be effective at a particular time if that time is the same or after theeffective_start
and before theeffective_end
.The
effective_start
must be set to theeffective_end
of the last record with sameuuid
, or to thecreated_at
of the record if there is no previous records with the sameuuid
.The
effective_end
must be set to the current time (UTC) when deleting a record or to the updated record’supdated_at
when updating a record.
Additionally there are two constraints on the history table that must not be broken for the finder methods to function correctly:
-
When adding a record to the history table its
effective_end
must be after all other records in the history table with the sameuuid
. -
When inserting a new record to the primary table its
id
must not exist in the history table.
The history table name defaults to the name of the primary table concatenated with _history_entries
. This can be overriden when calling Hoodoo::ActiveRecord::Dated::ClassMethods#dating_enabled
.
Example:
class Post < ActiveRecord::Base include Hoodoo::ActiveRecord::Dated dating_enabled( history_table_name: 'historical_posts' ) end
Migration assistance¶ ↑
Compatible database migration generators are included in service_shell
. These migrations create the history table and add database triggers (PostgreSQL specific) which will handle the creation of the appropriate history entry when a record is deleted or updated without breaking the history table constraints. See github.com/LoyaltyNZ/service_shell/blob/master/bin/generators/effective_date.rb for more information.
Model instance creation¶ ↑
It is VERY IMPORTANT that you use method Hoodoo::ActiveRecord::Creator::ClassMethods.new_in
to create new resource instances when using dating. You could just manually read the ‘context.request.dated_from` value to ensure that an appropriate creation time is set; presently, `created_at` and `updated_at` are set from the `dated_from` value. However, using `new_in` for this isolates your code from any possible under-the-hood implementation changes therein and future-proofs your code.
Public Class Methods
Instantiates this module when it is included.
Example:
class SomeModel < ActiveRecord::Base include Hoodoo::ActiveRecord::Dated # ... end
model
-
The
ActiveRecord::Base
descendant that is including this module.
# File lib/hoodoo/active/active_record/dated.rb, line 123 def self.included( model ) model.class_attribute( :nz_co_loyalty_hoodoo_dated_with, :instance_predicate => false, :instance_accessor => false ) instantiate( model ) unless model == Hoodoo::ActiveRecord::Base super( model ) end
When instantiated in an ActiveRecord::Base
subclass, all of the Hoodoo::ActiveRecord::Dated::ClassMethods
methods are defined as class methods on the including class.
model
-
The
ActiveRecord::Base
descendant that is including this module.
# File lib/hoodoo/active/active_record/dated.rb, line 141 def self.instantiate( model ) model.extend( ClassMethods ) end