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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +778 -7
- data/lib/activerecord/temporal/application_versioning/application_versioned.rb +122 -0
- data/lib/activerecord/temporal/application_versioning.rb +4 -68
- data/lib/activerecord/temporal/patches/association_reflection.rb +10 -3
- data/lib/activerecord/temporal/patches/join_dependency.rb +4 -0
- data/lib/activerecord/temporal/patches/merger.rb +1 -1
- data/lib/activerecord/temporal/patches/relation.rb +10 -6
- data/lib/activerecord/temporal/patches/through_association.rb +4 -1
- data/lib/activerecord/temporal/{as_of_query → querying}/association_macros.rb +1 -1
- data/lib/activerecord/temporal/querying/association_scope.rb +55 -0
- data/lib/activerecord/temporal/{as_of_query → querying}/association_walker.rb +1 -1
- data/lib/activerecord/temporal/querying/predicate_builder/contains_handler.rb +24 -0
- data/lib/activerecord/temporal/querying/predicate_builder/handlers.rb +31 -0
- data/lib/activerecord/temporal/querying/query_methods.rb +37 -0
- data/lib/activerecord/temporal/querying/scope_registry.rb +95 -0
- data/lib/activerecord/temporal/querying/scoping.rb +70 -0
- data/lib/activerecord/temporal/{as_of_query → querying}/time_dimensions.rb +13 -3
- data/lib/activerecord/temporal/querying/where_clause_refinement.rb +17 -0
- data/lib/activerecord/temporal/querying.rb +95 -0
- data/lib/activerecord/temporal/scoping.rb +7 -0
- data/lib/activerecord/temporal/system_versioning/history_model.rb +47 -0
- data/lib/activerecord/temporal/system_versioning/history_model_namespace.rb +45 -0
- data/lib/activerecord/temporal/system_versioning/history_models.rb +29 -0
- data/lib/activerecord/temporal/system_versioning/schema_creation.rb +8 -5
- data/lib/activerecord/temporal/system_versioning/schema_definitions.rb +10 -8
- data/lib/activerecord/temporal/system_versioning/schema_statements.rb +62 -24
- data/lib/activerecord/temporal/system_versioning/system_versioned.rb +13 -0
- data/lib/activerecord/temporal/system_versioning.rb +6 -18
- data/lib/activerecord/temporal/version.rb +1 -1
- data/lib/activerecord/temporal.rb +64 -33
- metadata +23 -15
- data/lib/activerecord/temporal/as_of_query/association_scope.rb +0 -54
- data/lib/activerecord/temporal/as_of_query/query_methods.rb +0 -24
- data/lib/activerecord/temporal/as_of_query/scope_registry.rb +0 -38
- data/lib/activerecord/temporal/as_of_query.rb +0 -109
- data/lib/activerecord/temporal/system_versioning/model.rb +0 -37
- data/lib/activerecord/temporal/system_versioning/namespace.rb +0 -34
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-temporal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Martin-Alexander
|
|
@@ -15,7 +15,7 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '8'
|
|
19
19
|
- - "<"
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
21
|
version: '9.0'
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
requirements:
|
|
26
26
|
- - ">="
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: '
|
|
28
|
+
version: '8'
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '9.0'
|
|
@@ -35,7 +35,7 @@ dependencies:
|
|
|
35
35
|
requirements:
|
|
36
36
|
- - ">="
|
|
37
37
|
- !ruby/object:Gem::Version
|
|
38
|
-
version: '
|
|
38
|
+
version: '8'
|
|
39
39
|
- - "<"
|
|
40
40
|
- !ruby/object:Gem::Version
|
|
41
41
|
version: '9.0'
|
|
@@ -45,7 +45,7 @@ dependencies:
|
|
|
45
45
|
requirements:
|
|
46
46
|
- - ">="
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: '
|
|
48
|
+
version: '8'
|
|
49
49
|
- - "<"
|
|
50
50
|
- !ruby/object:Gem::Version
|
|
51
51
|
version: '9.0'
|
|
@@ -77,32 +77,40 @@ files:
|
|
|
77
77
|
- README.md
|
|
78
78
|
- lib/activerecord/temporal.rb
|
|
79
79
|
- lib/activerecord/temporal/application_versioning.rb
|
|
80
|
-
- lib/activerecord/temporal/
|
|
81
|
-
- lib/activerecord/temporal/as_of_query/association_macros.rb
|
|
82
|
-
- lib/activerecord/temporal/as_of_query/association_scope.rb
|
|
83
|
-
- lib/activerecord/temporal/as_of_query/association_walker.rb
|
|
84
|
-
- lib/activerecord/temporal/as_of_query/query_methods.rb
|
|
85
|
-
- lib/activerecord/temporal/as_of_query/scope_registry.rb
|
|
86
|
-
- lib/activerecord/temporal/as_of_query/time_dimensions.rb
|
|
80
|
+
- lib/activerecord/temporal/application_versioning/application_versioned.rb
|
|
87
81
|
- lib/activerecord/temporal/patches/association_reflection.rb
|
|
88
82
|
- lib/activerecord/temporal/patches/join_dependency.rb
|
|
89
83
|
- lib/activerecord/temporal/patches/merger.rb
|
|
90
84
|
- lib/activerecord/temporal/patches/relation.rb
|
|
91
85
|
- lib/activerecord/temporal/patches/through_association.rb
|
|
86
|
+
- lib/activerecord/temporal/querying.rb
|
|
87
|
+
- lib/activerecord/temporal/querying/association_macros.rb
|
|
88
|
+
- lib/activerecord/temporal/querying/association_scope.rb
|
|
89
|
+
- lib/activerecord/temporal/querying/association_walker.rb
|
|
90
|
+
- lib/activerecord/temporal/querying/predicate_builder/contains_handler.rb
|
|
91
|
+
- lib/activerecord/temporal/querying/predicate_builder/handlers.rb
|
|
92
|
+
- lib/activerecord/temporal/querying/query_methods.rb
|
|
93
|
+
- lib/activerecord/temporal/querying/scope_registry.rb
|
|
94
|
+
- lib/activerecord/temporal/querying/scoping.rb
|
|
95
|
+
- lib/activerecord/temporal/querying/time_dimensions.rb
|
|
96
|
+
- lib/activerecord/temporal/querying/where_clause_refinement.rb
|
|
97
|
+
- lib/activerecord/temporal/scoping.rb
|
|
92
98
|
- lib/activerecord/temporal/system_versioning.rb
|
|
93
99
|
- lib/activerecord/temporal/system_versioning/command_recorder.rb
|
|
94
|
-
- lib/activerecord/temporal/system_versioning/
|
|
95
|
-
- lib/activerecord/temporal/system_versioning/
|
|
100
|
+
- lib/activerecord/temporal/system_versioning/history_model.rb
|
|
101
|
+
- lib/activerecord/temporal/system_versioning/history_model_namespace.rb
|
|
102
|
+
- lib/activerecord/temporal/system_versioning/history_models.rb
|
|
96
103
|
- lib/activerecord/temporal/system_versioning/schema_creation.rb
|
|
97
104
|
- lib/activerecord/temporal/system_versioning/schema_definitions.rb
|
|
98
105
|
- lib/activerecord/temporal/system_versioning/schema_statements.rb
|
|
106
|
+
- lib/activerecord/temporal/system_versioning/system_versioned.rb
|
|
99
107
|
- lib/activerecord/temporal/version.rb
|
|
100
108
|
homepage: https://github.com/Martin-Alexander/activerecord-temporal
|
|
101
109
|
licenses:
|
|
102
110
|
- MIT
|
|
103
111
|
metadata:
|
|
104
112
|
bug_tracker_uri: https://github.com/Martin-Alexander/activerecord-temporal/issues
|
|
105
|
-
changelog_uri: https://github.com/Martin-Alexander/activerecord-temporal/CHANGELOG.md
|
|
113
|
+
changelog_uri: https://github.com/Martin-Alexander/activerecord-temporal/blob/master/CHANGELOG.md
|
|
106
114
|
homepage_uri: https://github.com/Martin-Alexander/activerecord-temporal
|
|
107
115
|
source_code_uri: https://github.com/Martin-Alexander/activerecord-temporal
|
|
108
116
|
rdoc_options: []
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
module ActiveRecord::Temporal
|
|
2
|
-
module AsOfQuery
|
|
3
|
-
class AssociationScope
|
|
4
|
-
class << self
|
|
5
|
-
def build(block)
|
|
6
|
-
scope = build_scope(block || default_base_scope)
|
|
7
|
-
|
|
8
|
-
def scope.as_of_scope? = true
|
|
9
|
-
|
|
10
|
-
scope
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
private
|
|
14
|
-
|
|
15
|
-
def build_scope(block)
|
|
16
|
-
temporal_scope = build_temporal_scope
|
|
17
|
-
|
|
18
|
-
if block.arity != 0
|
|
19
|
-
return ->(owner) do
|
|
20
|
-
base = instance_exec(owner, &block)
|
|
21
|
-
instance_exec(owner, base, &temporal_scope)
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
->(owner = nil) do
|
|
26
|
-
base = instance_exec(owner, &block)
|
|
27
|
-
instance_exec(owner, base, &temporal_scope)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def build_temporal_scope
|
|
32
|
-
->(owner, base) do
|
|
33
|
-
time_scopes = ScopeRegistry.query_scope_for(time_dimensions)
|
|
34
|
-
owner_time_scopes = owner&.time_scopes_for(time_dimensions)
|
|
35
|
-
|
|
36
|
-
time_scopes.merge!(owner_time_scopes) if owner_time_scopes
|
|
37
|
-
|
|
38
|
-
default_time_scopes = time_dimensions.map do |dimension|
|
|
39
|
-
[dimension, Time.current]
|
|
40
|
-
end.to_h
|
|
41
|
-
|
|
42
|
-
time_scope_constraints = default_time_scopes.merge(time_scopes)
|
|
43
|
-
|
|
44
|
-
base.existed_at(time_scope_constraints).time_scope(time_scopes)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def default_base_scope
|
|
49
|
-
proc { all }
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
module ActiveRecord::Temporal
|
|
2
|
-
module AsOfQuery
|
|
3
|
-
module QueryMethods
|
|
4
|
-
def time_scope(scope)
|
|
5
|
-
spawn.time_scope!(scope)
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def time_scope!(scope)
|
|
9
|
-
self.time_scope_values = time_scope_values.merge(scope)
|
|
10
|
-
self
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def time_scope_values
|
|
14
|
-
@values.fetch(:time_scope, ActiveRecord::QueryMethods::FROZEN_EMPTY_HASH)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def time_scope_values=(scope)
|
|
18
|
-
assert_modifiable! # TODO: write test
|
|
19
|
-
|
|
20
|
-
@values[:time_scope] = scope
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
module ActiveRecord::Temporal
|
|
2
|
-
module AsOfQuery
|
|
3
|
-
class ScopeRegistry
|
|
4
|
-
class << self
|
|
5
|
-
delegate :default_scopes, :query_scopes, :set_default_scopes, :query_scope_for, :with_query_scope, to: :instance
|
|
6
|
-
|
|
7
|
-
def instance
|
|
8
|
-
ActiveSupport::IsolatedExecutionState[:temporal_as_of_query_registry] ||= new
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
attr_reader :default_scopes, :query_scopes
|
|
13
|
-
|
|
14
|
-
def initialize
|
|
15
|
-
@default_scopes = {}
|
|
16
|
-
@query_scopes = {}
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def set_default_scopes(default_scopes)
|
|
20
|
-
@default_scopes = default_scopes
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def query_scope_for(dimensions)
|
|
24
|
-
query_scopes.slice(*dimensions)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def with_query_scope(scope, &block)
|
|
28
|
-
original = @query_scopes.dup
|
|
29
|
-
|
|
30
|
-
@query_scopes = @query_scopes.merge(scope)
|
|
31
|
-
|
|
32
|
-
block.call
|
|
33
|
-
ensure
|
|
34
|
-
@query_scopes = original
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
module ActiveRecord::Temporal
|
|
2
|
-
module AsOfQuery
|
|
3
|
-
class RangeError < StandardError; end
|
|
4
|
-
|
|
5
|
-
extend ActiveSupport::Concern
|
|
6
|
-
|
|
7
|
-
class_methods do
|
|
8
|
-
def existed_at_constraint(arel_table, time, time_dimension)
|
|
9
|
-
time_as_tstz = Arel::Nodes::As.new(
|
|
10
|
-
Arel::Nodes::Quoted.new(time),
|
|
11
|
-
Arel::Nodes::SqlLiteral.new("timestamptz")
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
cast_value = Arel::Nodes::NamedFunction.new("CAST", [time_as_tstz])
|
|
15
|
-
|
|
16
|
-
arel_table[time_dimension].contains(cast_value)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def temporal_association_scope(&block)
|
|
20
|
-
AssociationScope.build(block)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def resolve_time_scopes(time_or_time_scopes)
|
|
24
|
-
return time_or_time_scopes if time_or_time_scopes.is_a?(Hash)
|
|
25
|
-
|
|
26
|
-
{default_time_dimension.to_sym => time_or_time_scopes}
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
included do
|
|
31
|
-
include AssociationMacros
|
|
32
|
-
include TimeDimensions
|
|
33
|
-
|
|
34
|
-
delegate :resolve_time_scopes, to: :class
|
|
35
|
-
|
|
36
|
-
scope :as_of, ->(time) do
|
|
37
|
-
time_scopes = resolve_time_scopes(time)
|
|
38
|
-
|
|
39
|
-
existed_at(time_scopes).time_scope(time_scopes)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
scope :existed_at, ->(time) do
|
|
43
|
-
time_scopes = resolve_time_scopes(time)
|
|
44
|
-
|
|
45
|
-
rel = all
|
|
46
|
-
|
|
47
|
-
time_scopes.each do |time_dimension, time|
|
|
48
|
-
next unless time_dimension_column?(time_dimension)
|
|
49
|
-
|
|
50
|
-
rel = rel.where(existed_at_constraint(table, time, time_dimension))
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
rel
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def time_scopes
|
|
58
|
-
@time_scopes || {}
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def time_scopes=(value)
|
|
62
|
-
@time_scopes = value&.slice(*time_dimensions)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def time_scope
|
|
66
|
-
time_scopes[default_time_dimension]
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def time_scopes_for(time_dimensions)
|
|
70
|
-
time_scopes.slice(*time_dimensions)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def as_of!(time)
|
|
74
|
-
time_scopes = resolve_time_scopes(time)
|
|
75
|
-
|
|
76
|
-
ensure_time_scopes_in_bounds!(time_scopes)
|
|
77
|
-
|
|
78
|
-
reload
|
|
79
|
-
|
|
80
|
-
self.time_scopes = time_scopes
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def as_of(time)
|
|
84
|
-
time_scopes = resolve_time_scopes(time)
|
|
85
|
-
|
|
86
|
-
self.class.as_of(time_scopes).find_by(self.class.primary_key => [id])
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def initialize_time_scope_from_relation(relation)
|
|
90
|
-
associations = relation.includes_values | relation.eager_load_values
|
|
91
|
-
|
|
92
|
-
self.time_scopes = relation.time_scope_values
|
|
93
|
-
|
|
94
|
-
AssociationWalker.each_target(self, associations) do |target|
|
|
95
|
-
target.time_scopes = relation.time_scope_values
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
private
|
|
100
|
-
|
|
101
|
-
def ensure_time_scopes_in_bounds!(time_scopes)
|
|
102
|
-
time_scopes.each do |dimension, time|
|
|
103
|
-
if time_dimension_column?(dimension) && !time_dimension(dimension).cover?(time)
|
|
104
|
-
raise RangeError, "#{time} is outside of '#{dimension}' range"
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
module ActiveRecord::Temporal
|
|
2
|
-
module SystemVersioning
|
|
3
|
-
module Model
|
|
4
|
-
extend ActiveSupport::Concern
|
|
5
|
-
|
|
6
|
-
included do
|
|
7
|
-
include AsOfQuery
|
|
8
|
-
|
|
9
|
-
set_time_dimensions :system_period
|
|
10
|
-
|
|
11
|
-
reflect_on_all_associations.each do |reflection|
|
|
12
|
-
scope = temporal_association_scope(&reflection.scope)
|
|
13
|
-
|
|
14
|
-
send(reflection.macro, reflection.name, scope, **reflection.options)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
class_methods do
|
|
19
|
-
def polymorphic_class_for(name)
|
|
20
|
-
super.version_model
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def sti_name
|
|
24
|
-
superclass.sti_name
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def find_sti_class(type_name)
|
|
28
|
-
superclass.send(:find_sti_class, type_name).version_model
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def finder_needs_type_condition?
|
|
32
|
-
superclass.finder_needs_type_condition?
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
module ActiveRecord::Temporal
|
|
2
|
-
module SystemVersioning
|
|
3
|
-
module Namespace
|
|
4
|
-
extend ActiveSupport::Concern
|
|
5
|
-
|
|
6
|
-
class_methods do
|
|
7
|
-
def const_missing(name)
|
|
8
|
-
model = name.to_s.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
|
-
version_model = if (history_table = model.history_table)
|
|
17
|
-
Class.new(model) do
|
|
18
|
-
self.table_name = history_table
|
|
19
|
-
self.primary_key = model.primary_key_from_db + [:system_period]
|
|
20
|
-
|
|
21
|
-
include Model
|
|
22
|
-
end
|
|
23
|
-
else
|
|
24
|
-
Class.new(model) do
|
|
25
|
-
include Model
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
const_set(name, version_model)
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|