chrono_model 0.10.1 → 0.11.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/lib/chrono_model/patches.rb +56 -6
- data/lib/chrono_model/schema_format.rb +19 -0
- data/lib/chrono_model/version.rb +1 -1
- data/spec/support/helpers.rb +15 -0
- data/spec/time_machine_spec.rb +29 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6372b41af69ba073bf8d6ea27f34c238ce3c1426
|
4
|
+
data.tar.gz: 9bda73df3fa2d169da83fedd4d499521745a61b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a04bfe04096c15f977c0972458e53508c81bccbf84b81df79e990f3174152af5f788f1564acf27ace77e3721ba9717a30ec8e5d68f813aaa2761528bed57da52
|
7
|
+
data.tar.gz: 66289e54ef6511b1ff005705386585f82ce5571a99763b37d522d1208dcd87c738c7a2b2d004e205ed5438ff53d28cf8cc076bf29eb5657be318f326953efd1b
|
data/lib/chrono_model/patches.rb
CHANGED
@@ -56,6 +56,8 @@ module ChronoModel
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
# Build a preloader at the +as_of_time+ of this relation
|
60
|
+
#
|
59
61
|
def build_preloader
|
60
62
|
ActiveRecord::Associations::Preloader.new(as_of_time: as_of_time)
|
61
63
|
end
|
@@ -68,15 +70,60 @@ module ChronoModel
|
|
68
70
|
module Preloader
|
69
71
|
attr_reader :options
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
73
|
+
# We overwrite the initializer in order to pass the +as_of_time+
|
74
|
+
# parameter above in the build_preloader
|
75
|
+
#
|
74
76
|
def initialize(options = {})
|
75
77
|
@options = options.freeze
|
76
78
|
end
|
77
79
|
|
80
|
+
# See +ActiveRecord::Associations::Preloader::NULL_RELATION+
|
81
|
+
NULL_RELATION = ActiveRecord::Associations::Preloader::NULL_RELATION
|
82
|
+
|
83
|
+
# Extension of +NULL_RELATION+ adding an +as_of_time+ property. See
|
84
|
+
# +preload+.
|
85
|
+
AS_OF_PRELOAD_SCOPE = Struct.new(:as_of_time, *NULL_RELATION.members)
|
86
|
+
|
87
|
+
# Patches the AR Preloader (lib/active_record/associations/preloader.rb)
|
88
|
+
# in order to carry around the +as_of_time+ of the original invocation.
|
89
|
+
#
|
90
|
+
# * The +records+ are the parent records where the association is defined
|
91
|
+
# * The +associations+ are the association names involved in preloading
|
92
|
+
# * The +given_preload_scope+ is the preloading scope, that is used only
|
93
|
+
# in the :through association and it holds the intermediate records
|
94
|
+
# _through_ which the final associated records are eventually fetched.
|
95
|
+
#
|
96
|
+
# As the +preload_scope+ is passed around to all the different
|
97
|
+
# incarnations of the preloader strategies, we are using it to pass
|
98
|
+
# around the +as_of_time+ of the original query invocation, so that
|
99
|
+
# preloaded records are preloaded honoring the +as_of_time+.
|
100
|
+
#
|
101
|
+
# The +preload_scope+ is not nil only for through associations, but the
|
102
|
+
# preloader interfaces expect it to be always defined, for consistency.
|
103
|
+
# So, AR defines a +NULL_RELATION+ constant to pass around for the
|
104
|
+
# association types that do not have a preload_scope. It quacks like a
|
105
|
+
# Relation, and it contains only the methods that are used by the other
|
106
|
+
# preloader methods. We extend AR's +NULL_RELATION+ with an `as_of_time`
|
107
|
+
# property.
|
108
|
+
#
|
109
|
+
# For `:through` associations, the +given_preload_scope+ is already a
|
110
|
+
# +Relation+, that already has the +as_of_time+ getters and setters,
|
111
|
+
# so we use them here.
|
112
|
+
#
|
78
113
|
def preload(records, associations, given_preload_scope = nil)
|
79
114
|
if (as_of_time = options[:as_of_time])
|
115
|
+
preload_scope = if given_preload_scope.respond_to?(:as_of_time!)
|
116
|
+
given_preload_scope.as_of_time!(as_of_time)
|
117
|
+
else
|
118
|
+
null_relation_preload_scope(as_of_time, given_preload_scope)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
super records, associations, preload_scope
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def null_relation_preload_scope(as_of_time, given_preload_scope)
|
80
127
|
preload_scope = AS_OF_PRELOAD_SCOPE.new
|
81
128
|
|
82
129
|
preload_scope.as_of_time = as_of_time
|
@@ -85,12 +132,15 @@ module ChronoModel
|
|
85
132
|
NULL_RELATION.members.each do |member|
|
86
133
|
preload_scope[member] = given_preload_scope[member]
|
87
134
|
end
|
88
|
-
end
|
89
135
|
|
90
|
-
|
91
|
-
|
136
|
+
return preload_scope
|
137
|
+
end
|
92
138
|
|
93
139
|
module Association
|
140
|
+
# Builds the preloader scope taking into account a potential
|
141
|
+
# +as_of_time+ set above in +Preloader#preload+ and coming from the
|
142
|
+
# user's query invocation.
|
143
|
+
#
|
94
144
|
def build_scope
|
95
145
|
scope = super
|
96
146
|
|
@@ -17,6 +17,8 @@ end
|
|
17
17
|
# PG utilities
|
18
18
|
#
|
19
19
|
module PG
|
20
|
+
SQL_COMMENT_BEGIN = "--".freeze
|
21
|
+
|
20
22
|
extend self
|
21
23
|
|
22
24
|
def config!
|
@@ -30,6 +32,7 @@ module PG
|
|
30
32
|
|
31
33
|
def make_dump(target, username, database, *options)
|
32
34
|
exec 'pg_dump', '-f', target, '-U', username, '-d', database, *options
|
35
|
+
remove_sql_header_comments(target)
|
33
36
|
end
|
34
37
|
|
35
38
|
def load_dump(source, username, database, *options)
|
@@ -76,4 +79,20 @@ module PG
|
|
76
79
|
ActiveRecord::Base.logger
|
77
80
|
end
|
78
81
|
|
82
|
+
private
|
83
|
+
|
84
|
+
def remove_sql_header_comments(filename)
|
85
|
+
tempfile = Tempfile.open("uncommented_structure.sql")
|
86
|
+
begin
|
87
|
+
File.foreach(filename) do |line|
|
88
|
+
unless line.start_with?(SQL_COMMENT_BEGIN)
|
89
|
+
tempfile << line
|
90
|
+
end
|
91
|
+
end
|
92
|
+
ensure
|
93
|
+
tempfile.close
|
94
|
+
end
|
95
|
+
FileUtils.mv(tempfile.path, filename)
|
96
|
+
end
|
97
|
+
|
79
98
|
end
|
data/lib/chrono_model/version.rb
CHANGED
data/spec/support/helpers.rb
CHANGED
@@ -74,6 +74,11 @@ module ChronoTest::Helpers
|
|
74
74
|
t.references :foo
|
75
75
|
end
|
76
76
|
|
77
|
+
adapter.create_table 'sub_bars' do |t|
|
78
|
+
t.string :name
|
79
|
+
t.references :bar
|
80
|
+
end
|
81
|
+
|
77
82
|
adapter.create_table 'bazs' do |t|
|
78
83
|
t.string :name
|
79
84
|
t.references :bar
|
@@ -104,17 +109,27 @@ module ChronoTest::Helpers
|
|
104
109
|
include ChronoModel::TimeMachine
|
105
110
|
|
106
111
|
has_many :bars
|
112
|
+
has_many :sub_bars, :through => :bars
|
107
113
|
end
|
108
114
|
|
109
115
|
class ::Bar < ActiveRecord::Base
|
110
116
|
include ChronoModel::TimeMachine
|
111
117
|
|
112
118
|
belongs_to :foo
|
119
|
+
has_many :sub_bars
|
113
120
|
has_one :baz
|
114
121
|
|
115
122
|
has_timeline :with => :foo
|
116
123
|
end
|
117
124
|
|
125
|
+
class ::SubBar < ActiveRecord::Base
|
126
|
+
include ChronoModel::TimeGate
|
127
|
+
|
128
|
+
belongs_to :bar
|
129
|
+
|
130
|
+
has_timeline :with => :bar
|
131
|
+
end
|
132
|
+
|
118
133
|
class ::Baz < ActiveRecord::Base
|
119
134
|
include ChronoModel::TimeGate
|
120
135
|
|
data/spec/time_machine_spec.rb
CHANGED
@@ -16,11 +16,18 @@ describe ChronoModel::TimeMachine do
|
|
16
16
|
bar = ts_eval { Bar.create! :name => 'bar', :foo => foo }
|
17
17
|
ts_eval(bar) { update_attributes! :name => 'foo bar' }
|
18
18
|
|
19
|
+
#
|
20
|
+
subbar = ts_eval { SubBar.create! :name => 'sub-bar', :bar => bar }
|
21
|
+
ts_eval(subbar) { update_attributes! :name => 'bar sub-bar' }
|
22
|
+
|
19
23
|
ts_eval(foo) { update_attributes! :name => 'new foo' }
|
20
24
|
|
21
25
|
ts_eval(bar) { update_attributes! :name => 'bar bar' }
|
22
26
|
ts_eval(bar) { update_attributes! :name => 'new bar' }
|
23
27
|
|
28
|
+
ts_eval(subbar) { update_attributes! :name => 'sub-bar sub-bar' }
|
29
|
+
ts_eval(subbar) { update_attributes! :name => 'new sub-bar' }
|
30
|
+
|
24
31
|
#
|
25
32
|
baz = Baz.create :name => 'baz', :bar => bar
|
26
33
|
|
@@ -145,6 +152,16 @@ describe ChronoModel::TimeMachine do
|
|
145
152
|
it { expect(Foo.as_of(bar.ts[3]).includes(:bars).first.bars.first.name).to eq 'new bar' }
|
146
153
|
|
147
154
|
|
155
|
+
it { expect(Foo.as_of(foo.ts[0]).includes(bars: :sub_bars).first.bars).to eq [] }
|
156
|
+
it { expect(Foo.as_of(foo.ts[1]).includes(bars: :sub_bars).first.bars).to eq [] }
|
157
|
+
it { expect(Foo.as_of(foo.ts[2]).includes(bars: :sub_bars).first.bars).to eq [bar] }
|
158
|
+
|
159
|
+
it { expect(Foo.as_of(bar.ts[0]).includes(bars: :sub_bars).first.bars.first.name).to eq 'bar' }
|
160
|
+
it { expect(Foo.as_of(bar.ts[1]).includes(bars: :sub_bars).first.bars.first.name).to eq 'foo bar' }
|
161
|
+
it { expect(Foo.as_of(bar.ts[2]).includes(bars: :sub_bars).first.bars.first.name).to eq 'bar bar' }
|
162
|
+
it { expect(Foo.as_of(bar.ts[3]).includes(bars: :sub_bars).first.bars.first.name).to eq 'new bar' }
|
163
|
+
|
164
|
+
|
148
165
|
it { expect(Bar.as_of(bar.ts[0]).includes(:foo).first.foo).to eq foo }
|
149
166
|
it { expect(Bar.as_of(bar.ts[1]).includes(:foo).first.foo).to eq foo }
|
150
167
|
it { expect(Bar.as_of(bar.ts[2]).includes(:foo).first.foo).to eq foo }
|
@@ -154,6 +171,18 @@ describe ChronoModel::TimeMachine do
|
|
154
171
|
it { expect(Bar.as_of(bar.ts[1]).includes(:foo).first.foo.name).to eq 'foo bar' }
|
155
172
|
it { expect(Bar.as_of(bar.ts[2]).includes(:foo).first.foo.name).to eq 'new foo' }
|
156
173
|
it { expect(Bar.as_of(bar.ts[3]).includes(:foo).first.foo.name).to eq 'new foo' }
|
174
|
+
|
175
|
+
|
176
|
+
it { expect(Bar.as_of(bar.ts[0]).includes(foo: :sub_bars).first.foo).to eq foo }
|
177
|
+
it { expect(Bar.as_of(bar.ts[1]).includes(foo: :sub_bars).first.foo).to eq foo }
|
178
|
+
it { expect(Bar.as_of(bar.ts[2]).includes(foo: :sub_bars).first.foo).to eq foo }
|
179
|
+
it { expect(Bar.as_of(bar.ts[3]).includes(foo: :sub_bars).first.foo).to eq foo }
|
180
|
+
|
181
|
+
it { expect(Bar.as_of(bar.ts[0]).includes(foo: :sub_bars).first.foo.name).to eq 'foo bar' }
|
182
|
+
it { expect(Bar.as_of(bar.ts[1]).includes(foo: :sub_bars).first.foo.name).to eq 'foo bar' }
|
183
|
+
it { expect(Bar.as_of(bar.ts[2]).includes(foo: :sub_bars).first.foo.name).to eq 'new foo' }
|
184
|
+
it { expect(Bar.as_of(bar.ts[3]).includes(foo: :sub_bars).first.foo.name).to eq 'new foo' }
|
185
|
+
|
157
186
|
end
|
158
187
|
|
159
188
|
it 'doesn\'t raise RecordNotFound when no history records are found' do
|
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: 0.
|
4
|
+
version: 0.11.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: 2017-
|
12
|
+
date: 2017-09-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|