chrono_model 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +11 -10
- data/Gemfile +0 -19
- data/README.md +55 -26
- data/chrono_model.gemspec +11 -3
- data/lib/chrono_model.rb +10 -0
- data/lib/chrono_model/adapter.rb +46 -26
- data/lib/chrono_model/patches.rb +69 -6
- data/lib/chrono_model/schema_format.rake +23 -7
- data/lib/chrono_model/schema_format.rb +2 -1
- data/lib/chrono_model/time_gate.rb +3 -11
- data/lib/chrono_model/time_machine.rb +53 -102
- data/lib/chrono_model/version.rb +1 -1
- data/spec/adapter_spec.rb +115 -111
- data/spec/json_ops_spec.rb +5 -5
- data/spec/spec_helper.rb +3 -2
- data/spec/support/matchers/base.rb +5 -8
- data/spec/support/matchers/column.rb +22 -9
- data/spec/support/matchers/index.rb +8 -4
- data/spec/support/matchers/schema.rb +5 -1
- data/spec/support/matchers/table.rb +55 -24
- data/spec/time_machine_spec.rb +195 -176
- data/spec/time_query_spec.rb +39 -39
- metadata +116 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46c5c3d3c7a91b99e025dbf9dbd961962cc36914
|
4
|
+
data.tar.gz: 0d120a3f68b711f77031e3e3df5d203efd812a23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17f0b8f514b94579d9cd5f4b32fad9c5d8bed4d95282c361acf10f930d7f1fd64d503139b6eb6f5bde6e9c2c51134f3bcfafdb5034b0dde95af1814bdae563c4
|
7
|
+
data.tar.gz: 4f7d2ee5109d774d3217cec430bf74964b269e9fff6c8b790a6a328fd7bcb043c3e18c87defc3601a4c85e2c2a597bf314c4e0a0251054ca923d4250cc89b6c2
|
data/.travis.yml
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
rvm:
|
2
2
|
- 1.9.3
|
3
3
|
- 2.0.0
|
4
|
-
- 2.1.
|
4
|
+
- 2.1.7
|
5
|
+
- 2.2.3
|
5
6
|
|
6
|
-
|
7
|
-
- postgresql
|
7
|
+
sudo: false
|
8
8
|
|
9
|
-
|
9
|
+
language: ruby
|
10
|
+
cache: bundler
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- sudo service postgresql start 9.3
|
12
|
+
addons:
|
13
|
+
postgresql: "9.4"
|
14
|
+
apt:
|
15
|
+
packages: postgresql-plpython-9.4
|
16
16
|
|
17
17
|
before_script:
|
18
18
|
- psql -c "CREATE DATABASE chronomodel;" -U postgres
|
19
19
|
|
20
|
-
script:
|
20
|
+
script:
|
21
|
+
- bundle exec rake TEST_CONFIG=./spec/config.travis.yml CODECLIMATE_REPO_TOKEN=dedfb7472ee410eec459bff3681d9a8fd8dd237e9bd7e8675a7c8eb7e253bba9
|
data/Gemfile
CHANGED
@@ -2,22 +2,3 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in chrono_model.gemspec
|
4
4
|
gemspec
|
5
|
-
|
6
|
-
group :development do
|
7
|
-
gem 'pry'
|
8
|
-
gem 'hirb'
|
9
|
-
|
10
|
-
gem(
|
11
|
-
case RUBY_VERSION.to_f
|
12
|
-
when 1.8 then 'ruby-debug'
|
13
|
-
when 1.9 then 'debugger'
|
14
|
-
else 'byebug'
|
15
|
-
end
|
16
|
-
)
|
17
|
-
end
|
18
|
-
|
19
|
-
group :test do
|
20
|
-
gem 'rspec'
|
21
|
-
gem 'rake'
|
22
|
-
gem 'fuubar'
|
23
|
-
end
|
data/README.md
CHANGED
@@ -1,6 +1,17 @@
|
|
1
|
-
# Temporal database system on PostgreSQL using [updatable views][], [table inheritance][] and [INSTEAD OF triggers][].
|
1
|
+
# Temporal database system on PostgreSQL using [updatable views][], [table inheritance][] and [INSTEAD OF triggers][].
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status][build-status-badge]][build-status]
|
4
|
+
[![Dependency Status][deps-status-badge]][deps-status]
|
5
|
+
[![Code Climate][code-analysis-badge]][code-analysis]
|
6
|
+
[![Test Coverage][test-coverage-badge]][test-coverage]
|
7
|
+
[![Inlinedocs][docs-analysis-badge]][docs-analysis]
|
8
|
+
|
9
|
+
![{chronos - greek god of time}][chronos-image]
|
10
|
+
|
11
|
+
> Chronos, the greek god of time.
|
12
|
+
> Courtesy of [REBELLE SOCIETY][rebelle-society]
|
13
|
+
|
14
|
+
ChronoModel implements what Oracle sells as "Flashback Queries", with standard
|
4
15
|
SQL on free PostgreSQL. Academically speaking, ChronoModel implements a
|
5
16
|
[Type-2 Slowly-Changing Dimension][wp-scd-2] with [history tables][wp-scd-4].
|
6
17
|
|
@@ -52,10 +63,17 @@ All timestamps are _forcibly_ stored in as UTC, bypassing the
|
|
52
63
|
## Requirements
|
53
64
|
|
54
65
|
* Ruby >= 2.0 (1.9 is still supported, but support will be dropped soon).
|
55
|
-
* Active Record = 4.
|
66
|
+
* Active Record = 4.2
|
56
67
|
* PostgreSQL >= 9.3
|
57
|
-
* The `btree_gist` PostgreSQL
|
68
|
+
* The `btree_gist` and `plpython` PostgreSQL extensions:
|
69
|
+
|
70
|
+
With Homebrew:
|
58
71
|
|
72
|
+
brew install --with-python postgres
|
73
|
+
|
74
|
+
With Apt:
|
75
|
+
|
76
|
+
apt-get install postgresql-plpython
|
59
77
|
|
60
78
|
## Installation
|
61
79
|
|
@@ -236,7 +254,7 @@ History objects can be changed and `.save`d just like any other record.
|
|
236
254
|
|
237
255
|
## Running tests
|
238
256
|
|
239
|
-
You need a running PostgreSQL 9.3 instance. Create `spec/config.yml` with the
|
257
|
+
You need a running PostgreSQL >= 9.3 instance. Create `spec/config.yml` with the
|
240
258
|
connection authentication details (use `spec/config.yml.example` as template).
|
241
259
|
|
242
260
|
You need to connect as a database superuser, because specs need to create the
|
@@ -257,6 +275,10 @@ comparison of JSON objects [implemented in pl/python][json-opclass].
|
|
257
275
|
To load the opclass you can use the `ChronoModel::Json.create`
|
258
276
|
convenience method. If you don't use JSON don't bother doing this.
|
259
277
|
|
278
|
+
If you are on Postgres 9.4, you are strongly encouraged to use JSONB,
|
279
|
+
that has an equality operator built-in, it's faster and stricter, and
|
280
|
+
offers many more indexing abilities and better performance than JSON.
|
281
|
+
|
260
282
|
## Caveats
|
261
283
|
|
262
284
|
* Rails 4 support requires disabling tsrange parsing support, as it
|
@@ -264,7 +286,7 @@ convenience method. If you don't use JSON don't bother doing this.
|
|
264
286
|
as of now, mainly due to a [design clash with ruby][pg-tsrange-and-ruby].
|
265
287
|
|
266
288
|
* There is (yet) no upgrade path from [v0.5][chronomodel-0.5],
|
267
|
-
(PG 9.0-compatible, `box()` and hacks) to v0.6 and up (9.3-only, `tsrange`
|
289
|
+
(PG 9.0-compatible, `box()` and hacks) to v0.6 and up (>=9.3-only, `tsrange`
|
268
290
|
and _less_ hacks).
|
269
291
|
|
270
292
|
* The triggers and temporal indexes cannot be saved in schema.rb. The AR
|
@@ -296,40 +318,47 @@ convenience method. If you don't use JSON don't bother doing this.
|
|
296
318
|
|
297
319
|
|
298
320
|
[build-status]: https://travis-ci.org/ifad/chronomodel
|
299
|
-
[build-status-badge]: https://travis-ci.org/ifad/chronomodel.
|
321
|
+
[build-status-badge]: https://travis-ci.org/ifad/chronomodel.svg
|
300
322
|
[deps-status]: https://gemnasium.com/ifad/chronomodel
|
301
|
-
[deps-status-badge]: https://gemnasium.com/ifad/chronomodel.
|
323
|
+
[deps-status-badge]: https://gemnasium.com/ifad/chronomodel.svg
|
302
324
|
[code-analysis]: https://codeclimate.com/github/ifad/chronomodel
|
303
|
-
[code-analysis-badge]: https://codeclimate.com/github/ifad/chronomodel.
|
304
|
-
|
305
|
-
[
|
306
|
-
[
|
307
|
-
[
|
325
|
+
[code-analysis-badge]: https://codeclimate.com/github/ifad/chronomodel.svg
|
326
|
+
[docs-analysis]: http://inch-ci.org/github/ifad/chronomodel
|
327
|
+
[docs-analysis-badge]: http://inch-ci.org/github/ifad/chronomodel.svg?branch=master
|
328
|
+
[test-coverage]: https://codeclimate.com/github/ifad/chronomodel
|
329
|
+
[test-coverage-badge]: https://codeclimate.com/github/ifad/chronomodel/badges/coverage.svg
|
330
|
+
|
331
|
+
[chronos-image]: http://i.imgur.com/8NObYiZl.jpg
|
332
|
+
[rebelle-society]: http://www.rebellesociety.com/2012/10/11/the-writers-way-week-two-facing-procrastination/chronos_oeuvre_grand1/
|
333
|
+
|
334
|
+
[updatable views]: http://www.postgresql.org/docs/9.4/static/sql-createview.html#SQL-CREATEVIEW-UPDATABLE-VIEWS
|
335
|
+
[table inheritance]: http://www.postgresql.org/docs/9.4/static/ddl-inherit.html
|
336
|
+
[INSTEAD OF triggers]: http://www.postgresql.org/docs/9.4/static/sql-createtrigger.html
|
308
337
|
[wp-scd-2]: http://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2
|
309
338
|
[wp-scd-4]: http://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_4
|
310
|
-
[triggers]: http://www.postgresql.org/docs/9.
|
311
|
-
[schema]: http://www.postgresql.org/docs/9.
|
312
|
-
[inherits]: http://www.postgresql.org/docs/9.
|
313
|
-
[`current_timestamp`]: http://www.postgresql.org/docs/9.
|
339
|
+
[triggers]: http://www.postgresql.org/docs/9.4/static/trigger-definition.html
|
340
|
+
[schema]: http://www.postgresql.org/docs/9.4/static/ddl-schemas.html
|
341
|
+
[inherits]: http://www.postgresql.org/docs/9.4/static/ddl-inherit.html
|
342
|
+
[`current_timestamp`]: http://www.postgresql.org/docs/9.4/interactive/functions-datetime.html#FUNCTIONS-DATETIME-TABLE
|
314
343
|
|
315
|
-
[Partitioning]: http://www.postgresql.org/docs/9.
|
316
|
-
[partitioning-excl-constraints]: http://www.postgresql.org/docs/9.
|
344
|
+
[Partitioning]: http://www.postgresql.org/docs/9.4/static/ddl-partitioning.html)
|
345
|
+
[partitioning-excl-constraints]: http://www.postgresql.org/docs/9.4/static/ddl-partitioning.html#DDL-PARTITIONING-CONSTRAINT-EXCLUSION
|
317
346
|
[README.sql]: https://github.com/ifad/chronomodel/blob/master/README.sql
|
318
|
-
[GiST indexes]: http://www.postgresql.org/docs/9.
|
319
|
-
[exclusion constraints]: http://www.postgresql.org/docs/9.
|
320
|
-
[btree_gist]: http://www.postgresql.org/docs/9.
|
321
|
-
[COMMENT]: http://www.postgresql.org/docs/9.
|
347
|
+
[GiST indexes]: http://www.postgresql.org/docs/9.4/static/gist.html
|
348
|
+
[exclusion constraints]: http://www.postgresql.org/docs/9.4/static/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
|
349
|
+
[btree_gist]: http://www.postgresql.org/docs/9.4/static/btree-gist.html
|
350
|
+
[COMMENT]: http://www.postgresql.org/docs/9.4/static/sql-comment.html
|
322
351
|
[TimeMachine]: https://github.com/ifad/chronomodel/blob/master/lib/chrono_model/time_machine.rb
|
323
352
|
|
324
353
|
[r4-tsrange-broken]: https://github.com/rails/rails/pull/13793#issuecomment-34608093
|
325
354
|
[r4-tsrange-incomplete]: https://github.com/rails/rails/issues/14010)
|
326
355
|
[pg-tsrange-and-ruby]: https://bugs.ruby-lang.org/issues/6864
|
327
356
|
[chronomodel-0.5]: https://github.com/ifad/chronomodel/tree/c2daa0f
|
328
|
-
[Common Table Expressions]: http://www.postgresql.org/docs/9.
|
357
|
+
[Common Table Expressions]: http://www.postgresql.org/docs/9.4/static/queries-with.html
|
329
358
|
[cte-optimization-fence]: http://archives.postgresql.org/pgsql-hackers/2012-09/msg00700.php
|
330
359
|
[cte-opt-out-fence]: http://archives.postgresql.org/pgsql-hackers/2012-10/msg00024.php
|
331
360
|
[chronomodel-cte-impl]: https://github.com/ifad/chronomodel/commit/18f4c4b
|
332
361
|
|
333
|
-
[json-type]: http://www.postgresql.org/docs/9.
|
334
|
-
[json-func]: http://www.postgresql.org/docs/9.
|
362
|
+
[json-type]: http://www.postgresql.org/docs/9.4/static/datatype-json.html
|
363
|
+
[json-func]: http://www.postgresql.org/docs/9.4/static/functions-json.html
|
335
364
|
[json-opclass]: https://github.com/ifad/chronomodel/blob/master/sql/json_ops.sql
|
data/chrono_model.gemspec
CHANGED
@@ -4,9 +4,9 @@ require File.expand_path('../lib/chrono_model/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ['Marcello Barnaba', 'Peter Joseph Brindisi']
|
6
6
|
gem.email = ['vjt@openssl.it', 'p.brindisi@ifad.org']
|
7
|
-
gem.description = %q{Give your models as-of date temporal extensions. Built entirely for PostgreSQL >= 9.
|
7
|
+
gem.description = %q{Give your models as-of date temporal extensions. Built entirely for PostgreSQL >= 9.3}
|
8
8
|
gem.summary = %q{Temporal extensions (SCD Type II) for Active Record}
|
9
|
-
gem.homepage = '
|
9
|
+
gem.homepage = 'https://github.com/ifad/chronomodel'
|
10
10
|
|
11
11
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
12
|
gem.files = `git ls-files`.split("\n")
|
@@ -15,7 +15,15 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = ChronoModel::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency "activerecord", "~> 4.
|
18
|
+
gem.add_dependency "activerecord", "~> 4.2.0"
|
19
19
|
gem.add_dependency "pg"
|
20
20
|
gem.add_dependency "multi_json"
|
21
|
+
|
22
|
+
gem.add_development_dependency 'pry'
|
23
|
+
gem.add_development_dependency 'hirb'
|
24
|
+
gem.add_development_dependency RUBY_VERSION.to_f == 1.9 ? 'debugger' : 'byebug'
|
25
|
+
gem.add_development_dependency 'rspec'
|
26
|
+
gem.add_development_dependency 'rake'
|
27
|
+
gem.add_development_dependency 'fuubar'
|
28
|
+
gem.add_development_dependency 'codeclimate-test-reporter'
|
21
29
|
end
|
data/lib/chrono_model.rb
CHANGED
@@ -23,6 +23,10 @@ if RUBY_VERSION.to_i >= 2
|
|
23
23
|
ActiveRecord::Associations::Association.instance_eval do
|
24
24
|
prepend ChronoModel::Patches::Association
|
25
25
|
end
|
26
|
+
|
27
|
+
ActiveRecord::Relation.instance_eval do
|
28
|
+
prepend ChronoModel::Patches::Relation
|
29
|
+
end
|
26
30
|
else
|
27
31
|
ActiveSupport::Deprecation.warn 'Ruby 1.9 is deprecated. Please update your Ruby <3'
|
28
32
|
|
@@ -32,5 +36,11 @@ else
|
|
32
36
|
end
|
33
37
|
|
34
38
|
ActiveRecord::Associations::Association = ChronoModel::Patches::AssociationPatch
|
39
|
+
|
40
|
+
class ChronoModel::Patches::RelationPatch < ActiveRecord::Relation
|
41
|
+
include ChronoModel::Patches::Relation
|
42
|
+
end
|
43
|
+
|
44
|
+
ActiveRecord::Relation = ChronoModel::Patches::RelationPatch
|
35
45
|
end
|
36
46
|
end
|
data/lib/chrono_model/adapter.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'active_record/connection_adapters/postgresql_adapter'
|
3
3
|
|
4
|
+
require 'multi_json'
|
5
|
+
|
4
6
|
module ChronoModel
|
5
7
|
|
6
8
|
# This class implements all ActiveRecord::ConnectionAdapters::SchemaStatements
|
@@ -285,7 +287,19 @@ module ChronoModel
|
|
285
287
|
chrono_alter(table_name) { super }
|
286
288
|
end
|
287
289
|
|
288
|
-
# Runs column_definitions
|
290
|
+
# Runs column_definitions in the temporal schema, as the table there
|
291
|
+
# defined is the source for this information.
|
292
|
+
#
|
293
|
+
# The default search path is included however, since the table
|
294
|
+
# may reference types defined in other schemas, which result in their
|
295
|
+
# names becoming schema qualified, which will cause type resolutions to fail.
|
296
|
+
#
|
297
|
+
define_method(:column_definitions) do |table_name|
|
298
|
+
return super(table_name) unless is_chrono?(table_name)
|
299
|
+
on_schema(TEMPORAL_SCHEMA + ',' + self.schema_search_path, false) { super(table_name) }
|
300
|
+
end
|
301
|
+
|
302
|
+
# Runs primary_key, indexes and default_sequence_name in the temporal schema,
|
289
303
|
# as the table there defined is the source for this information.
|
290
304
|
#
|
291
305
|
# Moreover, the PostgreSQLAdapter +indexes+ method uses current_schema(),
|
@@ -294,10 +308,11 @@ module ChronoModel
|
|
294
308
|
# Schema nesting is disabled on these calls, make sure to fetch metadata
|
295
309
|
# from the first caller's selected schema and not from the current one.
|
296
310
|
#
|
297
|
-
[:
|
298
|
-
define_method(method) do |
|
299
|
-
|
300
|
-
|
311
|
+
[:primary_key, :indexes, :default_sequence_name].each do |method|
|
312
|
+
define_method(method) do |*args|
|
313
|
+
table_name = args.first
|
314
|
+
return super(*args) unless is_chrono?(table_name)
|
315
|
+
_on_temporal_schema(false) { super(*args) }
|
301
316
|
end
|
302
317
|
end
|
303
318
|
|
@@ -454,6 +469,12 @@ module ChronoModel
|
|
454
469
|
end
|
455
470
|
end
|
456
471
|
|
472
|
+
def chrono_setup!
|
473
|
+
chrono_create_schemas
|
474
|
+
|
475
|
+
chrono_upgrade_structure!
|
476
|
+
end
|
477
|
+
|
457
478
|
# HACK: Redefine tsrange parsing support, as it is broken currently.
|
458
479
|
#
|
459
480
|
# This self-made API is here because currently AR4 does not support
|
@@ -468,18 +489,13 @@ module ChronoModel
|
|
468
489
|
#
|
469
490
|
# so, for now, we are implementing our own.
|
470
491
|
#
|
471
|
-
class TSRange < OID::
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
#exclude_start: (value[0] == '('),
|
478
|
-
#exclude_end: (value[-1] == ')')
|
479
|
-
}
|
480
|
-
end
|
492
|
+
class TSRange < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range
|
493
|
+
OID = 3908
|
494
|
+
|
495
|
+
def cast_value(value)
|
496
|
+
return if value == 'empty'
|
497
|
+
return value if value.is_a?(::Array)
|
481
498
|
|
482
|
-
def type_cast(value)
|
483
499
|
extracted = extract_bounds(value)
|
484
500
|
|
485
501
|
from = Conversions.string_to_utc_time extracted[:from]
|
@@ -487,13 +503,23 @@ module ChronoModel
|
|
487
503
|
|
488
504
|
[from, to]
|
489
505
|
end
|
506
|
+
|
507
|
+
def extract_bounds(value)
|
508
|
+
from, to = value[1..-2].split(',')
|
509
|
+
{
|
510
|
+
from: (value[1] == ',' || from == '-infinity') ? nil : from[1..-2],
|
511
|
+
to: (value[-2] == ',' || to == 'infinity') ? nil : to[1..-2],
|
512
|
+
}
|
513
|
+
end
|
490
514
|
end
|
491
515
|
|
492
|
-
def
|
493
|
-
|
494
|
-
|
516
|
+
def initialize_type_map(type_map)
|
517
|
+
super.tap do
|
518
|
+
ar_type = type_map.fetch(TSRange::OID)
|
519
|
+
cm_type = TSRange.new(ar_type.subtype, ar_type.type)
|
495
520
|
|
496
|
-
|
521
|
+
type_map.register_type TSRange::OID, cm_type
|
522
|
+
end
|
497
523
|
end
|
498
524
|
|
499
525
|
# Copy the indexes from the temporal table to the history table if the indexes
|
@@ -529,12 +555,6 @@ module ChronoModel
|
|
529
555
|
end
|
530
556
|
end
|
531
557
|
|
532
|
-
# Adds the above TSRange class to the PG Adapter OID::TYPE_MAP
|
533
|
-
#
|
534
|
-
def chrono_setup_type_map
|
535
|
-
OID::TYPE_MAP[3908] = TSRange.new
|
536
|
-
end
|
537
|
-
|
538
558
|
# Upgrades existing structure for each table, if required.
|
539
559
|
# TODO: allow upgrades from pre-0.6 structure with box() and stuff.
|
540
560
|
#
|
data/lib/chrono_model/patches.rb
CHANGED
@@ -3,6 +3,60 @@ require 'active_record'
|
|
3
3
|
module ChronoModel
|
4
4
|
module Patches
|
5
5
|
|
6
|
+
module AsOfTimeHolder
|
7
|
+
# Sets the virtual 'as_of_time' attribute to the given time, converting to UTC.
|
8
|
+
#
|
9
|
+
# See ChronoModel::Patches::AsOfTimeHolder
|
10
|
+
#
|
11
|
+
def as_of_time!(time)
|
12
|
+
@_as_of_time = time.utc
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
# Reads the virtual 'as_of_time' attribute
|
18
|
+
#
|
19
|
+
# See ChronoModel::Patches::AsOfTimeHolder
|
20
|
+
#
|
21
|
+
def as_of_time
|
22
|
+
@_as_of_time
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Relation
|
27
|
+
include AsOfTimeHolder
|
28
|
+
|
29
|
+
def load
|
30
|
+
return super unless @_as_of_time && !loaded?
|
31
|
+
|
32
|
+
super.each {|record| record.as_of_time!(@_as_of_time) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def merge(*)
|
36
|
+
return super unless @_as_of_time
|
37
|
+
|
38
|
+
super.as_of_time!(@_as_of_time)
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_arel
|
42
|
+
return super unless @_as_of_time
|
43
|
+
|
44
|
+
super.tap do |arel|
|
45
|
+
|
46
|
+
arel.join_sources.each do |join|
|
47
|
+
model = TimeMachine.chrono_models[join.left.table_name]
|
48
|
+
next unless model
|
49
|
+
|
50
|
+
join.left = Arel::Nodes::SqlLiteral.new(
|
51
|
+
model.history.virtual_table_at(@_as_of_time,
|
52
|
+
join.left.table_alias || join.left.table_name)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
6
60
|
# Patches ActiveRecord::Associations::Association to add support for
|
7
61
|
# temporal associations.
|
8
62
|
#
|
@@ -12,6 +66,9 @@ module ChronoModel
|
|
12
66
|
# on the join model's (:through association) one.
|
13
67
|
#
|
14
68
|
module Association
|
69
|
+
def skip_statement_cache?
|
70
|
+
super || _chrono_target?
|
71
|
+
end
|
15
72
|
|
16
73
|
# If the association class or the through association are ChronoModels,
|
17
74
|
# then fetches the records from a virtual table using a subquery scope
|
@@ -20,11 +77,7 @@ module ChronoModel
|
|
20
77
|
scope = super
|
21
78
|
return scope unless _chrono_record?
|
22
79
|
|
23
|
-
|
24
|
-
owner.public_send(reflection.foreign_type).constantize :
|
25
|
-
reflection.klass
|
26
|
-
|
27
|
-
if klass.chrono?
|
80
|
+
if _chrono_target?
|
28
81
|
# For standard associations, replace the table name with the virtual
|
29
82
|
# as-of table name at the owner's as-of-time
|
30
83
|
#
|
@@ -54,9 +107,10 @@ module ChronoModel
|
|
54
107
|
join.left.table_alias = table_alias
|
55
108
|
end
|
56
109
|
end
|
57
|
-
|
58
110
|
end
|
59
111
|
|
112
|
+
scope.as_of_time!(owner.as_of_time)
|
113
|
+
|
60
114
|
return scope
|
61
115
|
end
|
62
116
|
|
@@ -64,6 +118,15 @@ module ChronoModel
|
|
64
118
|
def _chrono_record?
|
65
119
|
owner.respond_to?(:as_of_time) && owner.as_of_time.present?
|
66
120
|
end
|
121
|
+
|
122
|
+
def _chrono_target?
|
123
|
+
@_target_klass ||= reflection.options[:polymorphic] ?
|
124
|
+
owner.public_send(reflection.foreign_type).constantize :
|
125
|
+
reflection.klass
|
126
|
+
|
127
|
+
@_target_klass.chrono?
|
128
|
+
end
|
129
|
+
|
67
130
|
end
|
68
131
|
|
69
132
|
end
|