activerecord-temporal 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +778 -7
  4. data/lib/activerecord/temporal/application_versioning/application_versioned.rb +122 -0
  5. data/lib/activerecord/temporal/application_versioning.rb +4 -68
  6. data/lib/activerecord/temporal/patches/association_reflection.rb +10 -3
  7. data/lib/activerecord/temporal/patches/join_dependency.rb +4 -0
  8. data/lib/activerecord/temporal/patches/merger.rb +1 -1
  9. data/lib/activerecord/temporal/patches/relation.rb +10 -6
  10. data/lib/activerecord/temporal/patches/through_association.rb +4 -1
  11. data/lib/activerecord/temporal/{as_of_query → querying}/association_macros.rb +1 -1
  12. data/lib/activerecord/temporal/querying/association_scope.rb +55 -0
  13. data/lib/activerecord/temporal/{as_of_query → querying}/association_walker.rb +1 -1
  14. data/lib/activerecord/temporal/querying/predicate_builder/contains_handler.rb +24 -0
  15. data/lib/activerecord/temporal/querying/predicate_builder/handlers.rb +31 -0
  16. data/lib/activerecord/temporal/querying/query_methods.rb +37 -0
  17. data/lib/activerecord/temporal/querying/scope_registry.rb +95 -0
  18. data/lib/activerecord/temporal/querying/scoping.rb +70 -0
  19. data/lib/activerecord/temporal/{as_of_query → querying}/time_dimensions.rb +13 -3
  20. data/lib/activerecord/temporal/querying/where_clause_refinement.rb +17 -0
  21. data/lib/activerecord/temporal/querying.rb +95 -0
  22. data/lib/activerecord/temporal/scoping.rb +7 -0
  23. data/lib/activerecord/temporal/system_versioning/history_model.rb +47 -0
  24. data/lib/activerecord/temporal/system_versioning/history_model_namespace.rb +45 -0
  25. data/lib/activerecord/temporal/system_versioning/history_models.rb +29 -0
  26. data/lib/activerecord/temporal/system_versioning/schema_creation.rb +8 -5
  27. data/lib/activerecord/temporal/system_versioning/schema_definitions.rb +10 -8
  28. data/lib/activerecord/temporal/system_versioning/schema_statements.rb +62 -24
  29. data/lib/activerecord/temporal/system_versioning/system_versioned.rb +13 -0
  30. data/lib/activerecord/temporal/system_versioning.rb +6 -18
  31. data/lib/activerecord/temporal/version.rb +1 -1
  32. data/lib/activerecord/temporal.rb +64 -33
  33. metadata +23 -15
  34. data/lib/activerecord/temporal/as_of_query/association_scope.rb +0 -54
  35. data/lib/activerecord/temporal/as_of_query/query_methods.rb +0 -24
  36. data/lib/activerecord/temporal/as_of_query/scope_registry.rb +0 -38
  37. data/lib/activerecord/temporal/as_of_query.rb +0 -109
  38. data/lib/activerecord/temporal/system_versioning/model.rb +0 -37
  39. data/lib/activerecord/temporal/system_versioning/namespace.rb +0 -34
@@ -0,0 +1,95 @@
1
+ module ActiveRecord::Temporal
2
+ module Querying
3
+ class RangeError < StandardError; end
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def resolve_time_coords(time_or_time_coords)
9
+ return time_or_time_coords if time_or_time_coords.is_a?(Hash)
10
+
11
+ {default_time_dimension.to_sym => time_or_time_coords}
12
+ end
13
+ end
14
+
15
+ included do
16
+ include AssociationMacros
17
+ include TimeDimensions
18
+ include PredicateBuilder::Handlers
19
+
20
+ delegate :resolve_time_coords, to: :class
21
+
22
+ default_scope do
23
+ at_time(Querying::ScopeRegistry.global_constraints_for(time_dimensions))
24
+ end
25
+
26
+ scope :as_of, ->(time) do
27
+ time_coords = resolve_time_coords(time)
28
+
29
+ at_time(time_coords).time_tags(time_coords)
30
+ end
31
+
32
+ scope :at_time, ->(time) do
33
+ time_coords = resolve_time_coords(time)
34
+
35
+ constraints = time_coords.slice(*time_dimension_columns)
36
+
37
+ return if constraints.empty?
38
+
39
+ rewhere_contains(constraints.transform_values { |v| contains(v) })
40
+ end
41
+ end
42
+
43
+ def time_tags
44
+ @time_tags || {}
45
+ end
46
+
47
+ def time_tags=(value)
48
+ @time_tags = value&.slice(*time_dimensions)
49
+ end
50
+
51
+ def time_tag
52
+ time_tags[default_time_dimension]
53
+ end
54
+
55
+ def time_tags_for(time_dimensions)
56
+ time_tags.slice(*time_dimensions)
57
+ end
58
+
59
+ def as_of!(time)
60
+ time_coords = resolve_time_coords(time)
61
+
62
+ ensure_time_tags_in_bounds!(time_coords)
63
+
64
+ reload
65
+
66
+ self.time_tags = time_coords
67
+ end
68
+
69
+ def as_of(time)
70
+ time_coords = resolve_time_coords(time)
71
+
72
+ self.class.as_of(time_coords).find_by(self.class.primary_key => [id])
73
+ end
74
+
75
+ def initialize_time_tags_from_relation(relation)
76
+ associations = relation.includes_values | relation.eager_load_values
77
+
78
+ self.time_tags = relation.time_tag_values
79
+
80
+ AssociationWalker.each_target(self, associations) do |target|
81
+ target.time_tags = relation.time_tag_values
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def ensure_time_tags_in_bounds!(time_tags)
88
+ time_tags.each do |dimension, time|
89
+ if time_dimension_column?(dimension) && !time_dimension(dimension).cover?(time)
90
+ raise RangeError, "#{time} is outside of '#{dimension}' range"
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveRecord::Temporal
2
+ module Scoping
3
+ def temporal_scoping
4
+ Querying::Scoping
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,47 @@
1
+ module ActiveRecord::Temporal
2
+ module SystemVersioning
3
+ module HistoryModel
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def polymorphic_class_for(name)
8
+ super.version_model
9
+ end
10
+
11
+ def sti_name
12
+ superclass.sti_name
13
+ end
14
+
15
+ def find_sti_class(type_name)
16
+ superclass.send(:find_sti_class, type_name).history_model
17
+ end
18
+
19
+ def finder_needs_type_condition?
20
+ superclass.finder_needs_type_condition?
21
+ end
22
+ end
23
+
24
+ included do
25
+ include Querying
26
+
27
+ if include?(SystemVersioned)
28
+ self.table_name = history_table_name
29
+ self.primary_key = Array(primary_key) + [:system_period]
30
+ end
31
+
32
+ self.time_dimensions = time_dimensions + [:system_period]
33
+
34
+ reflect_on_all_associations.each do |reflection|
35
+ next if reflection.scope&.temporal_scope?
36
+
37
+ send(
38
+ reflection.macro,
39
+ reflection.name,
40
+ reflection.scope,
41
+ **reflection.options.merge(temporal: true)
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ module ActiveRecord::Temporal
2
+ module SystemVersioning
3
+ module HistoryModelNamespace
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def const_missing(model_name)
8
+ model = join(@root, model_name).constantize
9
+ rescue NameError
10
+ super
11
+ else
12
+ unless model.is_a?(Class) && model < ActiveRecord::Base
13
+ raise NameError, "#{model} is not a descendent of ActiveRecord::Base"
14
+ end
15
+
16
+ history_model = Class.new(model) do
17
+ include HistoryModel
18
+ end
19
+
20
+ const_set(model_name, history_model)
21
+ end
22
+
23
+ def namespace(name, &block)
24
+ new_namespace = Module.new do
25
+ include HistoryModelNamespace
26
+ end
27
+
28
+ const_set(name, new_namespace)
29
+
30
+ new_namespace.root(join(@root, name))
31
+
32
+ new_namespace.instance_eval(&block) if block
33
+ end
34
+
35
+ def root(name)
36
+ @root = name
37
+ end
38
+
39
+ def join(base, name)
40
+ [base, name].compact.join("::")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ module ActiveRecord::Temporal
2
+ module SystemVersioning
3
+ module HistoryModels
4
+ class Error < StandardError; end
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ delegate :at_time, :as_of, to: :history
10
+
11
+ def history_model
12
+ raise Error, "abstract classes cannot have a history model" if abstract_class?
13
+
14
+ [history_model_namespace, name].join("::").constantize
15
+ end
16
+
17
+ def history
18
+ ActiveRecord::Relation.create(history_model)
19
+ end
20
+
21
+ private
22
+
23
+ def history_model_namespace
24
+ History
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -30,7 +30,8 @@ module ActiveRecord::Temporal
30
30
  verb: :insert,
31
31
  source_table: o.source_table,
32
32
  history_table: o.history_table,
33
- columns: o.columns
33
+ columns: o.columns,
34
+ gem_version: o.gem_version
34
35
  }
35
36
 
36
37
  <<~SQL
@@ -52,7 +53,7 @@ module ActiveRecord::Temporal
52
53
 
53
54
  def visit_UpdateHookDefinition(o)
54
55
  column_names = o.columns.map { |c| quote_column_name(c) }
55
- primary_key_quoted = o.primary_key.map { |c| quote_column_name(c) }
56
+ primary_key_quoted = Array(o.primary_key).map { |c| quote_column_name(c) }
56
57
  fields = column_names.join(", ")
57
58
  values = column_names.map { |c| "NEW.#{c}" }.join(", ")
58
59
  update_pk_predicates = primary_key_quoted.map { |c| "#{c} = OLD.#{c}" }.join(" AND ")
@@ -64,7 +65,8 @@ module ActiveRecord::Temporal
64
65
  source_table: o.source_table,
65
66
  history_table: o.history_table,
66
67
  columns: o.columns,
67
- primary_key: o.primary_key
68
+ primary_key: o.primary_key,
69
+ gem_version: o.gem_version
68
70
  }
69
71
 
70
72
  <<~SQL
@@ -94,14 +96,15 @@ module ActiveRecord::Temporal
94
96
  end
95
97
 
96
98
  def visit_DeleteHookDefinition(o)
97
- primary_key_quoted = o.primary_key.map { |c| quote_column_name(c) }
99
+ primary_key_quoted = Array(o.primary_key).map { |c| quote_column_name(c) }
98
100
  function_name = versioning_function_name(o.source_table, :delete)
99
101
  pk_predicates = primary_key_quoted.map { |c| "#{c} = OLD.#{c}" }.join(" AND ")
100
102
  metadata = {
101
103
  verb: :delete,
102
104
  source_table: o.source_table,
103
105
  history_table: o.history_table,
104
- primary_key: o.primary_key
106
+ primary_key: o.primary_key,
107
+ gem_version: o.gem_version
105
108
  }
106
109
 
107
110
  <<~SQL
@@ -1,37 +1,39 @@
1
1
  module ActiveRecord::Temporal
2
2
  module SystemVersioning
3
3
  class VersioningHookDefinition
4
- attr_accessor :source_table, :history_table, :columns, :primary_key
4
+ attr_accessor :source_table, :history_table, :columns, :primary_key, :gem_version
5
5
 
6
6
  def initialize(
7
7
  source_table,
8
8
  history_table,
9
9
  columns:,
10
- primary_key:
10
+ primary_key:,
11
+ gem_version:
11
12
  )
12
13
  @source_table = source_table
13
14
  @history_table = history_table
14
15
  @columns = columns
15
16
  @primary_key = primary_key
17
+ @gem_version = gem_version
16
18
  end
17
19
 
18
20
  def insert_hook
19
- InsertHookDefinition.new(@source_table, @history_table, @columns)
21
+ InsertHookDefinition.new(@source_table, @history_table, @columns, @gem_version)
20
22
  end
21
23
 
22
24
  def update_hook
23
- UpdateHookDefinition.new(@source_table, @history_table, @columns, @primary_key)
25
+ UpdateHookDefinition.new(@source_table, @history_table, @columns, @primary_key, @gem_version)
24
26
  end
25
27
 
26
28
  def delete_hook
27
- DeleteHookDefinition.new(@source_table, @history_table, @primary_key)
29
+ DeleteHookDefinition.new(@source_table, @history_table, @primary_key, @gem_version)
28
30
  end
29
31
  end
30
32
 
31
- InsertHookDefinition = Struct.new(:source_table, :history_table, :columns)
33
+ InsertHookDefinition = Struct.new(:source_table, :history_table, :columns, :gem_version)
32
34
 
33
- UpdateHookDefinition = Struct.new(:source_table, :history_table, :columns, :primary_key)
35
+ UpdateHookDefinition = Struct.new(:source_table, :history_table, :columns, :primary_key, :gem_version)
34
36
 
35
- DeleteHookDefinition = Struct.new(:source_table, :history_table, :primary_key)
37
+ DeleteHookDefinition = Struct.new(:source_table, :history_table, :primary_key, :gem_version)
36
38
  end
37
39
  end
@@ -2,45 +2,67 @@ module ActiveRecord::Temporal
2
2
  module SystemVersioning
3
3
  module SchemaStatements
4
4
  def create_versioning_hook(source_table, history_table, **options)
5
- columns = options.fetch(:columns)&.map(&:to_s)
6
- primary_key = Array(options.fetch(:primary_key, :id))
5
+ options.assert_valid_keys(:columns, :primary_key)
7
6
 
8
- ensure_table_exists!(source_table)
9
- ensure_table_exists!(history_table)
10
- ensure_columns_match!(source_table, history_table, columns)
11
- ensure_columns_exists!(source_table, primary_key)
7
+ columns = options.fetch(:columns, :all)
8
+ primary_key = options.fetch(:primary_key, :id)
9
+
10
+ column_names = if columns == :all
11
+ columns(source_table).map(&:name)
12
+ else
13
+ Array(columns).map(&:to_s)
14
+ end
15
+
16
+ primary_key = if primary_key.is_a?(Array) && primary_key.length == 1
17
+ primary_key.first
18
+ else
19
+ primary_key
20
+ end
21
+
22
+ assert_table_exists!(source_table)
23
+ assert_table_exists!(history_table)
24
+ assert_columns_match!(source_table, history_table, column_names)
25
+ assert_columns_exists!(source_table, Array(primary_key))
26
+ assert_primary_key_matches!(source_table, Array(primary_key))
12
27
 
13
28
  schema_creation = SchemaCreation.new(self)
14
29
 
15
30
  hook_definition = VersioningHookDefinition.new(
16
31
  source_table,
17
32
  history_table,
18
- columns: columns,
19
- primary_key: primary_key
33
+ columns: column_names,
34
+ primary_key: primary_key,
35
+ gem_version: VERSION
20
36
  )
21
37
 
22
38
  execute schema_creation.accept(hook_definition)
23
39
  end
24
40
 
25
- def drop_versioning_hook(source_table, history_table, columns: nil)
41
+ def drop_versioning_hook(source_table, history_table, **options)
42
+ options.assert_valid_keys(:columns, :primary_key, :if_exists)
43
+
26
44
  %i[insert update delete].each do |verb|
27
45
  function_name = versioning_function_name(source_table, verb)
28
46
 
29
- execute "DROP FUNCTION #{function_name}() CASCADE"
47
+ sql = "DROP FUNCTION"
48
+ sql << " IF EXISTS" if options[:if_exists]
49
+ sql << " #{function_name}() CASCADE"
50
+
51
+ execute sql
30
52
  end
31
53
  end
32
54
 
33
55
  def versioning_hook(source_table)
34
56
  update_function_name = versioning_function_name(source_table, :update)
35
57
 
36
- row = execute(<<~SQL.squish).first
58
+ row = exec_query(<<~SQL.squish, "SQL", [update_function_name]).first
37
59
  SELECT
38
60
  pg_proc.proname as function_name,
39
61
  obj_description(pg_proc.oid, 'pg_proc') as comment
40
62
  FROM pg_proc
41
63
  JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
42
64
  WHERE pg_namespace.nspname NOT IN ('pg_catalog', 'information_schema')
43
- AND pg_proc.proname = '#{update_function_name}'
65
+ AND pg_proc.proname = $1
44
66
  SQL
45
67
 
46
68
  return unless row
@@ -51,27 +73,33 @@ module ActiveRecord::Temporal
51
73
  metadata["source_table"],
52
74
  metadata["history_table"],
53
75
  columns: metadata["columns"],
54
- primary_key: metadata["primary_key"]
76
+ primary_key: metadata["primary_key"],
77
+ gem_version: metadata["gem_version"]
55
78
  )
56
79
  end
57
80
 
58
81
  def change_versioning_hook(source_table, history_table, options)
82
+ options.assert_valid_keys(:add_columns, :remove_columns)
83
+
59
84
  add_columns = (options[:add_columns] || []).map(&:to_s)
60
85
  remove_columns = (options[:remove_columns] || []).map(&:to_s)
61
86
 
62
- ensure_table_exists!(source_table)
63
- ensure_table_exists!(history_table)
64
- ensure_columns_match!(source_table, history_table, add_columns)
87
+ assert_table_exists!(source_table)
88
+ assert_table_exists!(history_table)
89
+ assert_columns_match!(source_table, history_table, add_columns)
65
90
 
66
91
  hook_definition = versioning_hook(source_table)
67
92
 
68
- ensure_hook_has_columns!(hook_definition, remove_columns)
93
+ assert_hook_has_columns!(hook_definition, remove_columns)
69
94
 
70
95
  drop_versioning_hook(source_table, history_table)
71
96
 
72
97
  new_columns = hook_definition.columns + add_columns - remove_columns
73
98
 
74
- create_versioning_hook(source_table, history_table, columns: new_columns)
99
+ create_versioning_hook source_table,
100
+ history_table,
101
+ columns: new_columns,
102
+ primary_key: hook_definition.primary_key
75
103
  end
76
104
 
77
105
  def history_table(source_table)
@@ -89,15 +117,18 @@ module ActiveRecord::Temporal
89
117
 
90
118
  private
91
119
 
92
- def ensure_table_exists!(table_name)
120
+ def validate_create_versioning_hook_options!(options)
121
+ end
122
+
123
+ def assert_table_exists!(table_name)
93
124
  return if table_exists?(table_name)
94
125
 
95
126
  raise ArgumentError, "table '#{table_name}' does not exist"
96
127
  end
97
128
 
98
- def ensure_columns_match!(source_table, history_table, column_names)
99
- ensure_columns_exists!(source_table, column_names)
100
- ensure_columns_exists!(history_table, column_names)
129
+ def assert_columns_match!(source_table, history_table, column_names)
130
+ assert_columns_exists!(source_table, column_names)
131
+ assert_columns_exists!(history_table, column_names)
101
132
 
102
133
  column_names.each do |column|
103
134
  source_column = columns(source_table).find { _1.name == column }
@@ -109,7 +140,7 @@ module ActiveRecord::Temporal
109
140
  end
110
141
  end
111
142
 
112
- def ensure_columns_exists!(table_name, column_names)
143
+ def assert_columns_exists!(table_name, column_names)
113
144
  column_names.each do |column|
114
145
  next if column_exists?(table_name, column)
115
146
 
@@ -117,7 +148,14 @@ module ActiveRecord::Temporal
117
148
  end
118
149
  end
119
150
 
120
- def ensure_hook_has_columns!(hook, column_names)
151
+ def assert_primary_key_matches!(source_table, primary_key)
152
+ primary_key = primary_key&.map(&:to_s)
153
+ unless Array(primary_key(source_table)) == primary_key
154
+ raise ArgumentError, "table '#{source_table}' does not have primary key #{primary_key}"
155
+ end
156
+ end
157
+
158
+ def assert_hook_has_columns!(hook, column_names)
121
159
  column_names.each do |column_name|
122
160
  next if hook.columns.include?(column_name)
123
161
 
@@ -0,0 +1,13 @@
1
+ module ActiveRecord::Temporal
2
+ module SystemVersioning
3
+ module SystemVersioned
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def history_table_name
8
+ table_name + "_history" if table_name
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,25 +2,13 @@ module ActiveRecord::Temporal
2
2
  module SystemVersioning
3
3
  extend ActiveSupport::Concern
4
4
 
5
- class_methods do
6
- def history_table
7
- connection.history_table(table_name)
8
- end
9
-
10
- def primary_key_from_db
11
- Array(connection.primary_key(table_name)).map(&:to_sym)
12
- end
13
-
14
- def version_model
15
- "Version::#{name}".constantize
16
- end
5
+ included do
6
+ include HistoryModels
7
+ end
17
8
 
18
- def system_versioning(namespace: "Version")
19
- unless Object.const_defined?(namespace)
20
- mod = Module.new
21
- mod.include(Namespace)
22
- Object.const_set(namespace, mod)
23
- end
9
+ class_methods do
10
+ def system_versioned
11
+ include SystemVersioned
24
12
  end
25
13
  end
26
14
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveRecord::Temporal
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,63 +1,94 @@
1
1
  require "active_support"
2
2
 
3
- require_relative "temporal/application_versioning"
4
- require_relative "temporal/as_of_query"
5
- require_relative "temporal/as_of_query/association_macros"
6
- require_relative "temporal/as_of_query/association_scope"
7
- require_relative "temporal/as_of_query/association_walker"
8
- require_relative "temporal/as_of_query/query_methods"
9
- require_relative "temporal/as_of_query/scope_registry"
10
- require_relative "temporal/as_of_query/time_dimensions"
3
+ require_relative "temporal/application_versioning/application_versioned"
4
+ require_relative "temporal/querying/association_macros"
5
+ require_relative "temporal/querying/association_scope"
6
+ require_relative "temporal/querying/association_walker"
7
+ require_relative "temporal/querying/predicate_builder/handlers"
8
+ require_relative "temporal/querying/query_methods"
9
+ require_relative "temporal/querying/scope_registry"
10
+ require_relative "temporal/querying/scoping"
11
+ require_relative "temporal/querying/time_dimensions"
11
12
  require_relative "temporal/patches/association_reflection"
12
13
  require_relative "temporal/patches/join_dependency"
13
14
  require_relative "temporal/patches/merger"
14
15
  require_relative "temporal/patches/relation"
15
16
  require_relative "temporal/patches/through_association"
16
- require_relative "temporal/system_versioning"
17
17
  require_relative "temporal/system_versioning/command_recorder"
18
- require_relative "temporal/system_versioning/namespace"
19
- require_relative "temporal/system_versioning/model"
18
+ require_relative "temporal/system_versioning/history_model_namespace"
19
+ require_relative "temporal/system_versioning/history_model"
20
+ require_relative "temporal/system_versioning/history_models"
20
21
  require_relative "temporal/system_versioning/schema_creation"
21
22
  require_relative "temporal/system_versioning/schema_definitions"
22
23
  require_relative "temporal/system_versioning/schema_statements"
24
+ require_relative "temporal/system_versioning/system_versioned"
25
+ require_relative "temporal/application_versioning"
26
+ require_relative "temporal/querying"
27
+ require_relative "temporal/scoping"
28
+ require_relative "temporal/system_versioning"
29
+ require_relative "temporal/version"
30
+
31
+ module ActiveRecord::Temporal
32
+ def system_versioning
33
+ include SystemVersioning
34
+ end
35
+
36
+ def application_versioning(**options)
37
+ include Querying
38
+ include ApplicationVersioning
39
+
40
+ self.time_dimensions = options[:dimensions] if options[:dimensions]
41
+ end
42
+ end
23
43
 
24
44
  ActiveSupport.on_load(:active_record) do
25
- require "active_record/connection_adapters/postgresql_adapter" # TODO: add test
45
+ require "active_record/connection_adapters/postgresql_adapter"
46
+
47
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
48
+ .include ActiveRecord::Temporal::SystemVersioning::SchemaStatements
49
+
50
+ ActiveRecord::Migration::CommandRecorder
51
+ .include ActiveRecord::Temporal::SystemVersioning::CommandRecorder
26
52
 
27
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.include(ActiveRecord::Temporal::SystemVersioning::SchemaStatements)
28
- ActiveRecord::Migration::CommandRecorder.include(ActiveRecord::Temporal::SystemVersioning::CommandRecorder)
29
- ActiveRecord::Relation.include(ActiveRecord::Temporal::AsOfQuery::QueryMethods)
53
+ ActiveRecord::Relation
54
+ .include ActiveRecord::Temporal::Querying::QueryMethods
30
55
 
31
56
  # Patches
32
57
 
33
- # Patches the `build_arel` method wrap itself in the as-of query scope registry.
58
+ # Patches `#build_arel` to wrap itself in the as-of query scope registry.
34
59
  # This is what allows temporal association scopes to be aware of the time-scope
35
60
  # value of the relation that included them.
36
61
  #
37
- # Patches the `instantiate_records` method to call `initialize_time_scope_from_relation`
62
+ # Patches `#instantiate_records` method to call `initialize_time_tags_from_relation`
38
63
  # on each loaded record.
39
- ActiveRecord::Relation.prepend(ActiveRecord::Temporal::Patches::Relation)
64
+ ActiveRecord::Relation
65
+ .prepend ActiveRecord::Temporal::Patches::Relation
40
66
 
41
- # Patches the `merge` method (called by `Relation#merge`) to handle the new
42
- # query method `time_scope` that this gem adds.
43
- ActiveRecord::Relation::Merger.prepend(ActiveRecord::Temporal::Patches::Merger)
67
+ # Patches `#merge` (called by `Relation#merge`) to handle the new query method
68
+ # `time_tags` that this gem adds.
69
+ ActiveRecord::Relation::Merger
70
+ .prepend ActiveRecord::Temporal::Patches::Merger
44
71
 
45
- # Patches the preloader's `through_scope` method to pass along the relation's
46
- # time-scope values when it handles has-many-through associations. The handler
47
- # for has-many assoication uses `Relation#merge`, but this one doesn't.
48
- ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(ActiveRecord::Temporal::Patches::ThroughAssociation)
72
+ # Patches `#through_scope` to pass along the relation's time tag values when it
73
+ # handles has-many-through associations. The handler for has-many association
74
+ # uses `Relation#merge`, but this one doesn't.
75
+ ActiveRecord::Associations::Preloader::ThroughAssociation
76
+ .prepend ActiveRecord::Temporal::Patches::ThroughAssociation
49
77
 
50
78
  # This permits association scopes generated by this gem to be eager-load if they
51
- # are "optionally instance-dependent." That is to say, they accept arguments,
52
- # but don't require any arguments.
79
+ # are "optionally instance-dependent." That is to say, they accept but don't
80
+ # require arguments.
53
81
  #
54
- # I think permitting eager-loading optionally instance-dependent association
55
- # scopes would make sense as a general feature. See this PR for my justification:
82
+ # I think permitting eager-loading of such scopes would make sense as a
83
+ # standalone feature for Active Record. See this PR for my justification:
56
84
  # https://github.com/rails/rails/pull/56004
57
- ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Temporal::Patches::AssociationReflection)
85
+ ActiveRecord::Reflection::AssociationReflection
86
+ .prepend ActiveRecord::Temporal::Patches::AssociationReflection
58
87
 
59
88
  # This is a copy of a fix from https://github.com/rails/rails/pull/56088 that
60
- # impacts this gem. I has been backported to supported stable versions of
61
- # Active Record, but until those patches are released it's included here.
62
- ActiveRecord::Associations::JoinDependency.prepend(ActiveRecord::Temporal::Patches::JoinDependency)
89
+ # impacts this gem. I has been merged and backported to supported stable
90
+ # versions of Active Record, but until those patches are released it's included
91
+ # here.
92
+ ActiveRecord::Associations::JoinDependency
93
+ .prepend ActiveRecord::Temporal::Patches::JoinDependency
63
94
  end