chrono_model 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/chrono_model/patches/join_node.rb +1 -1
- data/lib/chrono_model/time_machine.rb +14 -41
- data/lib/chrono_model/time_machine/history_model.rb +38 -21
- data/lib/chrono_model/version.rb +1 -1
- data/spec/chrono_model/adapter/counter_cache_race_spec.rb +8 -8
- data/spec/chrono_model/adapter/sti_bug_spec.rb +49 -0
- data/spec/chrono_model/time_machine_spec.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15a81b8b75b7b77c03e9e750db3122cc0107e238696882bd67f2fe8fc7647274
|
4
|
+
data.tar.gz: 599dd55436b014a5db0ce7c1b003aa34ce15e3e12f08dac3e5cc0c5549823a8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ba2dc61cc6a62e573dea411005abf071e8010bcd249b05f274afefc756b1ac85b47e6469a6c9988f3b97c3f340891606bbe32d97cd75dfc4fcf0c1f99148805
|
7
|
+
data.tar.gz: 2cce42ff59e611beb10929f8484b8a0ec9eb106f2284770566d51d979eb6b8bb0c0627f88a4e8d1ef0c83cd7dfbd0733b44e09b339994a0424e7114181a74bf6
|
@@ -29,64 +29,37 @@ module ChronoModel
|
|
29
29
|
descendants_with_history.reject(&:history?)
|
30
30
|
end
|
31
31
|
|
32
|
-
# STI support.
|
32
|
+
# STI support.
|
33
33
|
#
|
34
34
|
def inherited(subclass)
|
35
35
|
super
|
36
36
|
|
37
|
-
# Do not smash stack
|
38
|
-
# new
|
39
|
-
#
|
37
|
+
# Do not smash stack. The below +define_history_model_for+ method
|
38
|
+
# defines a new inherited class via Class.new(), thus +inherited+
|
39
|
+
# is going to be called again. By that time the subclass is still
|
40
|
+
# an anonymous one, so its +name+ method returns nil. We use this
|
41
|
+
# condition to avoid infinite recursion.
|
42
|
+
#
|
43
|
+
# Sadly, we can't avoid it by calling +.history?+, because in the
|
44
|
+
# subclass the HistoryModel hasn't been included yet.
|
45
|
+
#
|
40
46
|
unless subclass.name.nil?
|
41
|
-
ChronoModel::TimeMachine.
|
47
|
+
ChronoModel::TimeMachine.define_history_model_for(subclass)
|
42
48
|
end
|
43
49
|
end
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
47
53
|
def self.define_history_model_for(model)
|
48
|
-
history = Class.new(model)
|
54
|
+
history = Class.new(model) do
|
55
|
+
include ChronoModel::TimeMachine::HistoryModel
|
56
|
+
end
|
49
57
|
|
50
58
|
model.singleton_class.instance_eval do
|
51
59
|
define_method(:history) { history }
|
52
60
|
end
|
53
61
|
|
54
|
-
history.singleton_class.instance_eval do
|
55
|
-
define_method(:sti_name) { model.sti_name }
|
56
|
-
end
|
57
|
-
|
58
62
|
model.const_set :History, history
|
59
|
-
|
60
|
-
return history
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.define_inherited_history_model_for(subclass)
|
64
|
-
# Define history model for the subclass
|
65
|
-
history = Class.new(subclass.superclass.history)
|
66
|
-
history.table_name = subclass.superclass.history.table_name
|
67
|
-
|
68
|
-
# Override the STI name on the history subclass
|
69
|
-
history.singleton_class.instance_eval do
|
70
|
-
define_method(:sti_name) { subclass.sti_name }
|
71
|
-
end
|
72
|
-
|
73
|
-
# Return the subclass history via the .history method
|
74
|
-
subclass.singleton_class.instance_eval do
|
75
|
-
define_method(:history) { history }
|
76
|
-
end
|
77
|
-
|
78
|
-
# Define the History constant inside the subclass
|
79
|
-
subclass.const_set :History, history
|
80
|
-
|
81
|
-
history.instance_eval do
|
82
|
-
# Monkey patch of ActiveRecord::Inheritance.
|
83
|
-
# STI fails when a Foo::History record has Foo as type in the
|
84
|
-
# inheritance column; AR expects the type to be an instance of the
|
85
|
-
# current class or a descendant (or self).
|
86
|
-
def find_sti_class(type_name)
|
87
|
-
super(type_name + "::History")
|
88
|
-
end
|
89
|
-
end
|
90
63
|
end
|
91
64
|
|
92
65
|
module ClassMethods
|
@@ -50,21 +50,6 @@ module ChronoModel
|
|
50
50
|
true
|
51
51
|
end
|
52
52
|
|
53
|
-
# Getting the correct quoted_table_name can be tricky when
|
54
|
-
# STI is involved. If Orange < Fruit, then Orange::History < Fruit::History
|
55
|
-
# (see define_inherited_history_model_for).
|
56
|
-
# This means that the superclass method returns Fruit::History, which
|
57
|
-
# will give us the wrong table name. What we actually want is the
|
58
|
-
# superclass of Fruit::History, which is Fruit. So, we use
|
59
|
-
# non_history_superclass instead. -npj
|
60
|
-
def non_history_superclass(klass = self)
|
61
|
-
if klass.superclass.history?
|
62
|
-
non_history_superclass(klass.superclass)
|
63
|
-
else
|
64
|
-
klass.superclass
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
53
|
def relation
|
69
54
|
super.as_of_time!(Time.now)
|
70
55
|
end
|
@@ -72,14 +57,15 @@ module ChronoModel
|
|
72
57
|
# Fetches as of +time+ records.
|
73
58
|
#
|
74
59
|
def as_of(time)
|
75
|
-
|
60
|
+
superclass.from(virtual_table_at(time)).as_of_time!(time)
|
76
61
|
end
|
77
62
|
|
78
|
-
def virtual_table_at(time,
|
79
|
-
|
80
|
-
|
63
|
+
def virtual_table_at(time, table_name: nil)
|
64
|
+
virtual_name = table_name ?
|
65
|
+
connection.quote_table_name(table_name) :
|
66
|
+
superclass.quoted_table_name
|
81
67
|
|
82
|
-
"(#{at(time).to_sql}) #{
|
68
|
+
"(#{at(time).to_sql}) #{virtual_name}"
|
83
69
|
end
|
84
70
|
|
85
71
|
# Fetches history record at the given time
|
@@ -102,6 +88,37 @@ module ChronoModel
|
|
102
88
|
def of(object)
|
103
89
|
where(id: object)
|
104
90
|
end
|
91
|
+
|
92
|
+
# The `sti_name` method returns the contents of the inheritance
|
93
|
+
# column, and it is usually the class name. The inherited class
|
94
|
+
# name has the "::History" suffix but that is never going to be
|
95
|
+
# present in the data.
|
96
|
+
#
|
97
|
+
# As such it is overriden here to return the same contents that
|
98
|
+
# the parent would have returned.
|
99
|
+
def sti_name
|
100
|
+
superclass.sti_name
|
101
|
+
end
|
102
|
+
|
103
|
+
# For STI to work, the history model needs to have the exact same
|
104
|
+
# semantics as the model it inherits from. However given it is
|
105
|
+
# actually inherited, the original AR implementation would return
|
106
|
+
# false here. But for STI sake, the history model is located in the
|
107
|
+
# same exact hierarchy location as its parent, thus this is defined in
|
108
|
+
# this override.
|
109
|
+
#
|
110
|
+
def descends_from_active_record?
|
111
|
+
superclass.descends_from_active_record?
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
# STI fails when a Foo::History record has Foo as type in the
|
116
|
+
# inheritance column; AR expects the type to be an instance of the
|
117
|
+
# current class or a descendant (or self).
|
118
|
+
#
|
119
|
+
def find_sti_class(type_name)
|
120
|
+
super(type_name + "::History")
|
121
|
+
end
|
105
122
|
end
|
106
123
|
|
107
124
|
# The history id is `hid`, but this cannot set as primary key
|
@@ -170,7 +187,7 @@ module ChronoModel
|
|
170
187
|
# Returns this history entry's current record
|
171
188
|
#
|
172
189
|
def current_version
|
173
|
-
self.class.
|
190
|
+
self.class.superclass.find(rid)
|
174
191
|
end
|
175
192
|
|
176
193
|
def record #:nodoc:
|
data/lib/chrono_model/version.rb
CHANGED
@@ -4,15 +4,15 @@ require 'support/helpers'
|
|
4
4
|
describe 'models with counter cache' do
|
5
5
|
include ChronoTest::Helpers::TimeMachine
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
adapter.create_table 'sections', temporal: true, no_journal: %w( articles_count ) do |t|
|
8
|
+
t.string :name
|
9
|
+
t.integer :articles_count, default: 0
|
10
|
+
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
adapter.create_table 'articles', temporal: true do |t|
|
13
|
+
t.string :title
|
14
|
+
t.references :section
|
15
|
+
end
|
16
16
|
|
17
17
|
class ::Section < ActiveRecord::Base
|
18
18
|
include ChronoModel::TimeMachine
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/helpers'
|
3
|
+
|
4
|
+
describe 'models with STI' do
|
5
|
+
include ChronoTest::Helpers::TimeMachine
|
6
|
+
|
7
|
+
adapter.create_table 'animals', temporal: true do |t|
|
8
|
+
t.string :type
|
9
|
+
end
|
10
|
+
|
11
|
+
class ::Animal < ActiveRecord::Base
|
12
|
+
include ChronoModel::TimeMachine
|
13
|
+
end
|
14
|
+
|
15
|
+
class ::Dog < Animal
|
16
|
+
end
|
17
|
+
|
18
|
+
class ::Goat < Animal
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'it generates the right queries' do
|
22
|
+
before do
|
23
|
+
Dog.create!
|
24
|
+
@later = Time.new
|
25
|
+
Goat.create!
|
26
|
+
end
|
27
|
+
|
28
|
+
after do
|
29
|
+
tables = ['temporal.animals', 'history.animals']
|
30
|
+
ActiveRecord::Base.connection.execute "truncate #{tables.join(', ')} cascade"
|
31
|
+
end
|
32
|
+
|
33
|
+
specify "select" do
|
34
|
+
expect(Animal.first).to_not be_nil
|
35
|
+
expect(Animal.as_of(@later).first).to_not be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
specify "count" do
|
39
|
+
expect(Animal.count).to eq(2)
|
40
|
+
expect(Animal.as_of(@later).count).to eq(1)
|
41
|
+
|
42
|
+
expect(Dog.count).to eq(1)
|
43
|
+
expect(Dog.as_of(@later).count).to eq(1)
|
44
|
+
|
45
|
+
expect(Goat.count).to eq(1)
|
46
|
+
expect(Goat.as_of(@later).count).to eq(0)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chrono_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcello Barnaba
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-04-
|
12
|
+
date: 2019-04-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -253,6 +253,7 @@ files:
|
|
253
253
|
- spec/aruba/migrations_spec.rb
|
254
254
|
- spec/aruba/rake_task_spec.rb
|
255
255
|
- spec/chrono_model/adapter/counter_cache_race_spec.rb
|
256
|
+
- spec/chrono_model/adapter/sti_bug_spec.rb
|
256
257
|
- spec/chrono_model/adapter_spec.rb
|
257
258
|
- spec/chrono_model/conversions_spec.rb
|
258
259
|
- spec/chrono_model/json_ops_spec.rb
|
@@ -308,6 +309,7 @@ test_files:
|
|
308
309
|
- spec/aruba/migrations_spec.rb
|
309
310
|
- spec/aruba/rake_task_spec.rb
|
310
311
|
- spec/chrono_model/adapter/counter_cache_race_spec.rb
|
312
|
+
- spec/chrono_model/adapter/sti_bug_spec.rb
|
311
313
|
- spec/chrono_model/adapter_spec.rb
|
312
314
|
- spec/chrono_model/conversions_spec.rb
|
313
315
|
- spec/chrono_model/json_ops_spec.rb
|