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
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.1.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: '7.2'
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: '7.2'
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: '7.2'
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: '7.2'
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/as_of_query.rb
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/model.rb
95
- - lib/activerecord/temporal/system_versioning/namespace.rb
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