chrono_model 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 143b16e6e193bd56c88d1541f965150b07af191992aec7978dc94c84d7e53f87
4
- data.tar.gz: 4aa06ed4110a6dbd9047f580b2972589d813f5108b422fe6ba0a80c021d56d03
3
+ metadata.gz: 15a81b8b75b7b77c03e9e750db3122cc0107e238696882bd67f2fe8fc7647274
4
+ data.tar.gz: 599dd55436b014a5db0ce7c1b003aa34ce15e3e12f08dac3e5cc0c5549823a8e
5
5
  SHA512:
6
- metadata.gz: 57a62853b840b3edd78d1e0402c8d66458b01980956c87c880b84261490ed6f251a00b497c70f56646b4dd051869554118fc426d31b9bfda0af367e8977f3612
7
- data.tar.gz: 854b001d29648c4b191eb7e985d720d72bd82cd43dc40df481cc63a83d684fe15922b8127ea40418ad89e5cc7cee30bb3e2551e2eeb6964bbb6477a97aa2f109
6
+ metadata.gz: 2ba2dc61cc6a62e573dea411005abf071e8010bcd249b05f274afefc756b1ac85b47e6469a6c9988f3b97c3f340891606bbe32d97cd75dfc4fcf0c1f99148805
7
+ data.tar.gz: 2cce42ff59e611beb10929f8484b8a0ec9eb106f2284770566d51d979eb6b8bb0c0627f88a4e8d1ef0c83cd7dfbd0733b44e09b339994a0424e7114181a74bf6
@@ -22,7 +22,7 @@ module ChronoModel
22
22
  @as_of_time = as_of_time
23
23
 
24
24
  virtual_table = history_model.
25
- virtual_table_at(@as_of_time, @table_alias || @table_name)
25
+ virtual_table_at(@as_of_time, table_name: @table_alias || @table_name)
26
26
 
27
27
  super(virtual_table)
28
28
  end
@@ -29,64 +29,37 @@ module ChronoModel
29
29
  descendants_with_history.reject(&:history?)
30
30
  end
31
31
 
32
- # STI support. TODO: more thorough testing
32
+ # STI support.
33
33
  #
34
34
  def inherited(subclass)
35
35
  super
36
36
 
37
- # Do not smash stack: as the below method is defining a
38
- # new anonymous class, without this check this leads to
39
- # infinite recursion.
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.define_inherited_history_model_for(subclass)
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) { include ChronoModel::TimeMachine::HistoryModel }
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
- non_history_superclass.from(virtual_table_at(time)).as_of_time!(time)
60
+ superclass.from(virtual_table_at(time)).as_of_time!(time)
76
61
  end
77
62
 
78
- def virtual_table_at(time, name = nil)
79
- name = name ? connection.quote_table_name(name) :
80
- non_history_superclass.quoted_table_name
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}) #{name}"
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.non_history_superclass.find(rid)
190
+ self.class.superclass.find(rid)
174
191
  end
175
192
 
176
193
  def record #:nodoc:
@@ -1,3 +1,3 @@
1
1
  module ChronoModel
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -4,15 +4,15 @@ require 'support/helpers'
4
4
  describe 'models with counter cache' do
5
5
  include ChronoTest::Helpers::TimeMachine
6
6
 
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
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
- adapter.create_table 'articles', temporal: true do |t|
13
- t.string :title
14
- t.references :section
15
- end
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
@@ -93,6 +93,7 @@ describe ChronoModel::TimeMachine do
93
93
  'elements' => Element::History,
94
94
  'sections' => Section::History,
95
95
  'sub_bars' => SubBar::History,
96
+ 'animals' => Animal::History,
96
97
  ) }
97
98
  end
98
99
 
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.1.0
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-08 00:00:00.000000000 Z
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