chrono_model 0.10.1 → 0.11.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.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
|